<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/performance/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/performance/index.xml" rel="self" type="application/rss+xml"/><item><title>Review: Minisforum MS-S1 Max | AMD AI Max+ 395 com 96GB de VRAM</title><link>https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/</guid><pubDate>Tue, 31 Mar 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;Quem acompanha meus posts sobre &lt;a href="https://akitaonrails.github.io/2024/04/03/meu-netflix-pessoal-com-docker-compose/"&gt;home server&lt;/a&gt; sabe que eu rodava tudo num Intel NUC Core i7 com 32GB de RAM. Funcionava. Mas com o crescimento dos modelos de IA open source, o NUC virou um gargalo. Sem GPU dedicada, qualquer inferência de LLM ia pra CPU e ficava inutilizável.&lt;/p&gt;
&lt;p&gt;Eu comprei um Minisforum MS-S1 Max com o novo chip AMD Ryzen AI Max+ 395 por um motivo específico: esse chip suporta até 128GB de RAM unificada, e eu posso alocar 96GB como VRAM pro iGPU. Isso me dá mais VRAM do que qualquer placa gamer comercial, incluindo a RTX 5090 (32GB). E isso muda o que eu consigo rodar localmente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Quem acompanha meus posts sobre <a href="/2024/04/03/meu-netflix-pessoal-com-docker-compose/">home server</a> sabe que eu rodava tudo num Intel NUC Core i7 com 32GB de RAM. Funcionava. Mas com o crescimento dos modelos de IA open source, o NUC virou um gargalo. Sem GPU dedicada, qualquer inferência de LLM ia pra CPU e ficava inutilizável.</p>
<p>Eu comprei um Minisforum MS-S1 Max com o novo chip AMD Ryzen AI Max+ 395 por um motivo específico: esse chip suporta até 128GB de RAM unificada, e eu posso alocar 96GB como VRAM pro iGPU. Isso me dá mais VRAM do que qualquer placa gamer comercial, incluindo a RTX 5090 (32GB). E isso muda o que eu consigo rodar localmente.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-desk.jpg" alt="Minisforum MS-S1 Max na mesa"  loading="lazy" /></p>
<h2>Por que trocar o Intel NUC<span class="hx:absolute hx:-mt-20" id="por-que-trocar-o-intel-nuc"></span>
    <a href="#por-que-trocar-o-intel-nuc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O NUC serviu bem como servidor Docker por dois anos. Mas a limitação era clara: sem GPU com VRAM suficiente, eu não conseguia rodar LLMs localmente de forma usável. O <a href="https://github.com/akitaonrails/FrankYomik"target="_blank" rel="noopener">Frank Yomik</a>, meu sistema de tradução automática de mangás, precisava de OCR via CPU (lento) e conectava remotamente no Ollama do meu desktop (AMD 7950X3D + RTX 5090) pra tradução. Funcionava, mas significava que meu desktop precisava estar ligado pro servidor funcionar.</p>
<p><img src="https://raw.githubusercontent.com/akitaonrails/FrankYomik/master/docs/sample_translate.png" alt="Frank Yomik - tradução automática de mangás"  loading="lazy" /></p>
<p>Com o Minisforum, o Frank Yomik roda inteiramente no servidor. O worker agora usa ROCm pra OCR com a iGPU, e o Ollama roda local com 96GB de VRAM. Zero dependência do desktop.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-nuc-compare.jpg" alt="Comparação: Intel NUC (esquerda) vs Minisforum MS-S1 Max (direita)"  loading="lazy" /></p>
<p>Na foto dá pra ter uma ideia do tamanho. O NUC é aquele cubinho pequeno na esquerda. O Minisforum é maior, mas ainda é um mini-PC. Cabe na prateleira do rack embaixo do Synology NAS sem problema.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-shelf.jpg" alt="Minisforum instalado na prateleira, ao lado do NAS"  loading="lazy" /></p>
<h2>As specs<span class="hx:absolute hx:-mt-20" id="as-specs"></span>
    <a href="#as-specs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-fastfetch.png" alt="fastfetch do Minisforum"  loading="lazy" /></p>
<p>O chip é o AMD Ryzen AI Max+ 395: 16 cores / 32 threads Zen 5, com iGPU Radeon 8060S integrada e 128GB de LPDDR5X unificada. No BIOS, eu configurei UMA Frame Buffer Size pra 96GB, o que deixa ~30GB de RAM pro sistema operacional e containers. Mais os parâmetros de kernel pra TTM (sem eles, o ROCm só enxerga 15.5GB mesmo com a alocação do BIOS).</p>
<p>O sistema operacional é openSUSE MicroOS (mais sobre isso no <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">próximo post</a>). O consumo de energia da máquina inteira fica abaixo de 100W, o que é absurdo pra quem está acostumado com GPUs dedicadas que puxam 450W+ sozinhas.</p>
<h2>Minisforum vs meu desktop: benchmarks<span class="hx:absolute hx:-mt-20" id="minisforum-vs-meu-desktop-benchmarks"></span>
    <a href="#minisforum-vs-meu-desktop-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu rodei um <a href="https://github.com/akitaonrails/homelab-docs/tree/master/benchmarks"target="_blank" rel="noopener">conjunto de benchmarks</a> comparando o Minisforum com meu desktop (AMD 7950X3D, 96GB DDR5, RTX 5090 32GB GDDR7). Os resultados são claros.</p>
<h3>CPU<span class="hx:absolute hx:-mt-20" id="cpu"></span>
    <a href="#cpu" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Teste</th>
          <th>7950X3D</th>
          <th>AI Max+ 395</th>
          <th>Vantagem</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prime sieve (single-core)</td>
          <td>0.021s</td>
          <td>0.018s</td>
          <td>Strix Halo +14%</td>
      </tr>
      <tr>
          <td>Float pi (single-core)</td>
          <td>1.335s</td>
          <td>1.706s</td>
          <td>7950X3D +28%</td>
      </tr>
      <tr>
          <td>Multi-core sieve (32 threads)</td>
          <td>0.181s</td>
          <td>0.118s</td>
          <td>Strix Halo +53%</td>
      </tr>
      <tr>
          <td>SHA-256 throughput</td>
          <td>2.714 MB/s</td>
          <td>2.488 MB/s</td>
          <td>7950X3D +9%</td>
      </tr>
      <tr>
          <td>AES-256-CBC throughput</td>
          <td>1.613 MB/s</td>
          <td>1.410 MB/s</td>
          <td>7950X3D +14%</td>
      </tr>
  </tbody>
</table>
<p>Resultados mistos. O AI Max+ 395 é melhor em paralelismo puro (sieve multi-core), provavelmente por conta da latência menor na arquitetura de memória unificada. O 7950X3D ganha em float e crypto por causa dos clocks mais altos e do 3D V-Cache.</p>
<h3>Inferência de LLMs (modelos que cabem nos dois)<span class="hx:absolute hx:-mt-20" id="inferência-de-llms-modelos-que-cabem-nos-dois"></span>
    <a href="#infer%c3%aancia-de-llms-modelos-que-cabem-nos-dois" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui é onde a coisa fica interessante. Pra modelos que cabem nos 32GB da RTX 5090, a comparação é puramente de bandwidth de memória:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>Vantagem 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>9.1 GB</td>
          <td>155.1</td>
          <td>23.2</td>
          <td>6.7x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>9.3 GB</td>
          <td>138.9</td>
          <td>22.6</td>
          <td>6.1x</td>
      </tr>
      <tr>
          <td>phi4-reasoning</td>
          <td>11.1 GB</td>
          <td>130.2</td>
          <td>19.1</td>
          <td>6.8x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>20.2 GB</td>
          <td>66.9</td>
          <td>10.0</td>
          <td>6.7x</td>
      </tr>
  </tbody>
</table>
<p>A RTX 5090 é ~7x mais rápida. A explicação é simples: GDDR7 tem ~1.792 GB/s de bandwidth. LPDDR5X tem ~256 GB/s. A razão (7x) bate quase exatamente com a diferença de velocidade medida (6.7x). Inferência de LLM é um problema dominado por bandwidth de memória. Quem lê pesos mais rápido, gera tokens mais rápido.</p>
<h3>E o prompt processing?<span class="hx:absolute hx:-mt-20" id="e-o-prompt-processing"></span>
    <a href="#e-o-prompt-processing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>Vantagem 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>~1.933</td>
          <td>~212</td>
          <td>9.1x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>~1.474</td>
          <td>~155</td>
          <td>9.5x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>~767</td>
          <td>~68</td>
          <td>11.3x</td>
      </tr>
  </tbody>
</table>
<p>Prompt processing é ainda pior: 7-11x mais lento. Faz sentido, porque o prompt precisa ser processado inteiro antes de gerar o primeiro token, e é uma operação ainda mais bandwidth-intensiva.</p>
<h3>Onde o Strix Halo ganha: modelos grandes<span class="hx:absolute hx:-mt-20" id="onde-o-strix-halo-ganha-modelos-grandes"></span>
    <a href="#onde-o-strix-halo-ganha-modelos-grandes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem a razão pela qual eu comprei esse PC. Modelos que não cabem na RTX 5090:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>Strix Halo (tok/s)</th>
          <th>Notas</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>gpt-oss:20b</td>
          <td>13.8 GB MXFP4</td>
          <td>48.9</td>
          <td>MoE, mais rápido que esperado</td>
      </tr>
      <tr>
          <td>qwen3.5:35b</td>
          <td>23.9 GB</td>
          <td>43.2</td>
          <td>MoE, apenas ~4B params ativos</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>51.7 GB</td>
          <td>29.5</td>
          <td>MoE, 50GB+</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81.4 GB Q4_K_M</td>
          <td>19.2</td>
          <td>122B params, MoE</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>59.9 GB</td>
          <td>17.9</td>
          <td>Full precision bf16</td>
      </tr>
      <tr>
          <td>qwen2.5:72b</td>
          <td>47.4 GB Q4_K_M</td>
          <td>4.5</td>
          <td>Dense 72B, bandwidth-limited</td>
      </tr>
  </tbody>
</table>
<p>O qwen3.5:122b com 81GB de pesos rodando a 19 tok/s. Num mini-PC. Isso simplesmente não é possível numa RTX 5090. Na placa da NVIDIA, esse modelo teria que fazer offload de camadas pra RAM do sistema, caindo pra 2-3 tok/s. Na prática, inutilizável.</p>
<p>A diferença entre modelos MoE e densos é brutal. O qwen3.5:35b roda a 43 tok/s porque, apesar de ter 35B de parâmetros totais, só ~4B ficam ativos por token. Um modelo denso de 72B como o qwen2.5:72b precisa ler 40GB+ de pesos por token, e a 256 GB/s de bandwidth, o máximo teórico é ~6.7 tok/s. Os 4.5 medidos representam ~67% de eficiência, que é o esperado pra iGPU (overhead de bus compartilhado e drivers).</p>
<h3>Resumo: quando usar cada máquina<span class="hx:absolute hx:-mt-20" id="resumo-quando-usar-cada-máquina"></span>
    <a href="#resumo-quando-usar-cada-m%c3%a1quina" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Caso de uso</th>
          <th>Melhor máquina</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chat/coding interativo (modelos &lt;32GB)</td>
          <td>RTX 5090 (6-7x mais rápido)</td>
      </tr>
      <tr>
          <td>Modelos grandes (50GB+)</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Modelos densos 70B+</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Full-precision bf16</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Batch processing com contexto longo</td>
          <td>Strix Halo (mais VRAM pra KV cache)</td>
      </tr>
      <tr>
          <td>API serving com baixa latência</td>
          <td>RTX 5090 (sub-150ms TTFT)</td>
      </tr>
  </tbody>
</table>
<h3>Um bug de ROCm que ainda existe<span class="hx:absolute hx:-mt-20" id="um-bug-de-rocm-que-ainda-existe"></span>
    <a href="#um-bug-de-rocm-que-ainda-existe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nem tudo funciona. Modelos como deepseek-r1:70b, llama3.3:70b e llama4:scout crasham com um bug no ggml (<code>GGML_ASSERT(ggml_nbytes(src0) &lt;= INT_MAX) failed</code>). O tensor de embedding desses modelos excede 2GB e o kernel de cópia do ROCm usa inteiro de 32 bits pro tamanho. No CUDA (NVIDIA) já foi corrigido, mas no ROCm ainda não. Esperando o fix no Ollama 0.20.0+.</p>
<h2>LPDDR5X vs GDDR7: por que essa diferença<span class="hx:absolute hx:-mt-20" id="lpddr5x-vs-gddr7-por-que-essa-diferença"></span>
    <a href="#lpddr5x-vs-gddr7-por-que-essa-diferen%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A pergunta que vem é: por que a LPDDR5X é tão mais lenta?</p>
<p>GDDR7 é memória dedicada de GPU. Ela fica soldada na placa de vídeo, conectada por um barramento largo (384 ou 512 bits na RTX 5090) com clocks altos. O único trabalho dela é alimentar a GPU com dados. LPDDR5X é memória unificada que serve pra tudo: sistema operacional, aplicações, e GPU ao mesmo tempo. O barramento é mais estreito e compartilhado.</p>
<p>Na prática: GDDR7 entrega ~1.792 GB/s dedicados pra GPU. LPDDR5X entrega ~256 GB/s que ainda precisam ser divididos entre CPU e GPU. Inferência de LLM é basicamente &ldquo;leia todos os pesos do modelo da memória, multiplique pelo token atual, gere o próximo token, repita&rdquo;. Quem lê mais rápido, gera mais rápido. Não tem atalho.</p>
<p>A vantagem do Strix Halo não é velocidade. É capacidade. 96GB de VRAM num chip de 100W que custa uma fração de uma GPU profissional. A RTX 5090 é 7x mais rápida, mas trava em 32GB. Modelos que não cabem, não rodam.</p>
<h2>As alternativas: quem mais faz isso?<span class="hx:absolute hx:-mt-20" id="as-alternativas-quem-mais-faz-isso"></span>
    <a href="#as-alternativas-quem-mais-faz-isso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se 96GB não é suficiente ou se você quer mais velocidade, as opções são poucas.</p>
<p>O Framework Desktop usa o mesmo chip AI Max+ 395 com até 128GB de RAM. Mesma plataforma, mesma performance, mas com o diferencial de ser modular e reparável (é o Framework, afinal). Na prática é equivalente ao Minisforum em specs e preço.</p>
<p>Acima disso, a alternativa é o Mac Studio com M3 Ultra. O chip M3 Ultra suporta até 512GB de memória unificada, com bandwidth de ~819 GB/s (mais de 3x o Strix Halo). A Apple fabrica os chips de memória no package, então a latência e a bandwidth são superiores. Você poderia potencialmente alocar ~400GB como VRAM e rodar modelos que não cabem em lugar nenhum fora de servidores com GPUs profissionais.</p>
<p>O NVMe interno da Apple também é outro nível: ~7.4 GB/s de leitura sequencial no M3 Ultra, comparado com ~14 GB/s do Crucial T700 (PCIe 5.0). O T700 é mais rápido em throughput bruto, mas a latência do NVMe da Apple tende a ser menor em I/O aleatório por causa da integração com o SoC.</p>
<table>
  <thead>
      <tr>
          <th>Spec</th>
          <th>Minisforum MS-S1 Max</th>
          <th>Mac Studio M3 Ultra (max)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RAM máxima</td>
          <td>128 GB LPDDR5X</td>
          <td>512 GB unified</td>
      </tr>
      <tr>
          <td>VRAM alocável</td>
          <td>~96 GB</td>
          <td>~400 GB</td>
      </tr>
      <tr>
          <td>Memory bandwidth</td>
          <td>~256 GB/s</td>
          <td>~819 GB/s</td>
      </tr>
      <tr>
          <td>CPU</td>
          <td>Zen 5, 16C/32T</td>
          <td>Apple M3 Ultra, 32C</td>
      </tr>
      <tr>
          <td>GPU compute</td>
          <td>ROCm (gfx1151, experimental)</td>
          <td>Metal (mlx, mature)</td>
      </tr>
      <tr>
          <td>Consumo</td>
          <td>~100W</td>
          <td>~135W</td>
      </tr>
      <tr>
          <td>NVMe</td>
          <td>PCIe 5.0 (slot padrão)</td>
          <td>Custom Apple (~7.4 GB/s)</td>
      </tr>
      <tr>
          <td>Preço (EUA)</td>
          <td>~$1.500-2.000</td>
          <td>~$9.999 (512GB config)</td>
      </tr>
      <tr>
          <td>Preço estimado (Brasil)</td>
          <td>~R$ 12.000-15.000</td>
          <td>~R$ 110.000+ (importação)</td>
      </tr>
  </tbody>
</table>
<p>O preço no Brasil é o elefante na sala. A configuração máxima do Mac Studio custa $9.999 nos EUA. Com impostos de importação (~60% + ICMS estadual), passa dos R$ 110.000. O Minisforum com 128GB sai por R$ 12.000-15.000. A diferença de quase 8x no preço compra muita coisa.</p>
<p>Se você precisa de mais de 96GB de VRAM pra modelos realmente enormes (DeepSeek-V3 com 671B parâmetros cabe em ~400GB Q4, por exemplo), o Mac Studio com 512GB é a única opção consumer. A alternativa seria GPUs profissionais NVIDIA A6000 (48GB VRAM, ~$6.000 cada, e você precisaria de várias em NVLink). Pra tudo que cabe em 96GB, o Minisforum faz o trabalho por uma fração do custo.</p>
<h2>E projetos que prometem rodar LLMs grandes em GPUs pequenas?<span class="hx:absolute hx:-mt-20" id="e-projetos-que-prometem-rodar-llms-grandes-em-gpus-pequenas"></span>
    <a href="#e-projetos-que-prometem-rodar-llms-grandes-em-gpus-pequenas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Existe o conceito de &ldquo;layer offloading&rdquo; que projetos como llama.cpp já suportam. A ideia é: se o modelo não cabe inteiro na VRAM, mantém algumas camadas na GPU e o resto na RAM do sistema. A GPU processa as camadas que estão nela, transfere pro CPU processar o resto, e volta.</p>
<p>Na prática, não funciona bem. O gargalo é o PCIe: a velocidade de transferência entre RAM do sistema e VRAM da GPU é de ~32 GB/s (PCIe 5.0 x16). Cada token gerado precisa transferir dados ida e volta. O resultado é que você cai de 150 tok/s (tudo na VRAM) pra 2-8 tok/s (offload parcial). É lento demais pra uso interativo.</p>
<p>A VRAM é a limitação fundamental porque inferência de LLM é memory-bandwidth-bound, não compute-bound. A GPU tem compute sobrando. O que falta é capacidade de ler os pesos do modelo rápido o suficiente. Quando parte dos pesos está em RAM via PCIe, o pipeline inteiro espera pela transferência.</p>
<p>É por isso que memória unificada (como no Strix Halo ou no Apple Silicon) faz diferença. Não tem PCIe no meio. CPU e GPU acessam a mesma memória física. Os 256 GB/s do Strix Halo são lentos comparados com GDDR7, mas são 8x mais rápidos que ficar fazendo offload via PCIe.</p>
<h2>Avanços em otimização de LLMs (até 2026)<span class="hx:absolute hx:-mt-20" id="avanços-em-otimização-de-llms-até-2026"></span>
    <a href="#avan%c3%a7os-em-otimiza%c3%a7%c3%a3o-de-llms-at%c3%a9-2026" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra entender por que alguns modelos rodam tão melhor que outros no Strix Halo, precisa entender o que mudou no ecossistema nos últimos dois anos.</p>
<h3>Mixture of Experts (MoE)<span class="hx:absolute hx:-mt-20" id="mixture-of-experts-moe"></span>
    <a href="#mixture-of-experts-moe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você roda modelos locais, MoE é o avanço que mais importa. Um modelo MoE tem parâmetros totais altos (ex: 122B no qwen3.5:122b), mas ativa apenas uma fração deles por token (ex: ~4B). Os pesos inativos ficam na VRAM mas não são lidos a cada token, o que reduz drasticamente a bandwidth necessária.</p>
<p>Nos benchmarks do Strix Halo, modelos MoE rodam 3-10x mais rápido que modelos densos do mesmo tamanho. O qwen3.5:35b (MoE, ~4B ativos) roda a 43 tok/s enquanto o qwen2.5:72b (denso, 72B ativos) roda a 4.5 tok/s.</p>
<h3>DeepSeek e a otimização de treinamento<span class="hx:absolute hx:-mt-20" id="deepseek-e-a-otimização-de-treinamento"></span>
    <a href="#deepseek-e-a-otimiza%c3%a7%c3%a3o-de-treinamento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O DeepSeek V3 (dezembro 2024) mostrou que era possível treinar modelos de 671B parâmetros com custo uma ordem de magnitude menor que o previsto. Eles combinaram MoE com quantização FP8 durante o treinamento (não só na inferência), treinamento multi-estágio com curriculum learning, e várias otimizações de comunicação entre GPUs. O impacto: todo mundo copiou. Qwen, GLM, MiniMax, todos adotaram variações dessa técnica.</p>
<h3>Quantização: de FP16 pra Q4 sem perder muito<span class="hx:absolute hx:-mt-20" id="quantização-de-fp16-pra-q4-sem-perder-muito"></span>
    <a href="#quantiza%c3%a7%c3%a3o-de-fp16-pra-q4-sem-perder-muito" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quantização comprime os pesos do modelo de 16 bits (FP16) pra formatos menores: 8 bits (Q8), 4 bits (Q4), ou até 2 bits. Um modelo de 70B que ocuparia ~140GB em FP16 cabe em ~40GB em Q4_K_M. A perda de qualidade existe, mas nos formatos modernos (GGUF Q4_K_M, AWQ, EXL2) é pequena o suficiente pra uso prático.</p>
<p>O GGUF (formato do llama.cpp) se tornou o padrão pra inferência local. AWQ e GPTQ são alternativas com calibração mais sofisticada, mas o ecossistema convergiu pro GGUF porque ele funciona em CPU, CUDA e ROCm sem recompilação.</p>
<h3>Destilação: modelos menores que sabem mais<span class="hx:absolute hx:-mt-20" id="destilação-modelos-menores-que-sabem-mais"></span>
    <a href="#destila%c3%a7%c3%a3o-modelos-menores-que-sabem-mais" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Destilação é treinar um modelo pequeno usando as respostas de um modelo grande como professor. O Phi-4 da Microsoft (14B) foi treinado com destilação do GPT-4 e compete com modelos de 70B em vários benchmarks. O Qwen3 fez o mesmo: o qwen3:14b é surpreendentemente capaz pro tamanho.</p>
<h3>Flash Attention e KV Cache otimizado<span class="hx:absolute hx:-mt-20" id="flash-attention-e-kv-cache-otimizado"></span>
    <a href="#flash-attention-e-kv-cache-otimizado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Flash Attention (Tri Dao, 2022) mudou como attention é computada: em vez de materializar a matriz inteira de attention na memória, processa em blocos mantendo os dados no SRAM on-chip da GPU, reduzindo consumo de memória de O(n²) pra O(n). Sem isso, contextos de 128K+ tokens seriam impraticáveis. Já passou pelas versões 2 e 3, com otimizações pra FP8 e operações assíncronas no H100. O PagedAttention (vLLM, UC Berkeley) fez o mesmo pro KV cache durante serving: aplica conceitos de memória virtual ao cache, eliminando fragmentação e melhorando throughput 2-4x.</p>
<p>No Ollama, eu configurei <code>OLLAMA_FLASH_ATTENTION=1</code> e <code>OLLAMA_KV_CACHE_TYPE=q8_0</code> no servidor. O primeiro ativa flash attention, o segundo usa KV cache em 8-bit em vez de fp16, cortando a bandwidth necessária pela metade por token. São otimizações que custam zero em hardware e melhoram throughput mensurável.</p>
<h3>O que Qwen, Kimi, MiniMax e GLM estão fazendo<span class="hx:absolute hx:-mt-20" id="o-que-qwen-kimi-minimax-e-glm-estão-fazendo"></span>
    <a href="#o-que-qwen-kimi-minimax-e-glm-est%c3%a3o-fazendo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Qwen (Alibaba) tem sido consistentemente o melhor custo-benefício em modelos open source. O Qwen3:14b é denso e forte; o Qwen3.5:122b é MoE e roda surpreendentemente bem em 96GB. O GLM-4.7 (Zhipu AI) é notável por oferecer versões bf16 full precision que rodam inteiras em 96GB. O MiniMax experimentou com contextos longos (até 4M tokens). O Kimi (Moonshot AI) focou em context windows grandes com arquiteturas lineares.</p>
<h3>O que roda bem em 96GB de VRAM<span class="hx:absolute hx:-mt-20" id="o-que-roda-bem-em-96gb-de-vram"></span>
    <a href="#o-que-roda-bem-em-96gb-de-vram" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com 96GB no Strix Halo, os modelos que funcionam bem pra uso diário:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>tok/s</th>
          <th>Uso</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>qwen3.5:35b</td>
          <td>24 GB</td>
          <td>43.2</td>
          <td>Uso geral, excelente</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>52 GB</td>
          <td>29.5</td>
          <td>Código, MoE</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81 GB</td>
          <td>19.2</td>
          <td>Pesado mas usável</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>60 GB</td>
          <td>17.9</td>
          <td>Full precision</td>
      </tr>
      <tr>
          <td>qwen2.5-coder:32b</td>
          <td>20 GB</td>
          <td>10.2</td>
          <td>Código, denso</td>
      </tr>
      <tr>
          <td>deepseek-r1:32b</td>
          <td>20 GB</td>
          <td>7.4</td>
          <td>Raciocínio</td>
      </tr>
  </tbody>
</table>
<p>Modelos densos de 70B+ (deepseek-r1:70b, llama3.3:70b) ainda estão bloqueados pelo bug do ROCm que mencionei. Quando for corrigido, devem rodar a ~4-6 tok/s, usáveis pra batch mas não pra chat interativo.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu comprei o Minisforum pra rodar modelos que não cabem em GPU gamer nenhuma. Pra isso, funciona. Não é rápido. 19 tok/s num modelo de 122B não é a experiência que você tem com Claude ou ChatGPT. Mas é local, é privado, e roda na minha prateleira consumindo menos energia que uma lâmpada antiga.</p>
<p>Pra quem pergunta sobre o Mac Studio: se você tem orçamento, é a melhor máquina pra rodar LLMs locais. 512GB de memória unificada, 819 GB/s de bandwidth, ecossistema Metal/mlx maduro. Dá pra rodar DeepSeek-V3 inteiro em Q4. Mas no Brasil, com importação, passa dos R$ 110 mil. O Minisforum com 128GB por R$ 12-15 mil é a opção realista.</p>
<p>E pra quem acha que dá pra contornar a limitação de VRAM com offloading de camadas: não dá. PCIe é lento demais. O modelo precisa caber inteiro na VRAM pra inferência ser usável. É o motivo pelo qual GPUs gamers com 32GB de GDDR7 ultra-rápida continuam limitadas em tamanho de modelo, e por que a memória unificada do Strix Halo e do Apple Silicon mudou a equação.</p>
<p>No <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">próximo post</a> eu conto como migrei todo o home server pro Minisforum usando Claude Code, os problemas que encontrei, e como o openSUSE MicroOS se comporta como sistema operacional de servidor Docker.</p>
]]></content:encoded><category>hardware</category><category>modelos-locais</category><category>performance</category><category>reviews</category></item><item><title>Coisas que "Iniciantes" Ignoram sobre PCs</title><link>https://akitaonrails.github.io/2025/09/14/coisas-que-iniciantes-ignoram-sobre-pcs/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/14/coisas-que-iniciantes-ignoram-sobre-pcs/</guid><pubDate>Sun, 14 Sep 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;Faz tempo que eu queria escrever este artigo, mas sempre tive preguiça porque é o tipo de artigo onde algumas partes vão envelhecer logo. Isso porque vou mencionar alguns componentes de 2025, mas daqui 2 ou 5 anos já vai ter mudado, então leiam levando em consideração o ano que estiver lendo, caso esteja no futuro.&lt;/p&gt;
&lt;p&gt;Eu recebo dezenas de perguntas iguais quase todos os dias e em resumo todos se parecem com &lt;em&gt;&amp;ldquo;qual computador recomenda pra mim?&amp;rdquo;&lt;/em&gt; E não existe resposta pra isso. Eu não sei porque alguém pensa que teria uma resposta. Sempre cai no problema de &lt;a href="https://akitaonrails.com/2016/04/20/off-topic-se-voce-precisa-de-validacao-provavelmente-esta-errado/"target="_blank" rel="noopener"&gt;validação&lt;/a&gt; que eu tanto explico.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faz tempo que eu queria escrever este artigo, mas sempre tive preguiça porque é o tipo de artigo onde algumas partes vão envelhecer logo. Isso porque vou mencionar alguns componentes de 2025, mas daqui 2 ou 5 anos já vai ter mudado, então leiam levando em consideração o ano que estiver lendo, caso esteja no futuro.</p>
<p>Eu recebo dezenas de perguntas iguais quase todos os dias e em resumo todos se parecem com <em>&ldquo;qual computador recomenda pra mim?&rdquo;</em> E não existe resposta pra isso. Eu não sei porque alguém pensa que teria uma resposta. Sempre cai no problema de <a href="https://akitaonrails.com/2016/04/20/off-topic-se-voce-precisa-de-validacao-provavelmente-esta-errado/"target="_blank" rel="noopener">validação</a> que eu tanto explico.</p>
<p>Todo mundo tem que saber todo componente que tem num PC e quais as direrenças e como isso afeta ou não afeta pro seu caso de uso específico. No geral a regra é simples: se tiver dinheiro sobrando, compre o modelo mais caro de todos. Preço é quase sempre proporcional à qualidade do que comprou.</p>
<p>Se não tiver dinheiro sobrando e ainda assim precisa de um PC, compre exatamente o que puder gastar e tire o máximo disso.</p>
<h3>CPUs<span class="hx:absolute hx:-mt-20" id="cpus"></span>
    <a href="#cpus" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>CPUs basicamente você olha clock base e quantidade de cores. Não vai fugir muito disso. Sempre compre o mais recente, se puder.</p>
<p>Clock base é a velocidade que sua CPU vai performar &ldquo;normalmente&rdquo;. Boost clock é nas raras vezes que estiver processando algo pesado, como recompilar a kernel do Linux, ou algum jogo pesado.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913164642_screenshot-2025-09-13_16-46-15.png" alt="Threads"  loading="lazy" /></p>
<p>Não confundir cores com threads. Cores ou núcleos são componentes físicos na CPU que permitem executar mais de uma thread ao mesmo tempo. Em cima disso existe funcionalidades como Hyperthread, onde otimizamos usando o conceito que raramente todos os cores estão ocupados o tempo todo, então o OS enxerga um core físico como tendo capacidade pra até 2 threads.</p>
<p>Por isso na imagem acima, estamos vendo meu <strong>Ryzen 9 7850x3D</strong>, que tem 16 cores físicos, aparecendo como se tivesse 32 threads. Mas é impossível usar todas as 32 em 100% de capacidade.</p>
<p>Ter vários aplicativos abertos não significa que cada um vai estar ocupando uma core. O OS move processos entre cores conforme estratégia do <strong>Scheduler</strong>. Assista minha mini-série sobre <strong>Concorrência e Paralismo</strong>, <a href="/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3/">Parte 1</a> e <a href="2019/03/20/akitando-44-concorrencia-e-paralelismo-parte-2-entendendo-back-end-para-iniciantes-parte-4/">Parte 2</a>. Se for programador, é obrigatório entender isso.</p>
<p>Outras funcionalides que depende da CPU é suporte de <strong>PCI Express</strong> ou <strong>PCIe</strong>. Esse é o bus, ou barramento, de comunicação entre a CPU e diversos dispositivos de alta velocidade, como a GPU ou SSD/NVME - já vou falar deles. Vamos entender:</p>
<ul>
<li>
<p><strong>PCIe 4.0</strong> foi lançado no meio de <strong>2017</strong>, então evite comprar CPUs anteriores a isso, vai ser muito mais lento. Ela nasce no AMD Ryzen 3000 e chipset X570. Banda de aproximadamente <strong>2GB/s</strong> (~16 GT/s)</p>
</li>
<li>
<p><strong>PCIe 5.0</strong> foi lançado no meio de <strong>2019</strong>, portanto, se puder, prefira CPUs depois desse ano. Ela nasce no Intel Alder Lake e AMD Ryzen 7000. Banda de aproximadamente <strong>4GB/s</strong> (~32 GT/s), sim é o <strong>dobro</strong> de PCIe 4.0.</p>
</li>
</ul>
<p>Agora dois conceitos importantes. Primeiro <strong>PCI Lanes</strong>:</p>
<ul>
<li>Um &ldquo;lane&rdquo; (tipo uma &ldquo;pista&rdquo;) é um par de sinais diferenciais TX/RX, tecnicamente falando.</li>
<li>CPU/chipsets (chipset significa &ldquo;conjunto de chips&rdquo;) expõe um número <strong>fixo</strong> de lanes (varia de 20 a 28, mais que isso gera gargalo no chipset).</li>
<li>PCIe não varia a quantidade de lanes e sim a banda de cada lane.</li>
</ul>
<p>Exemplos:</p>
<ul>
<li>AMD Ryzen 7000 (AM5): 24 PCIe 5.0 lanes do CPU (16 pro GPU, 4 pra NVMe, 4 pro chipset).</li>
<li>Intel Alder Lake/Raptor Lake: 16 PCIe 5.0 lanes pra GPU + 4 PCIe 4.0 pra storage.</li>
</ul>
<p>PCI Lanes são importantes pra GPU e pra NVME. &ldquo;SSDs&rdquo; são outro barramento: SATA, depois falo disso. Se o plano é comprar uma GPU moderna e poderosa, tipo uma AMD Radeon 9070 XT ou NVIDIA RTX 5090, vai precisar de PCIe 5.0 com o máximo de lanes e banda disponível pra elas (16 lanes). Isso afeta também a escolha de placa-mãe.</p>
<p>Muita gente pensa em comprar CPUs de servidor, como Intel Xeon ou AMD EPIC. Cuidado. A idéia é tentar achar no eBay um que já tenha sido descartado mas ainda funciona. A &ldquo;vantagem&rdquo; é que CPUs de servidor costumam ter muito mais cores do que CPUs nível consumidor.</p>
<p>Um Intel Xeon costuma ter <strong>60 cores/120 threads</strong> ou um AMD EPIC costuma ter <strong>96 cores/192 threads</strong>, mesmo modelos velhos como um Xeon Platinum/Gold (era Skylake de 2017) tem de 20 a 28 cores. Um AMD EPIC 7001 de 2017 tem 32 cores. Se conseguir achar usado, barato num eBay, não vale a pena?</p>
<p>Na prática: <strong>NÃO</strong>.</p>
<p>Só pra usos muito específicos. Por exemplo, se for montar um servidor de armazenamento, um NAS, com ZFS, que tira vantagem de múltiplos cores, precisa de <strong>MUITA RAM</strong>, mas a banda pros discos não precisa ser absurdamente grande. Vamos ver as desvantagens:</p>
<ul>
<li>esses CPUs velhos que tem preços considerados &ldquo;acessíveis&rdquo; vão ser de 2017 pra trás, ou seja, <strong>PCIe 3.0</strong>, banda mais lenta por lane.</li>
<li>base clock vai ser baixo. Num CPU médio, fica acima de 2Ghz, num antigo vai ser abaixo de 2Ghz. Um com CPU hoje tem base clock de 4Ghz.</li>
<li>alto TDP (Thermal Design Power). Na prática, vai ser mais lento e ainda assim vai consumir mais energia e dissipar MUITO calor. Ou seja, são altamente ineficientes hoje.</li>
<li>não basta ter a CPU barata, elas precisam de <strong>placas-mãe</strong> específicas com sockets LGA 3647, SP3, SP5. São caras e não são formato ATX, então não cabem em qualquer gabinete.</li>
<li>RAM precisa ser ECC RDIMM/LRDIMM. ECC significa Error Correction Code. São RAMs mais caras que garantem que não vai existir bits corrompidos, por isso são obrigatórios em servidores. Nível consumidor não precisa disso. Nas raras vezes que RAM corromper, vai dar uma tela azul e você só reboota. Por isso são mais baratas também.</li>
<li>Não tem várias funcionalidades comuns em CPUs pra consumidores. Não tem iGPU (GPU integrada), não dá pra fazer nenhum tipo de overclocking, tem suporte mais fraco a AVX 512 (vai ter problemas com video, áudio modernos).</li>
</ul>
<p>De novo, só vale a pena se você sabe exatamente todos os detalhes e tem uma aplicação MUITO específica em mente que tire vantagem desse tipo de CPU e não vai sentir falta do que ele não tem. Se você é amador, nem tente.</p>
<p>Na prática, eu dou preferência a CPUs AMD. A série Ryzen tem seguidamente sido bem eficiente. A nomenclatura de nomes de CPUs é coisa que marketeiro adora criar confusão, porque quanto mais confusão, mais fácil enganar consumidor. Isso vai ser um tema constante neste artigo, prestem atenção.</p>
<p>Depende de cada nova arquitetura, mas em geral:</p>
<p><strong>Nomenclaturas AMD</strong></p>
<p>Formato: Ryzen [3/5/7/9] [####] [sufixo]</p>
<ul>
<li>
<p>Tier (3 / 5 / 7 / 9): performance class.</p>
<ul>
<li>Ryzen 3: entry (4–6 cores).</li>
<li>Ryzen 5: midrange (6–8 cores).</li>
<li>Ryzen 7: high-end (8–12 cores).</li>
<li>Ryzen 9: enthusiast (12–16 cores desktop; até 64 cores no Threadripper/EPYC).</li>
</ul>
</li>
<li>
<p>Primeiro Dígito: geração/arquitetura.</p>
<ul>
<li>1 = 1st gen (2017, Zen),</li>
<li>5 = 5th gen (Zen 3),</li>
<li>7 = 7000 series (Zen 4),</li>
<li>9 = 9000 series (Zen 5).</li>
</ul>
</li>
<li>
<p>Dígitos do Meio: modelo dentro da geração. Maior = mais rápido (ex., 7600 vs 7900).</p>
</li>
<li>
<p>Sufixos:</p>
<ul>
<li>X = higher TDP, higher clocks.</li>
<li>G = integrated GPU.</li>
<li>X3D = 3D V-Cache (bom pra games).</li>
<li>HS / U / HX (mobile) = laptop power targets (U = ultra-low, HS = balanced, HX = high-performance).</li>
</ul>
</li>
</ul>
<p><strong>Nomenclaturas Intel</strong></p>
<ul>
<li>
<p>Formato Antigo (pre-2024): Core i3/i5/i7/i9-#### [sufixo]</p>
<ul>
<li>i3 / i5 / i7 / i9 = entrada → alto nível.</li>
</ul>
</li>
<li>
<p>Dígitos:</p>
<ul>
<li>Primeiro = geração (e.g. i7-8700 = 8th gen).</li>
<li>Resto = SKU dentro da geração (8700 vs 8700K).</li>
</ul>
</li>
<li>
<p>Sufixos:</p>
<ul>
<li>K = desbloqueado (overclockable).</li>
<li>F = sem iGPU.</li>
<li>KF = desbloqueado + sem iGPU.</li>
<li>T = baixa energia.</li>
<li>H / HK = laptop alta-performance.</li>
<li>U / Y = ultra-baixa energia.</li>
</ul>
</li>
<li>
<p>Novo Formato (2024+, Meteor Lake / Arrow Lake):</p>
<ul>
<li>Core Ultra 5 / 7 / 9 (drops “i”).</li>
<li>Mesmos tiers: Ultra 5 = midrange, Ultra 9 = top.</li>
<li>Adiciona sufixos “H/U” pra classe de energia de laptop.</li>
</ul>
</li>
</ul>
<p>Eu tenho uma &ldquo;antiga&rdquo; (ainda muito performática) Ryzen 9 7850X3D que é Zen 4, 16 cores/32 threads, 3D V-Cache que é bom pra jogos. O equivalente mais novo é a <strong>9850X3D</strong>, que é arquitetura mais nova Zen 5 (está pra sair Zen 6).</p>
<p>No mundo notebook, mobile/console/handhelds saiu a linha <strong>Ryzen AI MAX/MAX+</strong> que tem CPU+GPU+NPU integradas na onda de acelerar tarefas de redes neurais, rodar <strong>pequenos</strong> modelos de IA. A Ryzen AI Max+ 395 é Zen 5, integrada com GPU Radeon, até 40 CUs (mais ou menos equivalente da AMD a Nvidia Cuda Cores). A vantagem é ter acesso a mais RAM/VRAM pra carregar modelos mais pesados, mas não vai ser rápido porque CPU desktop ainda é melhor, obviamente. Em notebook você sempre vai estar limitado por solução térmica.</p>
<p>O maior problema de consoles/notebooks/handhelds é que não dá pra fornecer tanta energia (TGP) quanto desktop porque não tem pra onde dissipar tanto calor. Por isso notebook <strong>sempre</strong> vai ser mais limitado do que desktop. Isso dito, se puder pagar, recomendo a Ryzen AI Max, parecem bons chips mesmo.</p>
<p>Se tem dúvidas sobre sua CPU e estiver num Linux, dá pra checar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ lscpu
</span></span><span class="line"><span class="cl">Architecture:                x86_64
</span></span><span class="line"><span class="cl">  CPU op-mode<span class="o">(</span>s<span class="o">)</span>:            32-bit, 64-bit
</span></span><span class="line"><span class="cl">  Address sizes:             <span class="m">48</span> bits physical, <span class="m">48</span> bits virtual
</span></span><span class="line"><span class="cl">  Byte Order:                Little Endian
</span></span><span class="line"><span class="cl">CPU<span class="o">(</span>s<span class="o">)</span>:                      <span class="m">32</span>
</span></span><span class="line"><span class="cl">  On-line CPU<span class="o">(</span>s<span class="o">)</span> list:       0-31
</span></span><span class="line"><span class="cl">Vendor ID:                   AuthenticAMD
</span></span><span class="line"><span class="cl">  Model name:                AMD Ryzen <span class="m">9</span> 7950X3D 16-Core Processor
</span></span><span class="line"><span class="cl">    CPU family:              <span class="m">25</span>
</span></span><span class="line"><span class="cl">    Model:                   <span class="m">97</span>
</span></span><span class="line"><span class="cl">    Thread<span class="o">(</span>s<span class="o">)</span> per core:      <span class="m">2</span>
</span></span><span class="line"><span class="cl">    Core<span class="o">(</span>s<span class="o">)</span> per socket:      <span class="m">16</span>
</span></span><span class="line"><span class="cl">    Socket<span class="o">(</span>s<span class="o">)</span>:               <span class="m">1</span>
</span></span><span class="line"><span class="cl">    Stepping:                <span class="m">2</span>
</span></span><span class="line"><span class="cl">    Frequency boost:         enabled
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De curiosidade, já expliquei isso <a href="https://akitaonrails.com/2021/05/13/akitando-97-so-precisamos-de-640-kb-de-memoria-16-bits-ate-64-bits/"target="_blank" rel="noopener">neste video</a> mas, apesar da arquitetura ser 64-bits, note como endereços de memória são <strong>48 bits</strong>, porque nem existe forma em hardware de conseguir ter tanta RAM pra precisar de 64-bits. Então é mais eficiente economizar transistor e usar um endereço menor.</p>
<p>Também expliquei as nuances de se ter muitos cores e como Cache L3 influencia seu funcionamento no artigo de <a href="https://akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt/"target="_blank" rel="noopener">GPU Passthrough</a></p>
<h3>Placas-mãe<span class="hx:absolute hx:-mt-20" id="placas-mãe"></span>
    <a href="#placas-m%c3%a3e" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse é outro buraco de coelho desgraçado. Eu mesmo tenho dificuldades de escolher. Cada modelo de CPU tem diversas opções de placa-mãe. Você tem que escolher uma que é compatível com sua CPU (que tem socket que encaixa, na prática) e, de preferência, quando sair a próxima versão de CPU, seja possível fazer upgrade.</p>
<p>Isso depende do ano em que está comprando. Se estiver no começo de um ciclo de arquitetura de CPU, ainda deve vir novas CPUs da mesma arquitetura por alguns anos. Se estiver no fim de um ciclo, arrisca comprar uma CPU e placa-mãe que vai ficar obsoleta no ano que vem. Quando quiser dar upgrade, vai ter que trocar os dois. Isso não tem guia, você tem que procurar o <strong>roadmap</strong> da Intel e AMD pra checar.</p>
<p>Por exemplo, ano que vem a Intel vai lançar a nova arquitetura <a href="https://www.tomshardware.com/pc-components/cpus/intel-confirms-arrow-lake-refresh-set-for-2026-nova-lake-later-that-year-company-admits-there-are-holes-to-fill-on-the-desktop-front-says-it-is-confident-in-the-roadmap"target="_blank" rel="noopener">Nova Lake</a> pra substituir o Arrow Lake. Possivelmente não vai funcionar em placas-mãe atuais, não sei ainda.</p>
<p>Notebooks você não tem esse problema porque não existe upgrade de CPU. Quando sair um novo, se troca o notebook inteiro mesmo.</p>
<p>Placas-mãe você começa escolhendo o <strong>FORMATO</strong>. O mais comum pra gabinetes de torre, aqueles que se vê no Instagram de gamers com RGB pra lá e pra cá costumam ser o tamanho grande, que é <strong>ATX</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913172430_Motherboard-Form-Factor-Size-Comparison.webp" alt="moba form-factor"  loading="lazy" /></p>
<p>Mas existem outros formatos. Tem um maior que é &ldquo;Extended ATX&rdquo;, tem um menor que é &ldquo;Micro-ATX&rdquo;, tem menor ainda que é &ldquo;micro-ITX&rdquo;. Eu nunca vi mas parece que ainda tem uma maior de todas que é &ldquo;XL-ATX&rdquo;. Mini-PCs que vem tudo integrado tem placas-mãe proprietárias de cada fabricante, como Minisforum ou Beelink, que vou falar na última seção mais pra baixo.</p>
<p>Quanto menor a placa-mãe, menos funcionalidades vai ter, em resumo costuma ser: menos slots de PCIe (acaba sobrando só uma pra GPU, normalmente). Menos slots de RAM.</p>
<p>Daí vai depender de que gabinete vai escolher, que espaço tem em cima ou embaixo da sua mesa. Na prática, se não entende nada disso, compre ATX. Vai dar menos dor de cabeça. Boas marcas de gabinete costumam ser Lian Li ou Fractal, mas tem dezenas por aí, tem que prestar muita atenção na montagem, encaixes, funcionalidades, etc. Tamanho de radiador de AIO, coolers, tamanho da placa-gráfica, etc.</p>
<p><strong>Mini-ITX</strong> é só pra quem sabe o que está fazendo. Eis um exemplo da Fractal:</p>
<p><a href="https://www.pcgamer.com/best-mini-itx-case/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913173009_QMNn4zsBtHPm5DRCMibZcE.jpg" alt="fractal mini-itx case"  loading="lazy" /></a>
Tem três problemas principais: não cabe qualquer placa-gráfica. não cabe qualquer fonte (power supply/PSU), precisa de muito mais ventilação/solução térmica (porque não tem espaço pra dissipar o calor dentro).</p>
<p>Alguém vai chegar e comentar <em>&ldquo;ah, só colocar resfriamento com água&rdquo;</em>. Não é tão simples assim.</p>
<p>Resfriamento a água (AIO) precisa de manutenção, usar bons líquidos de resfriamento (não necessariamente água, tem dissipadores melhores hoje). E depende do modelo, porque tem tanque de líquido, tem canos por onde esse líquido passa, portanto <strong>OCUPA MUITO MAIS ESPAÇO</strong>. Em um gabinete ATX funciona, em ITX raramente.</p>
<p>Além disso, veja o tamanho de uma RTX 5090:</p>
<p><a href="https://www.forbes.com/sites/antonyleather/2025/01/07/nvidias-rtx-5090-is-so-small-its-compatible-with-tiny-pc-cases/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913173748_960x0.jpg" alt="RTX 5090"  loading="lazy" /></a>
Essa é da <strong>MSI</strong>. Mas tem outros integradores como Gigabyte, Gamerock, Zotac, a Founders Edition da própria NVIDIA e outros. Cada um vai ter um formato e tamanho <strong>diferentes</strong>, precisa pesquisar exatamente o modelo, de qual fabricante, com qual gabinete que vai caber. Mesmo em gabinetes ATX, nem sempre cabe na horizontal. Além de ocupar o espaço de 2 slots (série 40) ou 3 slots (série 50) na vertical.</p>
<p>Além disso tem que pensar: você quer uma workstation de trabalho pra coisas como edição de video? Precisa ver se sua placa-mãe já vem com pelo menos Ethernet 2.5 Gbps, idealmente 10 Gbps. A maioria dos populares ainda está com o obsoleto 1 Gbps.</p>
<p>Pra maioria das pessoas, que só tem plano de internet abaixo de 1Gbps, não tem problema, nem vai saturar a banda. Mas quem trabalha com video precisa ter streaming de video PESADO, estou falando acima de <strong>100 GB por hora de video</strong>, múltiplos videos na timeline, tocando ao mesmo tempo. Menos em 2.5 Gbps fica difícil editar em tempo real de playback.</p>
<p>Se acabar comprando uma placa-mãe velha com 1 Gbps de Ethernet, vai precisa comprar uma placa de rede PCIe separada. Só que se estiver usando um formato mini-ITX, <strong>não vai ter espaço</strong> ou nem vai ter slot extra pra plugar isso. Por isso tem que planejar o uso com antecedência.</p>
<p>A minha é velha, é uma <strong>X670E AORUS XTREME</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913174531_D_NQ_NP_858736-MLU74688986203_022024-O.webp" alt="X670E"  loading="lazy" /></p>
<p>É formato ATX e tem quase todas as funcionalidades que preciso, incluindo porta Ethernet 2.5Gbps. Mas como eu quis colocar um roteador 10Gbps e também coloquei upgrade de 10Gbps no meu NAS <strong>Synology DS1821+</strong>, resolvi adicionar uma placa PCIe de Ethernet 10Gbps. Como é formato ATX, mesmo com minha RTX 4090 ocupando espaço de 2 slots na vertical, ainda sobra espaço embaixo.</p>
<p>Tem dúvida de qual é sua placa-mãe? Se estiver num Linux tem este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ sudo dmidecode -t baseboard
</span></span><span class="line"><span class="cl"><span class="c1"># dmidecode 3.6</span>
</span></span><span class="line"><span class="cl">Getting SMBIOS data from sysfs.
</span></span><span class="line"><span class="cl">SMBIOS 3.7.0 present.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Handle 0x0002, DMI <span class="nb">type</span> 2, <span class="m">15</span> bytes
</span></span><span class="line"><span class="cl">Base Board Information
</span></span><span class="line"><span class="cl"> Manufacturer: Gigabyte Technology Co., Ltd.
</span></span><span class="line"><span class="cl"> Product Name: X670E AORUS XTREME
</span></span><span class="line"><span class="cl"> Version: x.x
</span></span><span class="line"><span class="cl"> Serial Number: Default string
</span></span><span class="line"><span class="cl"> Asset Tag: Default string
</span></span><span class="line"><span class="cl"> Features:
</span></span><span class="line"><span class="cl">  Board is a hosting board
</span></span><span class="line"><span class="cl">  Board is replaceable
</span></span><span class="line"><span class="cl"> Location In Chassis: Default string
</span></span><span class="line"><span class="cl"> Chassis Handle: 0x0003
</span></span><span class="line"><span class="cl"> Type: Motherboard
</span></span><span class="line"><span class="cl"> Contained Object Handles: <span class="m">0</span>
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Acho que <code>sudo dmidecode -t system</code> faz a mesma coisa.</p>
<p>Tem as pequenas coisas também: Windows 11 costuma exigir a existência de um chip <strong>TPU 2.0</strong> que também costuma ser opcional na maioria dos modelos de placa-mãe. É bom ter, ou compre uma que já vem, ou compre separado e encaixe.</p>
<p>Alguns modelos não suportam overclocking. Se você for um entusiasta de tuning, tem que checar quais modelos suportam. Coisas como <strong>Secure Boot</strong>, é bom ter. Isso qualquer bom modelo tem, mas se estiver querendo escolher uma velha, pra economizar, cheque se tem.</p>
<p>O ideal é ter portas como Thunderbolt 4.0, USB 4.0, mas se tiver pelo menos USB 3 Gen 2, já ajuda. Nem toda USB 3 é igual!! Tem gerações diferentes! Só porque tem porta azul não quer dizer que é veloz. Vou falar disso depois.</p>
<p>Ideal ter Wi-fi embutido também, pra evitar ter que comprar uma placa ou USB separado. Mas cuidado, a maioria ainda oferece só o obsoleto Wi-Fi 5. Já estamos em <strong>Wi-fi 7</strong>, um Samsung Galaxy moderno já suporta, por exemplo. Mas como sempre, se está acostuma às baixas velocidades dos planos de internet que tem por aí, de fato, até Wi-fi 5 funciona. Mas se quiser transferir grandes arquivos entre seus computadores e celulares, vai querer um Wi-fi 7 mais moderno, no mínimo Wi-fi 6.</p>
<p>Também não adianta nada ter Wi-Fi 7 no seu PC mas continuar usando roteador que a Vivo, Claro, te deram. Essas porcarias que eles dão pra você são totalmente obsoletos, Wi-fi 5 no máximo. Eu mesmo uso um <strong>TP-Link BE9000 Wi-fi 7</strong> e desligo o roteador Wi-Fi da Vivo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914031547_TP-Link-BE9300-Archer-BE550-Wi-Fi-7-Router-771x1024.webp" alt="TP-Link"  loading="lazy" /></p>
<h3>RAM DDR 5<span class="hx:absolute hx:-mt-20" id="ram-ddr-5"></span>
    <a href="#ram-ddr-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra coisa importante pra ver na sua CPU e sua placa-mãe é qual tipo de RAM eles suportam. Como já falei antes, se quiser uma workstation de ponta, vai querer uma que suporte RAM ECC. Na prática, pra maioria de nós, não precisa de ECC. Só é mais caro, pras raras vezes que um raio cósmico conseguir flipar um bit e crashear seu sistema (é muito, muito raro).</p>
<p>Mais importante é escolher DDR 5. Mas tanto DDR 5 ou DDR 4 tem variações, só pra continuar complicando sua vida. Memória também tem <strong>clock</strong>!</p>
<p>Acho que a DDR 4 o mais veloz é 3200Mhz, o mais comum ainda é 2666Mhz. PCs mais velhos que 2014 vão ser DDR 3!! Cuidado!</p>
<p>Já DDR 5 começa em 4800Mhz e vai até uns 6000Mhz.</p>
<p>Se estiver num Linux dá pra saber as informações da sua assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># dmidecode 3.6</span>
</span></span><span class="line"><span class="cl">Getting SMBIOS data from sysfs.
</span></span><span class="line"><span class="cl">SMBIOS 3.7.0 present.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Handle 0x0011, DMI <span class="nb">type</span> 16, <span class="m">23</span> bytes
</span></span><span class="line"><span class="cl">Physical Memory Array
</span></span><span class="line"><span class="cl"> Location: System Board Or Motherboard
</span></span><span class="line"><span class="cl"> Use: System Memory
</span></span><span class="line"><span class="cl"> Error Correction Type: None
</span></span><span class="line"><span class="cl"> Maximum Capacity: <span class="m">128</span> GB
</span></span><span class="line"><span class="cl"> Error Information Handle: 0x0010
</span></span><span class="line"><span class="cl"> Number Of Devices: <span class="m">4</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Handle 0x0016, DMI <span class="nb">type</span> 17, <span class="m">100</span> bytes
</span></span><span class="line"><span class="cl">Memory Device
</span></span><span class="line"><span class="cl"> Array Handle: 0x0011
</span></span><span class="line"><span class="cl"> Error Information Handle: 0x0015
</span></span><span class="line"><span class="cl"> Total Width: <span class="m">64</span> bits
</span></span><span class="line"><span class="cl"> Data Width: <span class="m">64</span> bits
</span></span><span class="line"><span class="cl"> Size: <span class="m">48</span> GB
</span></span><span class="line"><span class="cl"> Form Factor: DIMM
</span></span><span class="line"><span class="cl"> Set: None
</span></span><span class="line"><span class="cl"> Locator: DIMM <span class="m">1</span>
</span></span><span class="line"><span class="cl"> Bank Locator: P0 CHANNEL A
</span></span><span class="line"><span class="cl"> Type: DDR5
</span></span><span class="line"><span class="cl"> Type Detail: Synchronous Unbuffered <span class="o">(</span>Unregistered<span class="o">)</span>
</span></span><span class="line"><span class="cl"> Speed: <span class="m">6000</span> MT/s
</span></span><span class="line"><span class="cl"> Manufacturer: CORSAIR
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai listar informações de cada pente de memória conectado ao barramento da placa-mãe. Com esse output (que está cortado pra não ficar longo demais) dá pra concluir que eu tenho 2 pentes de memória, cada um com <strong>48 GB</strong> e <strong>6000 MT/s</strong> de velocidade.</p>
<p>Eles são <strong>Corsair Vengeance</strong>, não-ECC, dando um total de <strong>96 GB</strong> de RAM DDR 5. Eu poderia colocar 128 GB ou mais, mas até hoje nunca consegui saturar esse tanto de memória, então vai aguentar assim por muito tempo ainda.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914022843_corsair-vengeance-rgb-ddr5-32gb-2x16gb-ddr5-6000-cl36-36-36-76-135v-amd-expo-intel-xmp-grey.webp" alt="vengeance"  loading="lazy" /></p>
<p>Tem até RGB 🤣.</p>
<p>Pra maioria das pessoas eu diria que 16 GB é o novo mínimo e 32 GB é o &ldquo;sweet-spot&rdquo;. Pra programadores que precisa subir muitos containers Docker pesados, só aí iria pra 64 GB.</p>
<h2>&ldquo;Transfers&rdquo; != &ldquo;bytes&rdquo;<span class="hx:absolute hx:-mt-20" id="transfers--bytes"></span>
    <a href="#transfers--bytes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um detalhe. Quando falamos em DDR 4 3200 ou DDR 5 6000, não é 6000 <strong>MB/s</strong> e sim 6000 <strong>MT/s</strong>.</p>
<p>Eu odeio essa unidade, mas isso são &ldquo;<strong>Mega Transfers</strong> por segundo&rdquo;. Quando falei de PCI Lanes, também é uma pegadinha. PCIe 5.0 diz ter 32 GT/s (32 <strong>Giga TRANSFER</strong>/segundo). Vamos traduzir.</p>
<blockquote>
  <p>&ldquo;Transfers&rdquo; se refere a quantos símbolos crús (que não equivale a 1 byte) dá pra transmitir por segundo. No caso de PCIe lanes, eles são seriais, então 1 transfer é 1 símbolo pelo fio físico. Mas 1 transfer não é 1 byte!</p>

</blockquote>
<p>No caso de PCIe Lanes:</p>
<ul>
<li>PCIe 3.0 - 8 GT/s aproximadamente 985 MB/s por lane</li>
<li>PCIe 4.0 - 16 GT/s aproximadamente 1.9 GB/s por lane</li>
<li>PCIe 5.0 - 32 GT/s aproximadamente 3.9 GB/s por lane</li>
</ul>
<p>Pra começar DDR significa <strong>Double-Data-Rate</strong>, onde dados são transferidos nas duas bordas de cada clock. Portanto:</p>
<ul>
<li>6000 MT/s é 3000 Mhz</li>
<li>DDR 5 6000 é 3000 Mhz</li>
<li>DDR 4 3200 é 1600 Mhz</li>
</ul>
<p>Por que diabos se fala nessa unidade idiota de &ldquo;mega-transfers&rdquo; ou &ldquo;giga-transfer&rdquo;? Única e exclusivamente pra <strong>marketing</strong>, porque é o número maior. E consumidor é burro, ele escolhe o número maior.</p>
<h3>&ldquo;GIBI-bytes&rdquo; != &ldquo;GIGA-bytes&rdquo;<span class="hx:absolute hx:-mt-20" id="gibi-bytes--giga-bytes"></span>
    <a href="#gibi-bytes--giga-bytes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O marketing não acaba em &ldquo;transfers&rdquo;. Já notou que você compra um SSD de <strong>1 TERABYTE</strong> mas quando pluga no Windows ele te fala que tem só uns &ldquo;<strong>931 GB</strong>&rdquo;?? Onde foram parar os outros 69 GB??</p>
<p>No mundo normal de computação, nós medimos dados na base 2. Portanto 2^10 = 1024. Se a unidade básica for &ldquo;byte&rdquo; então 1024 bytes é 1 KILO-byte. 2^20 é 1 MEGA-byte e 2^30 é 1 GIGA-byte.</p>
<p>Qualquer programador iniciante entende isso. Mas a população em geral não, então todo fabricante de HDs, SSD, NVME e qualquer tipo de &ldquo;storage&rdquo; resolveu mudar tudo e dizer o seguinte:</p>
<ul>
<li>1 KB = 1000 bytes</li>
<li>1 MB = 1000^2 = 1.000.000 bytes</li>
<li>1 GB = 1000^3 = 1.000.000.000 bytes</li>
</ul>
<p>E a unidade correta, que seria KILOBYTE ou MEGABYTE eles renomearam pra essa coisa <strong>HORRENDA</strong> que é &ldquo;KIBI-BYTE&rdquo; ou &ldquo;MIBI-BYTE&rdquo; ou &ldquo;GIBI-BYTE&rdquo;. E eu só tenho a dizer:</p>
<blockquote>
  <p>&ldquo;KIBI&rdquo; de cú é rola!!!</p>

</blockquote>
<p>Mas o marketing vai te dizer:</p>
<ul>
<li>1 KiB = 1024 bytes</li>
<li>1 MiB =1024^2 = 1.048.576 bytes</li>
<li>1 GiB = 1024^3 = 1.073.741.824 bytes</li>
</ul>
<p>Por isso que você compra um HD de 1 TB e o Windows (corretamente) diz 931 GiB. Pelo menos RAM e velocidade de rede tendem a ser as unidades binárias reais, então quando você compra um pente de &ldquo;16 GB&rdquo;, na linguagem do marketing você comprou &ldquo;16 GiB&rdquo; mesmo. E quando falamos rede de 1 Gbps estamos falando de 1 GiB/s. Só armazenamento que é essa putaria.</p>
<h3>HDs vs SSDs vs NVME<span class="hx:absolute hx:-mt-20" id="hds-vs-ssds-vs-nvme"></span>
    <a href="#hds-vs-ssds-vs-nvme" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Falando em armazenamento, precisa entender o seguinte:</p>
<ul>
<li>HDs mecânicos ainda existem, tem bom custo benefício, são confiáveis e razoavelmente velozes. Os melhores HDs são pau a pau com um SSD médio, porque no final ambos vão saturar o mesmo barramento, que é SATA.</li>
<li>Se quiser realmente alta-performance, você vai usar NVMEs, que se ligam diretamente ao barramento PCIe 4.0 ou 5.0 da sua placa-mãe. Mas são bem mais caros.</li>
</ul>
<p>Vamos lá:</p>
<ul>
<li>HDs mecânico é uma tecnologia que começou lá nos anos 50, quando poucos kilobytes precisavam de um rack inteiro do tamanho de um servidor inteiro de hoje. Como os Phoenix que você pode ver no canal do Usagi Electric.</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=IsgRKPPOMZI"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913183146_screenshot-2025-09-13_18-31-35.png" alt="Phoenix"  loading="lazy" /></a></p>
<ul>
<li>Os HDs mais velozes de hoje são de <strong>7200 RPM</strong> (rotações dos discos por minuto) e velocidades que variam de 150 a 200 MB/s, com latência de 1 a 2 ms. A vantagem é que eles oferecem grandes volumes a preços atrativos (eu tenho vários de 20 TB no meu NAS). Mas a velocidade realmente não é mais moderno.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913184838_st20000nt001-seagate-hd-interno-sata-20tb-7200-rpm-ironwolf-pro-7dcb4490-jxvnesfrs7.webp" alt="HD Ironwolf"  loading="lazy" /></p>
<ul>
<li>SSDs nasceram no fim dos anos 2000, Solid-State Drives porque não tem partes móveis, são como chips de RAM persistentes. Elas se conectam ao mesmo tipo de barramento que HDs mecânicos: <strong>SATA III</strong> (Serial ATA) e oferecem velocidade na faixa de 500 a 550 MB/s sequencial. O dobro de HDs mecânicos. E tem a vantagem de facilmente poder substituir um HD mecânico no mesmo barramento e ser compatível. Mas é isso: o teto é 550 MB/s.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913184704_20-147-798-V01.webp" alt="SSD Samsung"  loading="lazy" /></p>
<ul>
<li>
<p>NVMEs SSD muitos confundem porque também podem ser chamados de &ldquo;SSDs&rdquo;, afinal são &ldquo;solid-state&rdquo; mas de tecnologias diferentes. O correto seria dizer &ldquo;SSD SATA&rdquo; e &ldquo;SSD NVME&rdquo;. NVME significa <strong>Non-Volatile Memory Express</strong>, que pra maioria das pessoas não quer dizer nada mesmo.</p>
</li>
<li>
<p>NVMEs se ligam ao barramento PCIe, portanto o teto é a velocidade dos lanes de PCIe como falei antes. Em PCIe 3.0 o teto vai ser 3.5 GB/s (que já é 6x mais veloz que o mais veloz SSD SATA III). Em PCIe 5.0 o máximo teórico é 13 a 14 GB/s (muito difícil atingir tudo isso, a maioria fica na faixa dos 5GB/s mesmo). A latência cai de &ldquo;ms&rdquo; pra &ldquo;µs&rdquo; (milissegundo vs MICROssegundo).</p>
</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913184737_Samsung_980_PRO_PCIe_4.0_NVMe_SSD_1TB-top_PNr%c2%b00915.webp" alt="NVME"  loading="lazy" /></p>
<p>Ou seja SATA III está preso na faixa das centenas de MEGAbytes por segundo, já NVME sobe pra quase dezena de GIGAbyte por segundo. É uma ordem de grandeza de <strong>70x</strong>.</p>
<p>Vamos entender mais na prática:</p>
<ul>
<li><strong>PCIe 3.0 x4</strong> (2015-2020) ~3.0 a ~3.5 GB/s (Samsung 970 EVO Plus, WD Black SN750)</li>
<li><strong>PCIe 4.0 x4</strong> (2020-2023) ~6 a 7.5 GB/s (Samsung 980 PRO, WD SN850X, Crucial P5 Plus)</li>
<li><strong>PCIe 5.0 x4</strong> (a partir de 2023) ~10 a 12GB/s em <em>pico</em>, não média (Crucial T700, Corsair MP700, Gigabyte Aorus Gen5 10000)</li>
</ul>
<p>Entenda que essas velocidades são pra leitura <strong>sequencial</strong> de arquivos grandes e não pra leituras ou escritas <strong>aleatórias</strong> de arquivos pequenos. A velocidade diminui DRAMATICAMENTE em uso do dia a dia de verdade com arquivos pequenos.</p>
<p>Mesmo em 2025 ainda não é comum NVMEs PCI 5.0, todo mundo ainda usa <strong>NVMEs PCIe 4.0</strong>, portanto a velocidade máxima que se encontra hoje é na faixa dos 7 GB/s, mais comum perto dos 5 GB/s, que são os Samsung 980 PRO / 990 PRO, WD Black SN850X, Crucial P5 Plus / P3 Plus ou similares.</p>
<p>Mesmo com uma CPU e placa-mãe que suporte PCIe 5.0, você ainda vai plugar NVME PCIe 4.0 (não tem problema, porque são retro-compatíveis). Mas não espere conseguir essa faixa acima de 10 GB/s sem pagar MUITO CARO. Simplesmente não compensa.</p>
<p>Muita gente ainda usa SSD: <strong>fique longe dos genéricos</strong> sem marca e baratos demais da AliExpress. É super comum ser só um adaptador de microSD card. E também não gosto de Kingston, essa marca já me deu muita dor de cabeça. Fique com marcas confiáveis como Samsung, Sandisk, Crucial, Western Digital e coisas assim.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913185340_8tb-ssd-scam1-f849be8d05152279ce7ebb06c7b4a84c.webp" alt="SSD SCAM"  loading="lazy" /></p>
<p>Eu mesmo comprei um desses só pra demonstrar esse <strong>GOLPE</strong>, pelo Mercado Livre e postei stories no meu Instagram na época. É fácil de identificar: <strong>são excessivamente baratos</strong>. Acho que depois disso o MercadoLivre tentou ser mais esperto pra não vender isso, mas no AliExpress sempre tem.</p>
<p>Tem mais: NVMEs tem <strong>diferentes tamanhos</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913184910_m2-ssd-sizes.webp" alt="NVME sizes"  loading="lazy" /></p>
<p>NVMEs se conectam no tal <strong>slot M2</strong> na placa-mãe. É um slot que dá acesso direto ao barramento PCIe, normalmente x4 (4 lanes).</p>
<p>O modelo mais comum é o <strong>2280</strong>. É o que a maioria dos PCs suporta. Mas dependendo do notebook, ou se for um handheld como o primeiro <strong>Asus Rog Ally</strong> e acho que também o Steam Deck, vai precisar de tamanhos menores como os modelos <strong>2242</strong> ou <strong>2230</strong>.</p>
<p>É um trade-off de tamanho físico por capacidade. <strong>2242</strong> se não me engano, tem capacidade máxima de 2 TB. Eu gosto de 4 TB ou 8 TB então precisa ser pelo menos um <strong>2280</strong>.</p>
<p>Tem mais problemas: marcas &ldquo;baratas&rdquo; são baratas porque tiram alguns componentes como <strong>DRAM</strong>. Pra funcionar rápido, precisa existir uma memória RAM pra servir e buffer e cache. Além disso, modelos baratos tem controladores porcaria. E isso diminui não só a velocidade, como a vida útil dos chips.</p>
<p>Não use modelos baratos, eles são proporcionalmente piores e vão ou corromper seus dados ou morrer cedo. Não existe almoço grátis. Novamente, fique com marcas conhecidas como Samsung. Samsung sempre é confiável.</p>
<h3>MicroSD Cards<span class="hx:absolute hx:-mt-20" id="microsd-cards"></span>
    <a href="#microsd-cards" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mesma coisa: <strong>todo microSD sem marca ou barato é golpe</strong>. Ele não tem a capacidade que anuncia, quando bate num teto começa a sobrescrever os dados do começo. Não confie.</p>
<p>Só compro Samsung. Mas não é tão simples assim. Lembra os caras do <strong>marketing</strong> dos TIBI-BYTES?? Pois é, eles atacam pra valer em microSD. Existem 3 diferenças: classes (capacidade), velocidade do barramento (BUS), notas de velocidade (Speed), confiabilidade! Sim! Vamos entender:</p>
<ol>
<li><strong>Classes de Capacidade</strong></li>
</ol>
<ul>
<li><strong>SDSC</strong> (Standard Capacity): up to 2 GB (<strong>obsoleto</strong>).</li>
<li><strong>SDHC</strong> (High Capacity): 2–32 GB.</li>
<li><strong>SDXC</strong> (Extended Capacity): 32 GB–2 TB.</li>
<li><strong>SDUC</strong> (Ultra Capacity): up to 128 TB (<strong>não comum ainda</strong>).</li>
</ul>
<ol start="2">
<li><strong>Bus Interface</strong></li>
</ol>
<ul>
<li><strong>UHS-I</strong>: up to 104 MB/s.</li>
<li><strong>UHS-II</strong>: up to 312 MB/s (linha extra de pinos).</li>
<li><strong>UHS-III</strong>: up to 624 MB/s (raro).</li>
<li><strong>SD Express</strong> (PCIe/NVMe): teórico ~985 MB/s+ (ainda nicho).</li>
</ul>
<ol start="3">
<li><strong>Speed Ratings</strong></li>
</ol>
<ul>
<li>Speed Class (<strong>C</strong>): C2 (2 MB/s), C4, C6, C10.</li>
<li>UHS Speed Class (<strong>U</strong>): U1 (10 MB/s), U3 (30 MB/s).</li>
<li>Video Speed Class (<strong>V</strong>): V6 → V90 (6–90 MB/s, sustentado).</li>
</ul>
<ol start="4">
<li><strong>Application Class (A)</strong></li>
</ol>
<ul>
<li><strong>A1</strong>: ≥1500 IOPS read, ≥500 IOPS write, 10 MB/s sustentado.</li>
<li><strong>A2</strong>: ≥4000 IOPS read, ≥2000 IOPS write, 10 MB/s sustentado.</li>
</ul>
<ol>
<li><strong>Níveis de Confiança</strong></li>
</ol>
<ul>
<li>Consumer grade: pra câmera e fones (o mais comum).</li>
<li>High Endurance: pra escrita contínua (dashcams, câmeras de segurança).</li>
<li>Industrial grade: suporta range maior de temperatura, desgasta menos, muito mais caro.</li>
</ul>
<p>Na prática, procure sempre <strong>SDXC</strong>, <strong>UHS-II</strong> (ou UHS-I que é mais comum), <strong>V90</strong> (mais comum é abaixo de V30 mesmo). É isso que significa esse malditos códigos escritos num microSD:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913190622_microsd.jpeg" alt="samsung microsd"  loading="lazy" /></p>
<p>Esse da foto da Samsung tem capacidade de 512GB, classe SDXC, barramento UHS-I (até 90 MB/s), velocidade U3 (30MB/s) (é esse ícone de um 3 dentro do U na imagem). Feito pra funcionar bem em câmeras ou fones ou handhelds da vida. Menos que isso é <strong>ruim</strong>.</p>
<p>Outro exemplo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913190951_88555428f12eca2dab1110cac7de0a7079-micro-sd.rsquare.w400.webp" alt="comparison microsd"  loading="lazy" /></p>
<p>Agora você consegue ler. O Samsung EVO Select de 512GB, SDXC, U3, A2, V30 e o da direita SanDisk Ultra 128GB SDXC, U1, A1, C10. Agora você sabe o que procurar e o que isso significa.</p>
<p>Mais importante é Speed Rating, muitos são classe C como C10 acima. Mas se o uso for pra gravar video em boa qualidade, sem engasgar, procure classe V como V90!</p>
<h3>USB vs Thunderbolt<span class="hx:absolute hx:-mt-20" id="usb-vs-thunderbolt"></span>
    <a href="#usb-vs-thunderbolt" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra piorar a confusão de marketagem ainda tem USB e Thunderbolt. Vamos entender: primeiro de tudo ter conector USB-C não significa nada. USB-C é padrão do conector físico do cabo, só isso. Os dados que trafegam nela usam protocolos, e existem vários.</p>
<p>No caso de plug USB-A, que é o retângulo que tem lado certo pra encaixar e é o mais comum na parte de trás de um PC, alguns vem com cores diferentes:</p>
<ul>
<li>Preto → USB 2.0 (480 Mb/s).</li>
<li>Azul → USB 3.0 / 3.1 Gen 1 / 3.2 Gen 1 (5 Gb/s).</li>
<li>Verde-azulado / Turquesa → USB 3.1 Gen 2 / 3.2 Gen 2 (10 Gb/s).</li>
<li>Vermelho → porta de alta energia (quase sempre USB 3.2 Gen 2, algumas vezes “always-on charging”).</li>
<li>Amarelo / Laranja → Também porta “charging”, depende do fabricante.</li>
<li>Branco → Legado / BIOS flashback (quase sempre USB 2.0).</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914024147_11166Image10_zoom.jpg" alt="usb ports"  loading="lazy" /></p>
<p><strong>Confusão de Nomenclatura</strong></p>
<ul>
<li>USB 3.0 (2010) → depois renomeado pra USB 3.1 Gen 1 → daí pra USB 3.2 Gen 1 (5 Gb/s).</li>
<li>USB 3.1 (2013) → agora USB 3.2 Gen 2 (10 Gb/s).</li>
<li>USB 3.2 (2017) → USB 3.2 Gen 2×2 (20 Gb/s).</li>
<li>USB4 (2019) → 20–40 Gb/s, mesmo conector físico de USB-C, Thunderbolt-like.</li>
<li>USB4 v2 (2022) → até 80–120 Gb/s, ainda sendo espalhado durante 2025.</li>
</ul>
<p><strong>Portar mais populares em placas-mãe (2025)</strong></p>
<ul>
<li>USB 2.0 (preto/branco) → ainda presente pra mouse e teclado, BIOS flashback.</li>
<li>USB 3.2 Gen 1 (azul) → 5 Gb/s, muito comum.</li>
<li>USB 3.2 Gen 2 (verde-azulado/vermelho) → 10 Gb/s, comum em placas de médio ou alto custo.</li>
<li>USB 3.2 Gen 2×2 (Type-C, algumas vezes vermelho/verde-azulado) → 20 Gb/s, placas mais caras.</li>
<li>USB4 / Thunderbolt 4 (Type-C) → 40 Gb/s, placas premium, workstations.</li>
</ul>
<p>Fora isso, não é tão comum em PCs mas é bem comum em Macs, são as portas <strong>Thunderbolt</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914024235_7e05-mobo2.jpg" alt="thunderbolt"  loading="lazy" /></p>
<ul>
<li>
<p><strong>Thunderbolt 1</strong> (2011)</p>
<ul>
<li>Baseado em PCIe 2.0 ×4 + DisplayPort 1.1a.</li>
<li>Conector: Mini DisplayPort.</li>
<li>Banda: 10 Gb/s (bi-directional).</li>
<li>Maior parte é Apple + algumas placas Intel.</li>
</ul>
</li>
<li>
<p><strong>Thunderbolt 2</strong> (2013)</p>
<ul>
<li>Conector: Mini DisplayPort.</li>
<li>Canais agregados → 20 Gb/s.</li>
<li>Ainda PCIe 2.0, DisplayPort 1.2.</li>
<li>Usado na era do MacBook Pro / Mac Pro.</li>
</ul>
</li>
<li>
<p><strong>Thunderbolt 3</strong> (2015)</p>
<ul>
<li>Conector: USB-C (primeiro Thunderbolt a usar).</li>
<li>Banda: 40 Gb/s (PCIe 3.0 ×4 + DP 1.2).</li>
<li>Suporte completo a USB 3.1/3.2.</li>
<li>Largamente adotado em laptops, docks, eGPUs.</li>
<li>Requer certificação; nem toda porta USB-C = Thunderbolt 3.</li>
</ul>
</li>
<li>
<p><strong>Thunderbolt 4</strong> (2020)</p>
<ul>
<li>Conector: USB-C.</li>
<li>Ainda máximo de 40 Gb/s (como TB3), mas menos requerimentos mínimos:
<ul>
<li>Mínima banda de PCIe 3.0 ×4 garantida.</li>
<li>Suporte pra monitors duplos 4K ou um 8K.</li>
<li>Carregamento mandatório + suporte a &ldquo;wake from sleep&rdquo;.</li>
</ul>
</li>
<li>Mais consistente entre diversos dispositivos.</li>
<li>Suporte nativo no Intel Tiger Lake+, AMD Ryzen 7000+ (alguns).</li>
</ul>
</li>
<li>
<p><strong>Thunderbolt 5</strong> (anúncio em 2023, entregue em 2024/25)</p>
<ul>
<li>Conector: USB-C.</li>
<li>Banda: 80 Gb/s bi-direcional, 120 Gb/s modo assimétrico (pra monitores).</li>
<li>Usa tunelamento de PCIe 4.0.</li>
<li>DisplayPort 2.1, suporta dual 8K ou triplo 4K.</li>
<li>Ideal pra external GPUs, docks de alta-banda, monitores de workstation.</li>
</ul>
</li>
<li>
<p>Como Thunderbolt se relaciona com USB / USB4</p>
<ul>
<li>Thunderbolt 3 → formou a base pro USB4.</li>
<li>USB4 = padrão aberto, 20–40 Gb/s, compartilha muito do protocolo Thunderbolt.</li>
<li>USB4 v2 (especificação de 2022, saindo em 2025) = até 80–120 Gb/s, efetivamente equivalente a Thunderbolt 5.</li>
<li>Muitas novas portas são “compatíveis com USB4 / Thunderbolt,” mas nem todos garantem todas as funcionalidades de TB a menos que seja certificado.</li>
</ul>
</li>
</ul>
<p>Conector Thunderbolt, a partir do 3 se não me engano é igual USB-C mas tem o símbolo de &ldquo;raio&rdquo; nele pra diferenciar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914024546_screen_shot_2022-11-30_at_2.02.19_pm.webp" alt="thunderbolt connector"  loading="lazy" /></p>
<p>Como podemos ver, não basta ter uma porta azul ou porta USB-C. Tem que ler a especificação no manual da placa-mãe que escolher. No geral, a maioria das placas-mãe modernas tem pelo menos USB 3.2 Gen 2 ou até USB 4. Pra Thunderbolt o buraco é mais embaixo, a maioria não tem.</p>
<p>Thunderbolt é importante se quiser eGPU ou storage externo pra coisas como edição de video (DAS), como CalDigit TS5 Plus, Sonnet Echo 13 ou então ligar múltiplos monitores 4K.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914024434_Best-Thunderbolt-USB-C-Docks-Docking-Stations-MacBook-Mac.webp" alt="thunderbolt storage"  loading="lazy" /></p>
<h3>GPUs<span class="hx:absolute hx:-mt-20" id="gpus"></span>
    <a href="#gpus" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Primeiro: GPU é o que chamamos o chip principal dentro de uma &ldquo;placa-gráfica&rdquo; que é o produto completo que tem a GPU, VRAM, cooler, placa-mãe e outros componentes.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913232051_gpu.jpeg" alt="GPU"  loading="lazy" /></p>
<p>Todo mundo chama tudo só de &ldquo;GPU&rdquo; então meio que tanto faz, mas via de regra você está procurando uma &ldquo;placa-gráfica&rdquo;. Vamos resumir a história recente, caso esteja interessado em usados primeiro.</p>
<p><strong>AMD (Radeon)</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913233552_RX7900XTX-O24G-B_0.jpg" alt="rx 7900 xtx"  loading="lazy" /></p>
<ul>
<li>Série:
<ul>
<li>RX 5000 (Navi, 2019) – lançamento de PCIe 4.0, midrange (RX 5700 XT).</li>
<li>RX 6000 (RDNA2, 2020) – ray tracing, Infinity Cache, competitivo com RTX 3000.</li>
<li>RX 7000 (RDNA3, 2022) – design de &ldquo;chiplet&rdquo;, alta performance por /watt, RX 7900 XTX vs RTX 4080.</li>
</ul>
</li>
<li>Nomenclatura: RX [Series][Model]. quanto maior o número = mais novo, mais rápido. XT = variante mais rápida.</li>
<li>Vantagens: bom custo-benefício, mais VRAM que NVIDIA na mesma classe, drivers abertos no Linux.</li>
<li>Desvantagens: ray tracing mais lento que NVIDIA, mais fraco pra AI/compute.</li>
</ul>
<p><strong>Intel (Arc)</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913233641_placa-de-video-intel-arc-a770-phantom-gaming-asrock-16gb-gddr6-rgb-polychrome-sync-90-ga4kzz-00uanf_1696943713_gg.webp" alt="Arc a770"  loading="lazy" /></p>
<ul>
<li>Série:
<ul>
<li>Arc A300/A500/A700 (Alchemist, 2022) – primeira geração.</li>
<li>A380 = entrada, A770 = midrange pra um pouco mais alto.</li>
</ul>
</li>
<li>Futuro: Battlemage (2025), depois Celestial.</li>
<li>Vantagens: competitivo em preço, boa engine de mídia (encode/decode AV1), drivers melhorando.</li>
<li>Desvantagens: drivers imaturos(especialmente em Linux), opções high-end limitadas, sem CUDA/AI.</li>
</ul>
<p><strong>NVIDIA (GeForce / RTX)</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913233742_251313_1-17459188343668033.jpeg" alt="RTX 4090"  loading="lazy" /></p>
<ul>
<li>Série (recente):
<ul>
<li>GTX 10 (Pascal, 2016) – enorme salto de eficiência (GTX 1080).</li>
<li>RTX 20 (Turing, 2018) – introduziu ray tracing (RTX 2080 Ti).</li>
<li>RTX 30 (Ampere, 2020) – grande salto, popular (RTX 3080, 3090).</li>
<li>RTX 40 (Ada Lovelace, 2022) – melhor em ray tracing, DLSS 3 frame generation, RTX 4090 é o topo de linha.</li>
</ul>
</li>
<li>Naming: GTX/RTX + series + model (higher = better). Ti/SUPER = faster variant.</li>
<li>Strengths: best ray tracing, DLSS, CUDA/AI support, most mature ecosystem.</li>
<li>Weaknesses: high cost, VRAM sometimes lower than AMD at same tier.</li>
</ul>
<p>Eu sou enviesado pra opinar sobre gráficos porque sou time verde faz anos. Eu tive a GTX 1080, RTX 3090, RTX 4060 e RTX 4090. Esses últimos dois ainda uso hoje. A 4060 pra games é mais que suficiente até 1440p na maioria e até 4K e em muitos menos pesados.</p>
<p>Editei a maioria dos videos do meu canal com GTX 1080 (enquanto eu filmava em 1080p) e depois migrei pra RTX 3090 quando passei a gravar em 4K com minha câmera <strong>Sony A7S III</strong>. Ela seria mais que suficiente ainda hoje, mas resolvi migrar pra RTX 4090.</p>
<p>A RTX 4090 uso pra render 3D (Blender) ou IA (LLMs com Ollama, LM Studio, Aider, etc). Não me vejo precisando de uma 5090 ainda (não tem mais VRAM que a minha, que é a única coisa que seria relevante pra LLMs).</p>
<p>Pro dia a dia, gerenciador de janelas, navegar na web e tudo mais, eu dou output pela iGPU (GPU integrada) que já vem na Ryzen 9. A RTX fica como GPU secundária pra render offload.</p>
<p>Vamos ver as GPUs mais modernas:</p>
<p><strong>NVIDIA — GeForce RTX 50 (Blackwell, 2025)</strong></p>
<ul>
<li>Key: PCIe 5.0, GDDR7, DLSS 4 / Multi-Frame Generation, DisplayPort 2.1b (UHBR20).</li>
<li>RTX 5090: 32 GB GDDR7, 512-bit bus, arquitetura Blackwell.</li>
<li>RTX 5080: Blackwell + GDDR7 + DLSS 4.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913234020_gv-n5090aorus-m-ice-32gd9.jpg" alt="RTX 5090"  loading="lazy" /></p>
<p><strong>AMD — Radeon RX 9000 (RDNA 4, 2025)</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913234057_81OO5-B1DVL.jpg" alt="RX 9070 XT"  loading="lazy" /></p>
<ul>
<li>Foco: mid/high-range, RT melhorado + novos aceleradores de IA.</li>
</ul>
<p><strong>Intel — Arc B-series (Battlemage, 2024/2025)</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250913234137_placa-de-video-asrock-intel-arc-b570-challenger-oc-10gb-gddr6-xess-ray-tracing-b570-cl-10go_222549.jpg" alt="B570"  loading="lazy" /></p>
<ul>
<li>B580/B570); foco no valor, AV1 ainda forte, drivers melhorando.</li>
</ul>
<p><strong>Pontos Importantes</strong></p>
<ul>
<li>VRAM eu não recomendo menos que 12GB. Se for fazer coisas mais profissionais como editar videos e criação de conteúdo, pelo menos 16GB. Dá pra rodar LLMs menores com menos VRAM, mas pra rodar as maiores, no mínimo 24GB (como minha 4090) ou 32GB (5090).</li>
<li>Se estiver procurando a performance pra games de 4K com 240hz, garanta que tem <strong>Display Port 2.1</strong>. (RTX série 50 ou RX 9000).</li>
<li>Ecossistema: depende da tecnologia precisa. CUDA só tem em NVIDIA. DLSS 4 (super resolution, frame generation) só tem em NVIDIA. O equivalente em AMD é FSR/HYPR-RX. Intel não tem nada disso.</li>
<li>Performance também depende da banda, então PCIe 4 vs PCIe 5 como já expliquei antes. RTX série 50 tira vantagem de PCIe 5.</li>
</ul>
<p>Não adianta comprar uma RTX série 50 e colocar com uma placa-mãe e CPU que só suporta PCIe 4.0. Vai funcionar, mas não vai tirar proveito de uma placa gráfica tão cara. É desperdício, melhor ficar numa RTX 2080 ou RTX 3080, por exemplo.</p>
<p>Custo-benefício e suporte em Linux é melhor em AMD. Se tiver dinheiro, RX série 9000, ou pelo menos série 7000. Procure pela versão XT/XTX que são as de mais performance. Mas não vai ter suporte a CUDA. O equivalente de API na AMD é o <strong>ROCm</strong> mas nem todo software suporta ele ainda.</p>
<p>Em games, interessa muito <strong>DLSS</strong> (Deep Learning Super Sampling) que são modelos de IA especializados em tratar frames de games.</p>
<ul>
<li>
<p>RTX 20-series (Turing, 2018)</p>
<ul>
<li>Suporta DLSS 1.0 (primeira geração, neural net por jogo - que vem nas atualizações de drivers, qualidade de imagem mais ou menos).</li>
<li>Depois ganhou DLSS 2 via atualização de driver.</li>
</ul>
</li>
<li>
<p>RTX 30-series (Ampere, 2020)</p>
<ul>
<li>Suporta DLSS 2 completo (reconstrução de imagem mais nítida, estabilidade temporal).</li>
<li>Suporta DLSS 3 Frame Generation (frames interpolados por IA), mas somente na série 40 tem Optical Flow Accelerator v2, então série 30 não consegue frame generation.</li>
</ul>
</li>
<li>
<p>RTX 40-series (Ada Lovelace, 2022)</p>
<ul>
<li>Suporta DLSS 2.</li>
<li>Suporta DLSS 3 Frame Generation (insere frames de IA → maior FPShigher FPS, pequeno custo em latência).</li>
<li>Adiciona DLSS 3.5 Ray Reconstruction (IA substitui denoiser → melhor qualidade de ray tracing).</li>
</ul>
</li>
<li>
<p>RTX 50-series (Blackwell, 2025)</p>
<ul>
<li>Suporta DLSS 4 (Multi-Frame Generation).</li>
<li>Melhor interpolação de frame com redução de artefatos de latência comparado a DLSS 3.</li>
<li>Constrói em cima de DLSS 3.5 (Ray Reconstruction ainda incluso).</li>
</ul>
</li>
<li>
<p>DLSS 1 → Prova de conceito, qualidade inconsistente, obsoleto.</p>
</li>
<li>
<p>DLSS 2 → Per-pixel temporal upscaling, imagem nítida, artefatos mínimos, muito usado ainda hoje.</p>
</li>
<li>
<p>DLSS 3 → Frame Generation → interpola frames inteiros, boost de FPS em cenários limitados pela GPU; requer série 40 pra cima.</p>
</li>
<li>
<p>DLSS 3.5 → Ray Reconstruction → melhora luzes/reflexões de ray tracing, mais detalhes, menos &ldquo;noise&rdquo; (barulho). Funciona em todas as RTX.</p>
</li>
<li>
<p>DLSS 4 → Multi-Frame Generation (RTX 50+) → movimentos mais suaves, redução de latência de artefatos.</p>
</li>
</ul>
<p>AMD tem o equivalente a DLSS na forma de FSR, <strong>FidelityFX Super Resolution</strong>. Em Resumo:</p>
<ul>
<li>
<p>FSR 1.0 (2021)</p>
<ul>
<li>Spatial upscaler (per-frame, sem motion vectors).</li>
<li>Suporte a várias GPUs (funciona em NVIDIA, Intel, consoles).</li>
<li>Vantagem: simples, universal.</li>
<li>Desvantagem: imagem soft, shimmering.</li>
</ul>
</li>
<li>
<p>FSR 2.0 (2022)</p>
<ul>
<li>Temporal upscaler (usa motion vectors + histórico).</li>
<li>Mais perto de DLSS 2 em qualidade.</li>
<li>Suportado em AMD + NVIDIA + Intel GPUs.</li>
<li>Mais pesado, mas leve comparado com DLSS.</li>
</ul>
</li>
<li>
<p>FSR 3.0 (2023)</p>
<ul>
<li>Adiciona Frame Generation (interpolação sem IA).</li>
<li>Roda em AMD e NVIDIA.</li>
<li>Vantagem: mais FPS mesmo em GPUs mais velhas.</li>
<li>Desvantagem: mais latência de input do que Optical Flow + Reflex combo de DLSS 3.</li>
</ul>
</li>
<li>
<p>FSR 3.1 (2024)</p>
<ul>
<li>Separa Frame Gen de Upscaling → pode usar FG com outros upscalers (DLSS/XeSS).</li>
<li>Lida melhor com artefatos (ghosting, shimmering).</li>
</ul>
</li>
</ul>
<p>Intel tem XeSS mas é bem mais pobre que FSR (que parece que é open source, por isso também suporta outras GPUs) e DLSS (que é proprietário e só funciona em NVIDIA RTX).</p>
<p>NVIDIA série GTX pra trás não suporta nada disso e são considerados obsoletos já. Se quiser algo mais antigo pra economizar não iria mais pra trás do que RTX série 20, como uma 2060 ou 2080.</p>
<p>GPU de Notebook é outra coisa que a marketagem fode. Por exemplo, você vai encontrar notebooks que dizem ter &ldquo;RTX 4060&rdquo;, porém tem menos CUDA cores, tem menos clock (limitação térmica, não tem como resfriar dentro de um notebook), TGP que é o total de energia consumida que vai ser faixa de 35W a no máximo 140W, mas sempre fica mais baixo. Em desktop uma 4060 iria de 115 a 160W. Vai ter menos VRAM (8GB ou menos). Performance geral vai ser de 30% a 50% mais lento que modelo desktop de mesmo nome.</p>
<p>Então uma <strong>RTX 4060 mobile</strong> vai ser mais equivalente a uma <strong>RTX 3050 Ti</strong> ou <strong>RTX 3060</strong>.</p>
<p>Vamos comparar alguns modelos da NVIDIA (as duas que eu tenho e a mais nova de todas):</p>
<p><strong>RTX 4060 (Ada Lovelace, 2023)</strong></p>
<ul>
<li>Architecture: AD107</li>
<li>CUDA cores: 3,072</li>
<li>Base / Boost: 1.83 / 2.46 GHz</li>
<li>VRAM: 8 GB GDDR6, 128-bit bus</li>
<li>Bandwidth: 272 GB/s (with 24 MB L2 cache)</li>
<li>TGP: 115 W</li>
</ul>
<p><strong>RTX 4090 (Ada Lovelace, 2022)</strong></p>
<ul>
<li>Architecture: AD102</li>
<li>CUDA cores: 16,384</li>
<li>Base / Boost: 2.23 / 2.52 GHz</li>
<li>VRAM: 24 GB GDDR6X, 384-bit bus</li>
<li>Bandwidth: 1,008 GB/s</li>
<li>TGP: 450 W</li>
</ul>
<p><strong>RTX 5090 (Blackwell, 2025)</strong></p>
<ul>
<li>Architecture: GB202 (Blackwell)</li>
<li>CUDA cores: ~24,576</li>
<li>Base / Boost: ~2.5+ GHz</li>
<li>VRAM: 32 GB GDDR7, 512-bit bus</li>
<li>Bandwidth: ~1.5–1.6 TB/s</li>
<li>TGP: ~450–500 W (varies)</li>
</ul>
<p>Notem como TGP varia drasticamente de um modelo 60 pra 90, de 115W pra absurdos 450 a 500W! E realmente consome isso se começa a puxar modelos LLM ou jogos AAA pesados. Também note a diferença gritante de CUDA cores. De novo: a 4060 roda meus jogos da Steam (muitos AAA) super de boa, seja tem 1440p max settings ou 4K medium settings, 60 fps a 100fps. Graças a DLSS, dá pra ter boa resolução e bom FPS.</p>
<p>Modelos 90 é se realmente for renderizar coisa pesada, editar video pesado ou rodar muita IA.</p>
<h3>Power Supply (PSU)<span class="hx:absolute hx:-mt-20" id="power-supply-psu"></span>
    <a href="#power-supply-psu" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Toda hora estou falando sobre TGP, TDP, solução térmica e tem que falar de PSUs, as &ldquo;fontes&rdquo;. Qualidade aqui faz diferença, como sempre, nunca compre sem marca, marca genérica ou coisa barata do AliExpress. Um PSU porcaria pode diminuir vida útil dos seus componentes ou destruir mesmo seu PC.</p>
<p>Marcas boas são: Seasonic, Corsair, Cooler Master, Be Quiet, EVGA. Queremos certificações como 80 PLUS Gold, Platinum, Titanium (e cuidado, marcas xing-ling vem com essas etiquetas).</p>
<p>Sobre a capacidade, o que realmente vai fazer diferença na conta é o consumo e CPU e GPU, veja as especificações dos modelos que escolher, mas em linhas gerais:</p>
<ul>
<li>
<p>setup velho, com uma i5 ou Ryzen 5, GPU média como uma RTX 4060 vai ser faixa de consumo de 200W a 300W (varia entre &ldquo;idle&rdquo;, que é parado e pico, onde dá turbo boost ou está usando todos os cores, recompilando kernel, rodando LLMs, etc). Considerando outros componentes no PC é melhor pelo menos uma PSU de <strong>550 a 650W</strong>.</p>
</li>
<li>
<p>setup médio, com uma RTX 4070 ou RX 7800 vai consumir faixa de 300W a 450W então escolha uma PSU de <strong>750 a 850W</strong>.</p>
</li>
<li>
<p>setup de ponta, com uma RTX 4080 pra cima, puxando muito processamento de IA. Vai consumir faixa de 500 a 800W. Então é bom já considerar uma PSU <strong>1000W</strong> ou <strong>1600W</strong> se colocar 2 GPUs em paralelo.</p>
</li>
</ul>
<p>A que eu uso no meu PC é uma <strong>Corsair RM1000x Shift</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914002607_fonte-atx-1000w-corsair-rm1000x-shift.jpg" alt="Corsair RX1000x"  loading="lazy" /></p>
<p>Lembram que falei sobre tamanhos de placa-mãe e gabinetes? ATX, mini-ITX? A PSU RM1000x que falei agora não cabe nelas. Pra isso precisa de uma fonte low profile ou <strong>SFF</strong> (Small Form Factor).</p>
<p>Digamos que queira colocar uma RTX 4070 num gabinete mini-ITX. Vai precisar de uma SFX ou SFX-L, pelo menos de 650 ou 750W. Exemplos são:</p>
<ul>
<li>Corsair SF750 / SF600 Platinum (SFX)</li>
<li>SilverStone SX700 / SX750 (SFX)</li>
<li>Seasonic Focus SGX-650 / SGX-750 (SFX)</li>
<li>Lian Li SP series (SFX / SFX-L)</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914002949_FOCUS-SGX-750-PSU-box-scaled.webp" alt="Seasonic Focus"  loading="lazy" /></p>
<p><strong>CUIDADO</strong></p>
<p>Os cabos que vem <strong>junto</strong> com a PSU são específicas pra PSUs da <strong>mesma marca</strong>.</p>
<blockquote>
  <p><strong>Não misture cabos diferentes com marcas diferentes!!!!</strong></p>

</blockquote>
<p>Embora os encaixes pareçam o mesmo, elas podem danificar seus componentes!! Não existe um padrão de cabos pra PSU!!! Não misture seus cabos Corsair com PSUs Seasonic ou, pior, não compre cabos genéricos xing-ling no AliExpress e misture com sua PSU!! <strong>Você foi avisado!!</strong></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914003312_which_psu_cables_go_where.width-1500.format-webp.webp" alt="cabos"  loading="lazy" /></p>
<h3>Coolers<span class="hx:absolute hx:-mt-20" id="coolers"></span>
    <a href="#coolers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se estiver gastando bastante pra pegar os topos de linha de CPU, GPU, placa-mãe, PSU, não pode economizar em coolers.</p>
<p>Sabia que a <strong>maior parte</strong> da energia que seu PC consome é dissipado em forma de calor? Virtualmente tudo. O PC que falei que consome de 750 a 850W? Vai tudo pro ar em forma de calor. Somente uma <strong>pequena fração</strong> é convertida em outras formas como luz (LEDs), som (speakers), energia cinética (ventoinhas).</p>
<p>O princípio é baseado na <strong>lei de conservação de energia</strong>, que diz que energia não pode ser criada ou destruída, só transformada. O processo de computação por si não consome energia de uma forma que previne sua conversão em calor. O processo de processamento de informação envolve movimento de eléctrons através de circuitos, que geram calor por causa da resistência elétrica.</p>
<p>Portanto, quanto mais potente forem os componentes, mais energia consome e mais calor precisa ser dissipado rápido!</p>
<p>De cara, se dinheiro não for problema, escolha o melhor. O melhor é sempre <strong>Noctua</strong>. Eu nunca tive problema, são ultra silenciosos, confiáveis e nunca deixam na mão. E prefira <strong>air cooler</strong>, que tem a menor quantidade de peças móveis e que podem dar defeito ou precisar de manutenção. Liga uma vez e deixa pra sempre.</p>
<p>Se for uma CPU mais pesada como a minha Ryzen 9 7850X3D ou a mais nova 9850X3D tem que ser a <strong>NH-D15 G2</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914003832_232317-800-auto.webp" alt="NH-D15 G2"  loading="lazy" /></p>
<p>Eu tenho a G1 e funciona sem problemas já faz uns 3 anos.</p>
<p>Poderia usar <strong>Water Cooler (AIO)</strong>? Poderia, mas tem que ser um AIO com radiador de 280mm a <strong>360mm</strong> (lembram quando falei sobre medir a dimensão dos componentes com o gabinete que vai comprar??). Um AIO funciona assim: em vez de ter uma ventoinha diretamente no dissipador de metal que fica grudado na CPU, passa um tubo onde circula líquido que vem de um tanque com uma bomba.</p>
<p>A água fria passa pelo dissipador, vai capturando calor, e levando pra um radiador. O radiador precisa de ventoinhas, pra dissipar o calor dele pro ar. Ou seja: vai ter ventoinhas de qualquer forma. Por isso eu não vejo vantagem. Veja um exemplo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914004612_71rD5Q7LtLL.webp" alt="Corsair Nautilus"  loading="lazy" /></p>
<p>Tem várias opções como a <strong>Corsair Nautilus 360 RS Liquid CPU Cooler</strong> ou uma <strong>NZXT Kraken 240 RGB AIO</strong>.</p>
<p>Por que se vê tanto AIO por aí? Porque os marketeiros descobriram do que as pessoas gostam: <strong>luzes RGB</strong>. Essa é a única razão. Em vez de uma única ventoinha eficiente Noctua, bege, sem graça, porque não <strong>três</strong> ventoinhas coloridas no radiador?? 🤣</p>
<p>Honestamente, tirando essa única razão, não existe motivos pra não ir direto pra Noctua NH-D15. Eu acho AIOs uma enorme perda de tempo.</p>
<p>Isso tudo dito, comprou a melhor CPU, a melhor Noctua, agora falta uma coisa entre as duas: <strong>Pasta Térmica</strong>. Normalmente, quando se compra o dissipador+ventoinha, como essa Noctua, já vem pasta térmica junto. No exemplo, deve vir a Noctua NT-H2, que é boa e suficiente.</p>
<p>Eu, pessoalmente, sempre confiei na <strong>Thermal Grizzly</strong>. Sempre usei a Kryonaut. Mas se dinheiro não for problema, porque não já ter uma <strong>Thermal Grizzly Duronaut</strong>?</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914005118_3393159-n1.webp" alt="thermal grizzly"  loading="lazy" /></p>
<p>Outras boas opções são a <strong>Arctic MX-6</strong>, <strong>Corsair XTM60 Performance</strong>, <strong>Thermalright TF7 12.8 W/mK</strong>, <strong>Cooler Master Cryofuze 5</strong> ou <strong>ID-Cooling Frost X45</strong>. Qualquer uma delas vai ser boa.</p>
<p>Passe na CPU, espalhe com uma espátula de plástico pela área toda e é isso. Esquece as mandingas de colocar pasta em &ldquo;X&rdquo; ou qualquer outro formato. Só espalha igualmente e pronto, talvez um pouquinho mais no meio.</p>
<p>E sim, existe a opção de <strong>Metal Líquido</strong>, como vem nos Playstation 5. Mas você não vai querer usar.</p>
<p>Metal Líquido é <strong>CONDUTOR ELÉTRICO</strong>, diferente de pasta térmica. Pra usar precisa SABER como usar. E você não vai saber. Vai vazar pelos lados e vai dar curto e você vai fritar sua CPU. Nem cogite. É pra usos específicos de quem sabe o que está fazendo.</p>
<p>Na dúvida, compre Thermal Grizzly, Duronaut ou Kryonaut. Acabou.</p>
<h3>Undervolting<span class="hx:absolute hx:-mt-20" id="undervolting"></span>
    <a href="#undervolting" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um pequeno adendo sobre notebooks, especialmente os mais velhos de mais de 5 anos pra trás. Eles não tem como colocar coolers grandes como um NH-D15 ou um AIO. E a geração Intel de mais de 5 anos atrás dissipava MUITO calor. Não era incomum a CPU chegar a 100 graus!!</p>
<p>Uma CPU normal opera em clock base, digamos 2 ou 3Ghz, e quando precisa de mais performance numa tarefa pesada, ele pode dar Turbo Boost pra 4Ghz ou mais. Só que isso aumenta consumo de energia (bateria vai embora mais rápido) e o pior: <strong>esquenta</strong>.</p>
<p>Se esquentar demais vai acontecer o temido <strong>THERMAL THROTTLE</strong>, ou seja, a CPU - pra se proteger, e não morrer - vai derrubar o block <strong>abaixo</strong> do base clock, digamos pra menos de 1 Ghz.</p>
<p>Não adianta nada dar Boost de 4Ghz por alguns minutos e depois passar um tempão capado em 1 Ghz até a temperatura baixar.</p>
<p>Por isso muita gente recomenda fazer <strong>undervolting</strong> com uma ferramenta como <a href="https://www.maketecheasier.com/reduce-cpu-temperature-undervolting/"target="_blank" rel="noopener">Throttlestop</a> no Windows ou <a href="https://github.com/sicreative/VoltageShift"target="_blank" rel="noopener">VoltageShift</a> no Mac ou intel-undervolt e amdctl no Linux (leia o <a href="https://wiki.archlinux.org/title/Undervolting_CPU"target="_blank" rel="noopener">ArchWiki de Undelvolt</a>)</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914015934_2021-06-07-113438.webp" alt="Throttlestop"  loading="lazy" /></p>
<p>A idéia é diminuir a voltagem que vai pra CPU/GPU, evitar que ele bata no teto de temperatura e evitar thermal throttle. É melhor que rode um pouco menos rápido no geral, do que rebaixar lento demais num throttle.</p>
<p>É um processo de tentativa e erro, depende do firmware, modelo da CPU, etc. Mas se você sente esse problema no seu notebook antigo, essa é uma solução.</p>
<p>Eu precisei usar muito isso na época dos Macbook Intel ou Dell XPS da era de 2016. Cuidado com notebooks com reputação de não ter boa solução térmica (que esquenta demais).</p>
<h3>Mini-PCs<span class="hx:absolute hx:-mt-20" id="mini-pcs"></span>
    <a href="#mini-pcs" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se achou tudo isso complicado, e acha notebooks muito caros, existe uma outra boa alternativa: <strong>Mini-PCs</strong>.</p>
<p>Muita gente fala dos <strong>Beelink</strong> e eu concordo, são muito bons. Se conseguir achar uma boa configuração num preço que cabe no seu bolso, pode comprar. Hoje em dia funcionam muito bem.</p>
<p>Eu mesmo uso um <strong>Intel NUC 13 Pro Slim</strong>, que vem com uma Intel Core i7-1360P de 13a geração, com 16GB e eu dei upgrade pra 32 GB (DDR4), tem PCIe 4.0 pra NVME, acho que vem com 512GB mas dei upgrade pra 1TB, Wi-fi 6E e Bluetooth 5.3, iGPU Intel Irix Xe (4K 60hz, não presta pra jogos mas pra produtividade é de bom tamanho), Ethernet 2.5Gbps (excepcional pra um Mini-PC), saídas HDMI e DP, e ainda tem Thunderbolt 4 e USB 3 Gen 2.</p>
<p>Não é barato, mas também não é absurdo. No Mercado Livre tá na faixa de <strong>BRL 6.000</strong>, o que é super aceitável pra essa configuração e zero trabalho. Eu uso ela como meu <a href="/tags/homeserver">home server</a>, como já mostrei em vários posts anteriores no blog.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914010051_screenshot-2025-09-14_01-00-40.png" alt="Grafana cAdvisor"  loading="lazy" /></p>
<p>Tenho dezenas de containers Docker rodando coisas como Plex, Sonarr, Radarr, Grafana, Cloudflare, etc e não usa quase nada da minha CPU. Eu coloquei 32GB mas poderia ter deixado com os 16 GB que veio porque male-male está usando 4GB de RAM no total.</p>
<p>Esse mini-PC é suficiente pra um programador médio. Vai durar muito tempo. Só conectar um monitor, teclado, mouse e é isso.</p>
<p>Eu tenho um segundo mini-PC, o <strong>Minisforum EliteMini UM780 XTX</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914010312_Minisforum-EliteMini-UM780-XTX-Mini-PC-connectivity-1200x610.webp" alt="Minisforum"  loading="lazy" /></p>
<p>Esse é mais potente, tem <strong>AMD Ryzen 7 8840U</strong> (categoria similar a um <strong>Rog Ally X</strong> de primeira geração, o de segunda é Z1 Extreme), 32GB, 1TB NVME PCIe 4.0, tem USB 4 (não tem Thunderbolt porque é exclusividade da Intel, Apple licencia). Mas pra mim o mais importante é que é um dos poucos PCs que tem saída <strong>OCulink</strong>.</p>
<p>OCulink é uma porta que conecta direto no barramento PCIe. Diferente de USB ou Thunderbolt que tem overhead do protocolo, OCulink é como se eu estivesse espetando uma placa direto no slot da placa-mãe.</p>
<p>Quando saiu o <strong>SteamDeck</strong> talvez vocês já tenham visto videos de pessoas fazendo mod e conseguindo conectar uma GPU externa nele. Pra fazer isso a GPU precisa se conectar direto na PCIe e a <strong>gambiarra</strong> foi usar o slot de NVME pra isso. Porque NVME também é um slot M2 que é PCIe, como já expliquei lá em cima.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914010609_external-gpu-cooled-deck-v0-814qfkk5y4cb1.webp" alt="SteamDeck GPU"  loading="lazy" /></p>
<p>Pois bem, com OCulink não precisa fazer essa gambiarra. A Minisforum vende o <strong>DEG1 OCulink EGPU</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914010830_Minisforum-DEG1-Oculink-eGPU-with-AMD-Radeon-W7700-GPU-5-800x487.webp" alt="DEG1"  loading="lazy" /></p>
<p>Lembram que eu falei que tenho uma RTX 4090 no meu PC e também uma RTX 4060 pra games? Eu ligo nesse deck de egpu, conecto uma PSU só pra GPU e o DEG1 conecta na porta OCulink da UM780, e ela se liga na minha TV OLED 80&quot;, e pronto! Game 4K na sala.</p>
<p>Vai ter menos lanes de PCIe do que num PC de verdade, mas vai ser mais rápido do que um EGPU com Thunderbolt 4. Pro sistema operacional, seja Windows, seja Linux, ele não nota diferença de estar dentro ou fora do gabinete. Pra ele é uma GPU normal conectado num slot PCIe 4.0 diretamente. Não recomendo EGPUs via Thunderbolt, só com OCulink.</p>
<p>E não adianta ligar minha 4090 porque não tem lanes de PCIe suficientes. OCulink é PCIe x4. Quando se lê &ldquo;x4&rdquo; significa <strong>4 Lanes PCIe</strong>, no caso 4x PCIe 4.0 então uma banda máxima de <strong>64Gbps</strong>.</p>
<p>Uma RTX 4090 foi desenvolvida pra <strong>PCIe 5.0 x16</strong>. Então eu estaria desperdiçando 3/4 da banda que ele é capaz de trafegar. Por isso disse que seria um desperdício. Um RTX 4060 foi desenhado pra <strong>PCIe 4.0 x4</strong> que é exatamente o máximo da OCulink, então esse é o teto pra uma EGPU.</p>
<p>Portanto uma RTX 4060 via OCulink vai performar quase que totalmente, sem overhead se fosse eGPU via Thunderbolt (que perderia uns 20% da banda). Mas potente que isso, vai desperdiçar a GPU.</p>
<p>Voltando pra mini-PC, como falei, <strong>Beelink</strong> é uma marca bem popular. E se quiser o seu topo de linha, é a <strong>Beelink SER9 Pro</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914011539_ser9.webp" alt="SER9"  loading="lazy" /></p>
<p>Pela bagatela de <strong>BRL 8.000</strong> no Mercado Livre, ela vem com AMD Ryzen AI 9 HX370 (12 cores/24 threads, até 5.1Ghz boost, 80 TOPS de AI), 32 GB de LPDDR5 (bem melhor que DDR4), 1 TB NVME PCIe 4.0, iGPU Radeon 890M (que é boa pra jogos também! muito melhor que um SteamDeck), Ethernet 2.5Gbps (excelente), e o básico: USB 4, HDMI, DisplayPort, USB 3.2, etc.</p>
<p>Pela configuração, eu acho que o preço condiz bem. Um notebook de configuração similar seria um <strong>ASUS ROG Flow Z13</strong> que tá na faixa de <strong>BRL 13.000</strong>. Se considerar que é portátil, vem com uma tela excelente, teclado, touchpad, bateria, não é um preço tão ruim.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914012100_rog%20flow%20z13.jpg" alt="Asus Rog Flow"  loading="lazy" /></p>
<p>Ou se quiser algo ainda mais <strong>Premium</strong>, seria um <strong>Razer Blade 16</strong> que tem o mesmo Ryzen AI Max mas uma tela 16&quot;, super fino e tals, mas com a Taxa Brasil, vai sair salgados <strong>BRL 40.000</strong>. Não dá pra justificar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914012231_814PVSAztPL.webp" alt="Blade 16"  loading="lazy" /></p>
<p>Se não quiser gastar tanto, ainda tem a <strong>Beelink SER8</strong> que tem AMD Ryzen 7 8845HS, que ainda é razoavelmente suficiente pra programar, com 8 cores/16 threads, iGPU Radeon 780M (que serve pra jogos não tão pesados), 32 GB DDR5 5600 (que é bom), 1 TB PCIe 4.0 que dá pra subir até 4 TB como já expliquei. Vem com Wi-Fi 6, Bluetooth 5.2, Ethernet 2.5 Gbps também, suporta HDMI 2.1, DP 1.4, USB 4.</p>
<p>E num Mercado Livre ainda se acha por uma faixa de <strong>BRL 5.000</strong>, similar ao meu Intel NUC. Acho que esse é o &ldquo;sweet-spot&rdquo; entre preço e performance. Pra mim, pessoalmente, acho fraco, mas não quer dizer que seja inútil. De novo, depende de quanto você pode gastar. O melhor PC é aquele que você pode comprar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250914012714_ser8.webp" alt="Beelink SER8"  loading="lazy" /></p>
<p>Se instalar <a href="/tags/omarchy/">Omarchy</a> nesse Beelink, fica redondo! Recomendo!</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Acho que isso é tudo que eu queria compartilhar sobre escolher componentes de PCs. Nos videos do meu canal eu explico vários conceitos que complementam isso como <a href="https://www.youtube.com/watch?v=0TndL-Nh6Ok&amp;list=PLdsnXVqbHDUcTGjNZuRYCVj3AZtdt6oG7&amp;pp=gAQB"target="_blank" rel="noopener">Introdução a Redes</a> ou <a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;pp=gAQB"target="_blank" rel="noopener">Entendendo Armazenamento</a> ou mais &ldquo;escovação de bits&rdquo; em <a href="https://www.youtube.com/watch?v=8G80nuEyDN4&amp;list=PLdsnXVqbHDUcQIuiH9b-i9A85H3A2ZW5W&amp;pp=gAQB"target="_blank" rel="noopener">Como Computadores Funcionam</a> e até coisas mais nerds como <a href="https://www.youtube.com/watch?v=tEXX1jdpZN8&amp;list=PLdsnXVqbHDUfTsz27oFYUaQD-1n9PXoyy&amp;pp=gAQB"target="_blank" rel="noopener">Saga dos Teclados</a> ou, pra programadores, <a href="https://www.youtube.com/watch?v=bwO8EZf0gLI&amp;list=PLdsnXVqbHDUdjNjfekBoCBfGtJ6kNRef-&amp;pp=gAQB"target="_blank" rel="noopener">Como Containers Funcionam</a> e <a href="https://www.youtube.com/watch?v=suSvMnNwV-8&amp;list=PLdsnXVqbHDUd17xLINVEXhJ32RxbzCEWV&amp;pp=gAQB"target="_blank" rel="noopener">Sistemas Operacionais</a>, tudo organizado em playlists. Só assistir.</p>
<p>Pra se manter atualizado, recomendo alguns canais que eu gosto:</p>
<ul>
<li><a href="https://www.youtube.com/@LinusTechTips"target="_blank" rel="noopener">Linus Tech Tips</a></li>
<li><a href="https://www.youtube.com/@GamersNexus"target="_blank" rel="noopener">Gamers Nexus</a></li>
<li><a href="https://www.youtube.com/@ETAPRIME"target="_blank" rel="noopener">ETA Prime</a></li>
<li><a href="https://www.youtube.com/@Coreteks"target="_blank" rel="noopener">CoreTeks</a></li>
<li><a href="https://www.youtube.com/@DigitalFoundry"target="_blank" rel="noopener">Digital Foundry</a> (pra entender efeitos de funcionalidades de GPUs em games)</li>
<li><a href="https://www.youtube.com/@HardwareCanucks"target="_blank" rel="noopener">Hardware Canucks</a></li>
<li><a href="https://www.youtube.com/@MatthewMoniz"target="_blank" rel="noopener">Matthew Moniz</a></li>
<li><a href="https://www.youtube.com/@Dave2D"target="_blank" rel="noopener">Dave2D</a></li>
</ul>
<p>O que não falta no YouTube são &ldquo;Tech Reviewers&rdquo; e a GRANDE maioria são extremamente ruins. Não recomendo a maioria. Tirando os da minha lista acima, não sobra muitos não. Tem alguns mais ou menos como Austin Evans, mas é mais entretenimento que review técnico, então também não recomendo.</p>
<p>Achou tudo isso demais? Isso foi só a ponta do iceberg. Não se preocupe em acertar de primeira. Você nunca vai ter só 1 PC. O importante é que preste atenção onde errou pra acertar na próxima. Mas se não se importar em pesquisar esses componentes todos e ficar só ouvindo YouTuber, vai fazer igual gente que consulta astrólogo pra saber como vencer na vida: não vai.</p>
<blockquote>
  <p><strong>Vou repetir: o melhor PC é aquele que você pode comprar!</strong></p>

</blockquote>
<p>Meu primeiro PC foi um 286 de 1MB de RAM no fim dos anos 80. Desde então passei por <strong>vários</strong> PCs. Numa era antes da Internet, eu cansei de ler classificados nos jornais pra pesquisar preços, eu ia bater perna na rua Santa Ifigênia, em São Paulo, que era a meca dos componentes eletrônicos. No começo da Internet eu cansei de fuçar ecomerces mais ou menos, componentes da Ásia via ums LikSang da vida.</p>
<p>Tive vários PCs, vários notebooks. Por isso eu tenho uma boa noção hoje, com quase 50 anos e como podem ver, eu continuo atualizado. Você de 20 tem que ter vergonha de ter preguiça. Isso tudo leva tempo, mas precisa começar não tendo medo de <strong>fuçar</strong> e <strong>errar</strong>.</p>
<p>Eu já queimei componentes, já dei curto, já fiz gambiarra, já formatei HD errado. Tudo que se pode imaginar que dá pra fazer errado, eu já fiz, por isso sei que é errado. E eu sei ler, li o que os outros faziam, e tentava do meu jeito. Montar PC é algo que você vai fazer a vida toda. Quanto mais cedo tirar a preguiça e aprender os componentes, menos problemas vai ter no futuro.</p>
<p>Ser o &ldquo;melhor programador&rdquo; significa aprender a tirar o máximo que seu hardware consegue entregar e isso significa aprender a tunar cada componente e OS até extrair tudo dele. Foi assim que eu mais aprendi sobre computação: com hardware fraco. Ter dinheiro e comprar o mais caro só significa que você não vai aprender nada além de reclamar. E reclamar não tem valor.</p>
]]></content:encoded><category>hardware</category><category>performance</category></item><item><title>Seu Windows pode estar Capado sem Você Saber. Cheque isto!!</title><link>https://akitaonrails.github.io/2025/05/21/seu-windows-pode-estar-capado-sem-voce-saber-cheque-isto/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/21/seu-windows-pode-estar-capado-sem-voce-saber-cheque-isto/</guid><pubDate>Wed, 21 May 2025 17:15:00 -0300</pubDate><description>&lt;p&gt;Eu normalmente não ligo de documentar sobre problemas bobos de Windows, mas este em particular me deixou irritado, então vou relatar a sequência de eventos. Mas já deixo o spoiler do final: abra Control Panel e Power Options no seu Windows e cheque se ele não está em &amp;ldquo;Power saver&amp;rdquo; mode. Tire desse lixo, deixe no mínimo em Balanced, mas se for PC Desktop suba pra &amp;ldquo;High Performance&amp;rdquo; ou &amp;ldquo;Ultimate Performance&amp;rdquo;. Depois me agradeça.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu normalmente não ligo de documentar sobre problemas bobos de Windows, mas este em particular me deixou irritado, então vou relatar a sequência de eventos. Mas já deixo o spoiler do final: abra Control Panel e Power Options no seu Windows e cheque se ele não está em &ldquo;Power saver&rdquo; mode. Tire desse lixo, deixe no mínimo em Balanced, mas se for PC Desktop suba pra &ldquo;High Performance&rdquo; ou &ldquo;Ultimate Performance&rdquo;. Depois me agradeça.</p>
<p>Agora senta que lá vem história.</p>
<p>Eu montei um PC até que razoável pra minha namorada: Intel i7 12th Gen de 8 cores, 32GB DDR4-2400, NVIDIA RTX 3090, placa-mãe MSI Edge z790, NVME e até coloquei um NAS Synology DS1621+ em rede 2.5Gbps - porque ela é criadora de conteúdo e tem MUITO video pra editar. Modéstia a parte, ficou bonito:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a8c82k3ocnpu1jamxb9w0z1fx2cf" alt="pc branco"  loading="lazy" /></p>
<p>Tudo funcionou muito bem por bastante tempo, mas eventualmente, sem nenhum motivo aparente, tudo começou a parecer meio lento. Não totalmente lento, mas às vezes páginas web pareciam engasgar pra carregar. Clicava no menu Start do Windows e ele parecia dar umas travadas. Às vezes o Explorer travava e precisava ir no Task Manager e dar restart no processo manualmente. Várias coisas super inconvenientes mas que não dava nenhum &ldquo;erro&rdquo; explícito aparente. Só parecia &ldquo;estranho&rdquo;.</p>
<p>Eu sou usuário de Linux. Meu PC principal é Manjaro Linux. Só uso Windows nos meus mini-PC de games, exclusivamente pra rodar Steam e alguns emuladores. Eu evito ao máximo instalar qualquer coisa neles. Porque sei como é um saco diagnosticar Windows depois que algum programa estranho invade.</p>
<p>Fui olhar os suspeitos de sempre. Ela tinha instalado jogos pra fazer streaming, tipo Valorant, Genshin Impact e coisas assim. Eu sempre suspeito de anti-cheats, as porcarias do Riot Vanguard ou Easy ou VAC. Comecei desinstalando todos os games. Mas eu sempre acho que anti-cheat é que nem malware: uma vez instalado, nunca mais vai sair do sistema. Desinstalei tudo, mas continuou essa sensação de meio travando, meio lento, às vezes, intermitente.</p>
<p>Tentei os próximos suspeitos: software de periféricos, como os da Elgato, Logitech, Razr que instalam um monte de porcaria nas máquinas, e recentemente estava lendo sobre o malware que vinha em todos eles. Assistam este video do GamerNexus pra entender a casa de cartas que é toda a indústria de periféricos com RGB:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/H_O5JtBqODA"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Mesmo desinstalando todos, ainda mesmo comportamento: meio lento, meio travando, instável.</p>
<p>Vamos tentar ir mais a fundo, pra isso tem que instalar ferramentas como da <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/autoruns"target="_blank" rel="noopener">Sysinternals Autoruns</a> pra ver se tem alguma coisa iniciando (coisas escondidas no Registry por exemplo). Tentei desligar tudo que eu achava suspeito.</p>
<p>Nada. A essa altura também já tinha garantido que firmware de BIOS mais nova da MSI estava instalado. Chequei se nada tinha desligado na BIOS, XMP profile tava ok, nada de fast-boot e coisas assim.</p>
<h3>Será a Internet?<span class="hx:absolute hx:-mt-20" id="será-a-internet"></span>
    <a href="#ser%c3%a1-a-internet" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como o uso maior é internet e ela parecia &ldquo;lenta&rdquo;, pensei que poderia ser problemas de rede. Roteador da Vivo? Plano? Wifi com interferência?</p>
<p>Ficando sem paciência resolvi ir nuclear na idéia de internet. De fato, abrindo a página da <a href="https://speed.cloudflare.com/"target="_blank" rel="noopener">Cloudflare Speed Test</a> eu via que a velocidade em si estava ok, mas a latência estava MUITO alta, jitter alto e, principalmente PACKET LOSS estava em mais de 15%. Não é normal isso, tem que ser 0%.</p>
<p>Aproveitei que fui pra Miami e já trouxe um roteador TP-LINK BE9300 pra Wi-fi 7, comprei uma placa PCIe de Wi-fi 7 compatível também. Desliguei o roteador de Wi-fi da Vivo e mandei subir o plano pra 800Mbps. E de fato, melhorou muito a internet:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ogesvmtex2ek825hel7d4l0f5i30" alt="vivo internet"  loading="lazy" /></p>
<p>Latência caiu, jitter caiu, packet loss foi pra 0%.</p>
<p>Mas isso não resolveu o problema. Do nada o navegador dava umas pausas na hora de carregar alguma página. Aquela sensação meio de lento, meio travando. E sim, tentei trocar de Edge pra Chrome, pra Firefox, pra tudo. Nada fazia diferença. A internet ficou mais rápida mesmo, mas o Windows em si não pareceu que melhorou muito e navegando, começava a ficar mais e mais lento. Então o problema não era internet.</p>
<p>Estava ficando sem opções. Tudo que poderia ser manualmente limpo no Windows, eu limpei. Já não tinha quase mais nada pra apagar - e eu apago as coisas sem dó. Mas eu sempre fico na cabeça que é algum malware, alguma coisa que carrega antes do boot, algum &ldquo;ROOTKIT&rdquo;. Anti-cheats são Rootkits por definição. Eles carregam antes até da kernel e a partir desse ponto, o sistema comprometido não pode ser mais confiado, porque nenhuma ferramenta vai conseguir achar ele.</p>
<p>Assumindo que nesse ponto deve ser um sistema comprometido, resolvi ir na opção nuclear: reinstalar tudo do zero. Fizemos backup dos dados e lá fui eu reinstalar do zero.</p>
<h3>Reinstalar NÃO FUNCIONOU<span class="hx:absolute hx:-mt-20" id="reinstalar-não-funcionou"></span>
    <a href="#reinstalar-n%c3%a3o-funcionou" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora ficou pior.</p>
<p>Com o Windows 11 Pro, recém-instalado, garantidamente sem nenhum software de terceiros instalado, ainda assim estava com o mesmo comportamento de meio lento, meio travando. O menu de start não me deixava clicar em nada. Precisava abrir o task manager e matar o Explorer manualmente pra fazer ele voltar a funcionar.</p>
<p>Fiquei um tempo olhando pro Task Manager, mas não tinha nenhum processo rodando que eu poderia suspeitar mais: era Windows bare-bones. Eu até tinha rodado um <a href="https://github.com/Raphire/Win11Debloat"target="_blank" rel="noopener">Debloater</a> pra desinstalar os apps desnecessários da Microsoft como Teams.</p>
<p>Mas eis que, de tanto olhar o task manager, eu vi que estava ignorando um dado importante. Veja se acham:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/pky39kkwcbuyqocwpj15n36i9gai" alt="cpu low"  loading="lazy" /></p>
<p>A imagem tá meio zoada porque eu tirei foto do monitor com meu celular. O refresh rate conflita com da câmera e fica essa &ldquo;grade&rdquo;, mas acho que dá pra ler.</p>
<p>Mesmo quando você não tem quase nenhum programa rodando, não é natural a CPU estar com clock tão baixo assim, 0.50Ghz - MEIO GIGAHERTZ. Esse Intel i7 é capaz de ir acima de 4Ghz e o clock-base é de 2Ghz.</p>
<p>Abri navegadores, abri abas, fiquei monitorando, e nunca subia pra mais de 1.5Ghz. Um ocasional pico de 2Ghz por 1 segundo e depois caía pra abaixo de 1Ghz de novo. Daí pensei: será que o AIO está falhando??</p>
<p>Olhando o Event Viewer dava pra ver quando o menu Start (que faz parte do Explorer.exe) parava de funcionar e travava: ele registra no log como &ldquo;Hanging Events&rdquo; código 1002.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dtugk013p4decud6zo1dzg3rjhxr" alt="event viewer"  loading="lazy" /></p>
<p>Tá vendo esse monte de Error? Tudo Hanging Events, e &ldquo;hanging&rdquo; é justamente o comportamento: alguma coisa parece &ldquo;segurar&rdquo;, &ldquo;travar&rdquo; e só matando o processo pra ele voltar a funcionar.</p>
<p>E isso acontecia antes e também DEPOIS de resinstalar tudo do zero. Alguma coisa parecia fundamentalmente errada no hardware em si. Com o clock super baixo, esse comportamento começa a fazer sentido: está tão lento que ele não está conseguindo processar coisas básicas. Clique várias vezes no Start Menu (que vai ficar fuçando documentos recentes, atualizando lista de aplicativos, abrindo search bar, etc) e não tem clock pra processar isso enquanto ainda tem Windows Defender em background, abas de navegador processando.</p>
<h3>Será Thermal Throttle??<span class="hx:absolute hx:-mt-20" id="será-thermal-throttle"></span>
    <a href="#ser%c3%a1-thermal-throttle" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como o Windows está zerado, não pode mais ser anti-cheat, não pode ser anti-virus esquisito, não pode ser malware, não é a internet, é alguma coisa FORA, era o que fazia sentido.</p>
<p>Abri o PC e deixei aberto pra poder sentir se o dissipador estava ficando absurdamente quente. Se estiver, iria significar que meu AIO (Watercooler da Coolermaster) começou a falhar.</p>
<p>É normal quando a CPU super-aquece, ele derrubar o clock pra evitar queimar o chip. Isso se chama &ldquo;THEMAL THROTTLING&rdquo;.</p>
<p>E de novo esquisito: estava FRIO ao toque!!!</p>
<p>Não é possível. Abri a BIOS pra re-checar perfis de ventoinha, mas tudo normal:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yl02sk0dug6zj9d1gen877d22gnw" alt="cpu fans"  loading="lazy" /></p>
<p>Eis que finalmente me ocorreu:</p>
<pre><code>**DEIXA EU CHECAR CONFIGURAÇÃO DE POWER**
</code></pre>
<p>Num PC/notebook normal, deveria aparecer algo parecido com isto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a5dkm6eag4ebma6ye1i1mnzphy3s" alt="Power Settings"  loading="lazy" /></p>
<p>Por default, Windows sempre sobe em &ldquo;Balanced Mode&rdquo;.</p>
<p>Mas no PC da minha namorada, <strong>não sei por que</strong>, não tinha opção de Power Mode!!! E eu estou num Windows 11 pro com licença paga!!</p>
<p>Isso é esquisito, mas eu sei que no antigo Control Panel, tem opção de Power Options:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/70ymku67f5ufp0pd76jp0b2hqjx2" alt="control panel"  loading="lazy" /></p>
<p>E EIS O PROBLEMA:</p>
<p>Estava em &ldquo;Power saver&rdquo;!!!</p>
<p>Como isso aconteceu? Não tenho a mínima idéia. LEMBREM-SE: eu re-instalei o Windows do zero. Isso é logo depois de reinstalar!!</p>
<p>Em notebook faz sentido ter esse perfil, pra economizar bateria quando você não tem tomada. Mas em PC desktop, como é meu caso, não faz nenhum sentido!! Mudei pra &ldquo;Ultimate Performance&rdquo; e olha o task manager agora:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5hg5d85ivuxv9tw5si21wcb3hm5p" alt="cpu high"  loading="lazy" /></p>
<p>AGORA SIM!! Acima de 4Ghz!!!</p>
<p>O NORMAL desta CPU é estar na faixa de 4Ghz, com Turbo Boost de 5Ghz. Mas em Power saver mode, ele NUNCA sobe acima de 2Ghz e fica idling em 500Mhz. Ou seja, estava tudo <strong>DEZ VEZES MAIS LENTO</strong>, às vezes tentava dar boost e ficava ainda <strong>CINCO A DUAS VEZES MAIS LENTO</strong>. E é intermitente!!!</p>
<p>Testando instalar apps, navegar, etc, tudo agora está funcionando normalmente, sem travar, sem sensação de lentidão. Start menu não trava mais - porque agora tem CPU suficiente pra processar as porcarias que ele precisa em background.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>CHEQUE POWER OPTIONS NO CONTROL PANEL!!!!</p>
<p>Repetindo: eu tinha ACABADO DE REINSTALAR O WINDOWS. Estava zerado e, por default, ele subiu em Power Saver mode.</p>
<p>Não tenho idéia de porque ele fez isso, não sei se ele sempre faz isso, não sei se ele não reconheceu a placa-mãe da MSI, não sei que combinação de fatores de hardware podem fazer o Windows dar fallback pra isso.</p>
<p>Mas é ALARMANTE que ele faça isso sem avisar. Não tem mensagem de erro. Não tem notificação. Não aparece no Event Viewer. Ou seja, mesmo um Power User como eu eu, precisa aleatoriamente suspeitar &ldquo;hum, deixa eu ver o perfil de energia&rdquo;.</p>
<p>Muita gente com Windows deve estar sentindo essa lentidão e travamentos e achando &ldquo;hm, meu PC é porcaria mesmo, preciso juntar dinheiro pra comprar outro&rdquo;, sem saber que talvez ele só esteja capado por esse perfil de energia e na verdade o PC dele é de 2x a 5x, até 10x mais rápido e ele não sabe disso! Não é uma diferença pequena!</p>
<p>POWER SAVER MODE é uma grande porcaria, não tem nenhuma utilidade prática e nem deveria existir.  É uma das coisas legadas que fazem o Windows ter uma péssima experiência - porque ele não avisa.</p>
<p>Lembra os códigos de erros 1002 de hanging events? Se sair procurando no Google, todo fórum vai te dizer pra rodar programas de diagnósticos, desinstalar coisas, ou reinstalar o Windows. Nenhum cita nada sobre perfil de energia.</p>
<p>Espero que isso ajude mais alguém!</p>
]]></content:encoded><category>microsoft</category><category>performance</category><category>hardware</category></item><item><title>Último Tentativa de Treinar uma LLM com LoRa. Tiro de canhão, mas errando a mosca.</title><link>https://akitaonrails.github.io/2025/05/03/ultimo-tentativa-de-treinar-uma-llm-com-lora-tiro-de-canhao-mas-errando-a-mosca/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/03/ultimo-tentativa-de-treinar-uma-llm-com-lora-tiro-de-canhao-mas-errando-a-mosca/</guid><pubDate>Sat, 03 May 2025 21:15:00 -0300</pubDate><description>&lt;p&gt;Ok, eu sou insistente. no &lt;a href="https://www.akitaonrails.com/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; expliquei tudo que eu sabia sobre tentar fazer fine-tuning de um modelo com LoRa. Mas é um processo tedioso, demorado, e limitado à &amp;ldquo;pobreza&amp;rdquo; da minha RTX 4090. Em 24GB de VRAM tem que caber o modelo, coisas como KV cache, dataset de treinamento e espaço pra processar tudo. Apertando, cabe o modelo Qwen3-8B e 1MB de dataset e já era. Vai usar 21GB constantemente durante mais de 1 hora e o resultado final, apesar do modelo conseguir responser &amp;ldquo;sim, eu sei Zig 0.14&amp;rdquo;, não é bom - porque o modelo base de só 8B já não era grande coisa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ok, eu sou insistente. no <a href="https://www.akitaonrails.com/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"target="_blank" rel="noopener">post anterior</a> expliquei tudo que eu sabia sobre tentar fazer fine-tuning de um modelo com LoRa. Mas é um processo tedioso, demorado, e limitado à &ldquo;pobreza&rdquo; da minha RTX 4090. Em 24GB de VRAM tem que caber o modelo, coisas como KV cache, dataset de treinamento e espaço pra processar tudo. Apertando, cabe o modelo Qwen3-8B e 1MB de dataset e já era. Vai usar 21GB constantemente durante mais de 1 hora e o resultado final, apesar do modelo conseguir responser &ldquo;sim, eu sei Zig 0.14&rdquo;, não é bom - porque o modelo base de só 8B já não era grande coisa.</p>
<p>A saída: migrar meu treinamento pra uma máquina maior. Comecei tentando uma A40, pra ver se 40GB era suficiente pra caber o modelo Qwen3-32B pra esse treinamento. E não, não coube.</p>
<p>&ldquo;Ph0da-se&rdquo;, pensei. Aluguei a maior configuração que tem na RunPod:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/9m5uitxvhhbpotu5lkbrw0znd276" alt="H100"  loading="lazy" /></p>
<p>Essa H100 já é &ldquo;obsoleta&rdquo;, nos datacenters mais modernos já existe H200 Blackwell e outras coisas maiores. Mas no mundo mais de &ldquo;mortais&rdquo;, uma H100 ao custo de USD 3 por HORA, vai ter que servir. O bom: tem 80GB de VRAM. E o que eu não esperava: até isso é pouco!</p>
<p>E aqui a vantagem de ter feito tudo organizado e subir neste <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">repo no GitHub</a>. O setup foi trivial: foi só escolher subir um pod com image de PyTorch 2.8 com Python 3.10 e Cuda 12.8, que são as versões estáveis mais recentes. Baixar meu projeto, rodar um <code>pip install -r requirements.txt</code> e já podia rodar meu <code>./train-lora.py</code>.</p>
<p>Eu ia adicionar de volta aqueles 15MB de código-fonte da Standard Library do Zig mais novo, mas mesmo na H100, não coube! O treinamento só com o 1MB original que eu usei ontem também, já fez a máquina ficar entuchada quase no máximo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/iahqr9hzs2aaku3fup7u3cw9navk" alt="74GB"  loading="lazy" /></p>
<p>Olha que absurdo: ficou o tempo todo do treinamento ocupando quase 75GB dos 80GB que tem de VRAM. Literalmente não dá pra fazer treinamento com muito mais material que isso. Ou precisa descer pra um modelo menor, como o Qwen3-14B. Eu ainda não sei se 32B com menos material ou 14B com mais material, qual seria a melhor combinação. Eu já sei que 8B não presta pra programar nada complicado. 14B já tive um pouco mais de sucesso em testes, mas também não faz nada muito complicado. 32B não é garantia que faz muito melhor: o Qwen2.5-32B não se saiu melhor que o 14B, mas o novo Qwen3-32B tem &ldquo;cara&rdquo; que é mais estável. É isso que estou querendo testar, com o LoRa.</p>
<p>Isso foi na hora de almoço. Estou escrevendo este post na hora da janta. Esta foi a previsão do treinamento:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lzrfdl97n96qesnhde33lugidr3i" alt="6h"  loading="lazy" /></p>
<p>Essa estimativa fica variando, mas eu acho que levou em torno de 5 a 6 horas mesmo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/stoakih27g3o9c1e1d07y14szrf0" alt="lora size"  loading="lazy" /></p>
<p>E no final dessas quase 6 horas, foi isso que gerou: um LoRa de 5.1GB (contra os 3GB de ontem). Tamanho não é documento. Não significa que maior é melhor. Vamos ver.</p>
<h3>Depois do Treino<span class="hx:absolute hx:-mt-20" id="depois-do-treino"></span>
    <a href="#depois-do-treino" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora que acabou o treinamento, posso usar o mesmo pod com a H100 pra também servir esse modelo com o Lora, e precisaria ser ele de qualquer forma. Pra rodar é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">vllm serve Qwen/Qwen3-32B <span class="se">\
</span></span></span><span class="line"><span class="cl">    --enable-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --lora-modules <span class="nv">ziglora</span><span class="o">=</span>./qwen3-zig-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --max-model-len <span class="m">10000</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E precisa limitar ainda o tamanho do contexto. Por default o vLLM tenta subir com contexto de 40k tokens e, mesmo na H100, com 80GB, ele falha a inicialização com este erro:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ykusgib7o6wybhi5rs4zvx2yrp8j" alt="KV Cache error"  loading="lazy" /></p>
<p>Não sobra espaço pra subir o KV Cache (em resumo, no processo de geração de próxima palavra, vai adicionando uma nova linha e uma nova coluna na matrix, mas não precisa recalcular as linhas/colunas anteriores, é o mesmo resultado. Daí tem esse cache).</p>
<p>Um comportamento estranho tanto na minha máquina quanto na H100. Não sei se isso acontece sempre que carrega um Lora, ou se meu Lora tem defeito, ou se tem alguma configuração que eu não estou vendo. Mas carregando com Lora, a inicialização é EXTREMAMENTE LENTA. Subir o vLLM, que normalmente leva 1 minuto, agora leva 5 minutos, ou mais. Não sei o que causa isso, mas não é um tipo de servidor que pode ser facilmente desligado e religado. Faz eu ter saudade de subir um servidor J2EE &hellip;</p>
<p>Falando sério, parece que não sou só eu. Esta <a href="https://github.com/vllm-project/vllm/issues/6876"target="_blank" rel="noopener">Issue</a> não teve solução. &ldquo;Pode&rdquo; ser algum bug no vLLM, &ldquo;pode&rdquo; ser uma combinação de fatores que ninguém sabe. Mas nem a GPU e nem a CPU estão processando nada. CPU não sai de 10% de uso, GPU parece que está só esperando. Realmente, à primeira vista parece algum problema de I/O. Talvez o processo de checar os shards de checkpoints, puxando de um network volume, seja lento. Mas como vou rodar só hoje, não me preocupei em debugar isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mluc626od3h1q0swnv8kepe1aji5" alt="slow load"  loading="lazy" /></p>
<p>Olha isso. 8 MINUTOS nessa parte de carregar checkpoints. E depois ainda continua outras fases que também demora mais um pouco. Vou deixar essa foto aqui, caso alguém já tenha visto e saiba o que pode ser.</p>
<p>Só pra tirar a dúvida, experimentei subir o vLLM só com o Qwen3 e tirando a LoRa completamente e, pra minha surpresa:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2hzvcqrk087i5ov4otx0rxa8erdf" alt="sem lora"  loading="lazy" /></p>
<p>Também levou quase 8 minutos. Então o problema não é a LoRa. Ou é alguma coisa do vLLM que dá pra configurar - mas eu não sei como - ou é uma limitação do próprio modelo Qwen3-32B que é lento assim mesmo pra carregar.</p>
<p>Enfim, com Lora, tentei diminuir pra 16k tokens: não sobe. Tentei subir com 10k tokens. E nesse ponto eu diria que esse teste já fracassou. Um Qwen3-32 teoricamente tem capacidade de suportar janelas de até 40k tokens. Não é grande, mas dá pra trabalhar um pouco nesse contexto.</p>
<p>Mas abaixo de 15k tokens já não dá pra usar com ferramentas como Aider, que precisa mandar system prompts longos pra instruir a LLM como se comportar pra cuspir código que ele consegue capturar. Então é isso: um Qwen3-32b + Lora, numa H100 SXM de 80GB de VRAM, não é usável de verdade.</p>
<p>E eu até ficaria triste ou frustrado caso um modelo de 32B fosse realmente, ordens de grandeza melhor que um 14B da vida, mas nos meus testes empíricos: não é. Ele faz tanto erro quanto, e não é tão melhor assim que compense pagar tão caro por uma infra própria topo de linha. Todos eles male male só conseguem fazer códigos simples, e mesmo assim com erros.</p>
<p>No final, com 10k o server subiu, consumindo absurdos 63GB na inicialização, sem nem começar a processar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uetjwlzlgy7bpd4itn1qxczl3mhs" alt="63GB"  loading="lazy" /></p>
<p>E &hellip;</p>
<h3>Primeiros Testes<span class="hx:absolute hx:-mt-20" id="primeiros-testes"></span>
    <a href="#primeiros-testes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois de 10 minutos esperando, tive que dar Ctrl+C e começar tudo de novo. Porque eu fui burro. A RunPod é pra realmente rodar coisas experimentais, não sei se confio pra produção. Ela é mais um VPS do que uma AWS. E eu estou gambiarrando subindo um pod que espera ter mapeado só a porta 8888 pra subir um server de Jupyter Notebook (que eu não estou subindo).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/h4pzcdxydfiou5iulc7m67zctb9a" alt="runpod connect"  loading="lazy" /></p>
<p>MInha idéia é usar essa porta pra mapear o vLLM. Mas por default ele sobe na porta 8000. Eu esqueci de colocar <code>--port 8888</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>vllm serve Qwen/Qwen3-32b --enable-lora --lora-modules ziglora=./qwen3-zig-lora --max_model_len 10000 --port 8888</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E lá vamos nós de novo, esperar mais 10 minutos &hellip; &hellip; e finalmente subiu na porta certa e agora podemos testar. Primeiro, os teste de curl:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p8d3ogzwd7lzpu05o7lkm50f8k4h" alt="curl hello"  loading="lazy" /></p>
<p>E antes que alguém comente: não tem problema estar aparecendo a URL do meu pod na foto. Eu vou apagar esse pod antes de publicar este post.</p>
<p>Aliás, se subir um modelo remoto, como eu fiz, depois é só pegar a URL e colocar na variável <code>OPENAI_API_BASE</code> e qualquer coisa em <code>OPENAI_API_KEY</code> e ferramentas como Aider vão funcionar igual. A API do vLLM e Ollama são compatíveis com da OpenAI.</p>
<p>Como podem ver, a primeira pergunta respondeu parecido com meu teste local com o Qwen3-8B. Nada demais, isso já sabia que funcionava. Vamos pedir pra explicar uma funcionalidade nova do Zig 0.14.</p>
<p>Aliás, além de lento pra iniciar, também fica mais lento pra gerar texto, BEM mais lento. Eu devo ter feito algo errado no treino, mas eu senti a mesma coisa no Lora local ou na H100. Fica notavelmente mais lento. Ele fez mais um &ldquo;think&rdquo; longo, mas a resposta desta vez é mais condizente com o release note (cortei só o trecho que interessa):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>In Zig 0.14.0, the `@splat` function has been extended to support arrays, making it easier to initialize fixed-length arrays with a single repeated value. This replaces more verbose or unsafe initialization patterns and is type-safe, with the array&#39;s length inferred from the destination type. Here&#39;s a concise explanation and code example:


...

### **@splat for Arrays**  
`@splat(value: T) [N]T` creates an array of length `N` where every element is `value`. The array&#39;s length and element type are determined by the context in which `@splat` is used.  

### **Code Example**  

const Point = struct { x: i32, y: i32 };  

pub fn main() void {  
    // Initialize an array of 5 integers with 42  
    const numbers: [5]i32 = @splat(42);  

    // Initialize an array of 10 booleans with true  
    const flags: [10]bool = @splat(true);  

    // Initialize an array of 7 points with (0, 0)  
    const origin_points: [7]Point = @splat(Point{ .x = 0, .y = 0 });  
}  
....</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O código de exemplo também faz um pouco mais de sentido. Comparado com a versão 8B, pelo menos a explicação foi melhor. Mas pode ter afetado que eu fiz uma pequena modificação no dataset: eu dupliquei o release notes, pra tentar um pouco mais de ênfase nas funcionalidades novas. Provavelmente isso também fez efeito.</p>
<p>Agora vamos ver se ele consegue fazer algum código que funciona. Meu prompt foi este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;Can you give me a Zig 0.14 code example of a simple program that acts like an interactive chat, so the user can input messages. then the message is parsed to see if there are commands such as &#39;/list&#39;, which will list the files in the current directory, or &#39;/cat&#39; which will read the file asked, or &#39;/quit&#39; to exit the chat?&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isto aconteceu:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4n3bzm5g0gt46jfmqv55qy0x9tlu" alt="abort"  loading="lazy" /></p>
<p>Lembra como eu tive que ir diminuindo contexto pra conseguir subir? E lembram como eu falo que &ldquo;thinking&rdquo; desperdiça muitos tokens? Pois é. O modelo se auto-crasheou pensando demais. Tentei algumas vezes e ele crasheia. Pra tentar evitar isso, tentei fazer o <code>curl</code> mandando <code>enable_thinking=false</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl https://d8zck5aw8eimij-8888.proxy.runpod.net/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;, &#34;enable_thinking&#34;: &#34;false&#34;, &#34;skip_special_tokens&#34;: true,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;...&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">      }&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Temperatura, pra código, eu acho que não dá pra ser muito maior que 0.1. Pra bate-papo, coisas menos exatas, o padrão de 0.6 que todo mundo usa é ok. Mas pra código, eu realmente não preciso que ele fique sendo &ldquo;criativo&rdquo; (aleatório, na verdade), eu quero respostas o mais exatas quanto possível.</p>
<p>Enfim, mesmo tentando desligar o thinking, alguma coisa está errada. Possivelmente o LoRa que eu adicionei está fazendo ele &ldquo;pensar em círculos&rdquo; e nunca conseguindo retornar, estourando o contexto. Tentei várias vezes, mas ele está sendo incapaz de responder código.</p>
<h3>Final: Desisto por enquanto<span class="hx:absolute hx:-mt-20" id="final-desisto-por-enquanto"></span>
    <a href="#final-desisto-por-enquanto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sim, eu quebrei a LLM, ao que parece kkkkk 😅</p>
<p>Tentei várias vezes e ele realmente não consegue me devolver nenhum código. E eu sei que ele está tentando, porque se eu mando <code>stream=true</code>, eu consigo ver devolvendo tokens, mas em algum momento acaba espaço em algum lugar (com certeza o contexto, que é pequeno) e crasheia.</p>
<p>A sensação é que meu LoRa provavelmente &ldquo;grudou&rdquo; demais e desbalanceou parâmetros demais. Agora o modelo começa a escrever o texto e começa a entrar em pensamento circular, sem nunca parar de gerar mais texto.</p>
<p>Isso com certeza é efeito do Lora e de algum erro no treinamento. Se eu tiver que chutar, é porque eu enfiei textos super longos no treinamento, sem indicações manuais de aonde acaba cada parte com um <code>&lt;|im_end|&gt;</code> da vida. Sem isso, talvez ele ache que o texto inteiro é uma resposta e ele tem que responder longo assim. Pelo que entendi, no caso desse treinamento de Zig, eu teria que decompor a documentação, o release notes e tudo mais em pedaços menores, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;|im_start|&gt;system
You are a helpful assistant.&lt;|im_end|&gt;
&lt;|im_start|&gt;user
What is Zig?&lt;|im_end|&gt;
&lt;|im_start|&gt;assistant
Zig is a compiled programming language...&lt;|im_end|&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E fazer isso pra todas as 200 mil caracteres de documentação. Imagina o trabalho pra fazer isso. E evitar enviar textos super longos em contexto de começa e onde acaba cada parte.</p>
<p>Pelo menos, esse é meu chute. O problema: mesmo se eu gastar tempo formatando assim, daí são mais 6 horas processando, pra só depois saber se fez alguma diferença. Provavemente não vai fazer, preciso pensar outra hipóteses, e cada vez são 6 a 12 horas entre setup e processamento.</p>
<p>A partir deste ponto é um trampo que eu não tenho motivação pra fazer. Estou deixando anotado aqui pra ver se isso chega na mão de pesquisadores de verdade. Talvez seja algo que um pesquisador de I.A. ache super óbvio. Mas este é o ponto: essa informação não existe fácil publicamente em lugar nenhum. Todo exemplo que se acha online são muito simples. Só falam <em>&ldquo;nesta linha de python você carrega o dataset, e nesta próxima linha começa o treinamento&rdquo;</em> - mas cadê as instruções de COMO fazer um dataset e COMO determinar se ela é adequada? Não tem.</p>
<p>Se alguém tiver dicas de como melhorar o treinamento ou quiser um problema de pesquisa na área, <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">este é o GitHub</a> com todos os códigos e material de treinamento que eu fiz. Está faltando pesquisadores olharem pra esse tipo de coisa e divulgar melhor o que sabem. Eu sei que alguém sabe, mas só sair fuçando no Google não me levou a lugar algum.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>performance</category></item><item><title>Acessando seu NAS usando iSCSI em vez de SMB</title><link>https://akitaonrails.github.io/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb/</guid><pubDate>Thu, 24 Apr 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;Se você tem um NAS ou qualquer tipo de servidor de arquivos na rede, provavelmente o padrão é criar uma pasta compartilhada e depois montar no seu sistema operacional, seja Windows, Mac ou Linux usando o velho, e cansado, protocolo SMB (Samba).&lt;/p&gt;
&lt;p&gt;Em Linux, isso é um enorme pé no saco, porque o protocolo SMB (feito pra Windows), não trás direito conceitos como ownership (chown) ou permissões (chmod). Ele não entende coisas como permissão de execução (tudo é executável). Pra coisas simples como diretório de Downloads, Videos, Fotos, meio que não importa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você tem um NAS ou qualquer tipo de servidor de arquivos na rede, provavelmente o padrão é criar uma pasta compartilhada e depois montar no seu sistema operacional, seja Windows, Mac ou Linux usando o velho, e cansado, protocolo SMB (Samba).</p>
<p>Em Linux, isso é um enorme pé no saco, porque o protocolo SMB (feito pra Windows), não trás direito conceitos como ownership (chown) ou permissões (chmod). Ele não entende coisas como permissão de execução (tudo é executável). Pra coisas simples como diretório de Downloads, Videos, Fotos, meio que não importa.</p>
<p>Mas digamos que queira editar código fonte em projetos com Git. Vai ser um pesadelo, porque o SMB sobrescreve as permissões, independente do que está por baixo, e toda vez vai conflitar com o Git, que vai achar que arquivos foram modificados (mudar permissões é uma mudança que ele guarda), e vai ficar mandando você fazer commit disso, e isso vai sujar todo seu repositório (nunca use projetos Git em SMB na rede).</p>
<p>Em vez disso, eu estou em Linux, meu NAS é Linux. O certo é usar um protocolo de Linux. Esse protocolo é NFS e eu mostrei como configurei isso no <a href="https://www.akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux"target="_blank" rel="noopener">outro post</a> da semana passada.</p>
<p>Enfim, estou usando NFS e com muito menos dor de cabeça de permissões em arquivos. Mas aí tive outro problema: se viu meus últimos posts, viu que fiquei experimentando MUITO com Docker. Gerando imagens novas como se não houvesse amanhã. Rapidamente, meu NVME local de 2 TB encheu e começou a reclamar de falta de espaço. Então pensei, <em>&ldquo;Ah, é só mover o diretório de armazenamento do Docker, o /var/lib/docker, pra um diretório montado com NFS no meu NAS&rdquo;</em></p>
<p>Pra fazer isso é muito fácil:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /mnt/nfs/docker
sudo chown root:docker /mnt/nfs/docker
sudo chmod 771       /mnt/nfs/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é pra criar um novo diretório dentro do meu NAS e aplicar as permissões corretas.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl stop docker
sudo mv /var/lib/docker /mnt/nfs/docker
# sudo rm -Rf /var/lib/docker (isso vai liberar tudo pra mim)
sudo ln -s /mnt/nfs/docker /var/lib/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai liberar todo o espaço ocupado. Não preciso me preocupar com as imagens porque eu posso recriar tudo dos meus Dockerfiles. E não preciso me preocupar com volumes porque não tinha nada importante que não seja montado por fora, em diretórios de verdade. Regra: nunca guarde coisas importantes em volumes de Docker. É uma má prática!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># editar /etc/docker/daemon.json
{
  &#34;data-root&#34;: &#34;/mnt/nfs/docker&#34;
}

sudo systemctl start docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, isso muda a configuração de <code>/var/lib/docker</code> pro novo <code>/mnt/nfs/docker</code> e depois de reiniciar o serviço, ele vai passar a gravar tudo lá. Então acabou o post né?</p>
<p>É até aqui que a maioria vai. Mas se você trabalhar 2 minutos nessa configuração, vai sentir algo MUITO errado.</p>
<p>Eu tentei fazer build de uma nova imagem. E estava demorando ABSURDO. Acontece que NFS é sim, mais rápido que SMB, mas ambos foram feitos pra servidores de arquivos, e não pra sistemas operacionais. Faz muita diferença.</p>
<p>Esses protocolos são <strong>FILE-BASED</strong>, toda operação é baseada em arquivos. E isso é extremamente ineficiente.</p>
<p>Se assistiu meus videos sobre sistemas de arquivos, já deveria saber que eles são <strong>BLOCK-LEVEL</strong>. No nível do sistema de arquivos, se trabalha com blocos de bytes, de tamanho fixo, organizadas em alguma variação de árvores B-TREE (depende do sistema de arquivos). As operações são em nível de bloco. Arquivos são abstrações pra coleções de blocos.</p>
<p>Quando se faz um <code>chmod -R</code> que é mudança de permissão recursiva, dentro do build do Dockerfile, por exemplo, mesmo uma árvore vazia vai causar recursão em cada entrada lá dentro, seja arquivos escondidos, ACLs, etc. E ele precisa pesquisar todas as permissões de tudo lá dentro. Isso custa tempo de rede (porque NFS/SMB são protocolos de rede).</p>
<p>Isso causa latência. Cada permissão de cada arquivo é uma chamada remota individual, uma RPC call. Mesmo na rede mais rápida, como a minha de 10Gbps, mesmo que cada RPC seja de menos de 0.1 ms, rapidamente vai dar gargalo. E é isso mesmo que começou a acontecer.</p>
<p>Eu usei <code>sudo nfsiostat 1 10</code> pra checar. Tava dando isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>write: ops/s=2417  kB/s≈295 MiB/s  avg exe=106 ms  avg queue=102 ms</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Média de 106 ms pra cada RPC. Isso é uma eternidade. Tempo médio na fila de espera de 102 ms. Mesmo que que eu faça só 100 chamadas RPC, vai custar mais de 10 segundos só de latência. E num build de sistema operacional, instalando pacotes com milhares de arquivos, isso vai custar literamente uma eternidade. Não é viável usar Docker build em NFS/SMB.</p>
<h3>iSCSI<span class="hx:absolute hx:-mt-20" id="iscsi"></span>
    <a href="#iscsi" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviamente eu não sou o primeiro a esbarrar nesse problema e fazer a besteira de usar um protocolo file-based pra serviços de alto volume de alterações de arquivos como cache de Docker.</p>
<p>O certo é criar um drive virtual iSCSI no meu NAS. Se nunca ouviu falar, SCSI é um protocolo avançado pra drives. Em PCs baratos dos anos 90 e 2000 se usava IDE/ATA, que é um protocolo mais simples e mais barato. Mas os primeiros Mac e workstations UNIX dos anos 80 e 90 sempre usaram SCSI, que era muito superior.</p>
<p>Ele era <strong>assíncrono</strong>, com controle de fila. Dava pra mandar dezenas de comandos de uma só vez e o protocolo era inteligente de reordenar esses comandos pra ter mais eficiência (por exemplo, ignorar leitura de um arquivo se antes veio um comando de deletar). IDE era bloqueante, só um comando por vez. Um único BUS de SCSI conseguia ter até 16 dispositivos. Parallel ATA era 2 por cabo. Ridículo. SCSI tinha sistemas avançados de recuperação de erros e relatórios. Tinha recursos &ldquo;enterprise&rdquo; pra hot-swap, gerenciamento de energia, comportamentos de timeout, recuperação, clustering, etc. E além de tudo era agnóstico ao meio de transporte, podia ser fibra ótica, SAS ou até mesmo iSCSI que é SCSI sobre Ethernet, que é o que vamos usar.</p>
<p>IDE/ATA não tinha nada disso. Era o protocolo de discos de PC pobre. Workstation de verdade, SUN, IRIX, Silicon Graphics, Macs, usavam SCSI.</p>
<p>Mais importante iSCSI é um protocolo de blocos, e não de arquivos. Enfim, isso varia de gerenciador de NAS, mas no caso do Synology DSM, que é o que eu uso, ele tem um aplicativo chamado SAN Manager, que permite criar um drive iSCSI:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fraxoryhuszmp4cmr0ldc8h7hiip" alt="iSCSI create"  loading="lazy" /></p>
<p>Só seguir o wizard, dar nome, escolher o tamanho, habilitar CHAP se precisar de segurança extra (eu não preciso, estou numa rede local controlada). Isso vai criar o drive iSCSI (pense como se fosse um drive USB remoto) e um LUN (Logical Unit Number). Um LUN é simplesmente um de muitos potenciais &ldquo;discos virtuais&rdquo; que um target pode apresentar. LUN é como um slot numerado no array de storage de um target. Target é um servidor de discos. Num SAN você pode ter vários discos virtuais e a forma de organizar isso é com LUNs. No nosso caso não interessa muito saber detalhes de LUN.</p>
<p>Demora um pouco, mas ao terminar é só voltar pro meu Manjaro Linux e fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S open-iscsi
sudo systemctl enable --now iscsi </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso sobe o serviço pra iniciar automaticamente em todo boot. Agora pra descobrir meu drive na rede:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code></code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m discovery -t sendtargets -p 192.168.0.xx
192.168.0.xx:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[2804:1b3:....:fe18:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[fe80::9209:....:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Todo drive virtual tem um <strong>IQN</strong> que é tipo uma &ldquo;URL&rdquo; pro drive. Agora fazemos login:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node --login
Login to [iface: default, target: iqn.2000-01.com.synology:TERACHAD.docker.7af6e9116c1, portal: 192.168.0.21,3260] successful.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como não habilitei CHAP nem pede senha nem nada (em empresa o certo é estar habilitado, lógico). E pronto. Ao fazer isso eu ouço no GNOME o barulhinho igual de quando você conecta um pen-drive no PC e ele já automaticamente aparece como um disco. Se fizer <code>lsblk</code>, vai aparecer como um drive normal <code>/dev/sdX</code> da vida. No aplicativo Disks do GNOME (ou no Disk Management do Windows) vai realmente aparecer como se fosse qualquer outro hard-drive ou SSD da vida. Ele não se comporta como uma pasta compartilhada e sim como um drive de verdade. Incluve precisa formatar, igual um pen drive novo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/nvgrl3bov311vl4h251dcj1wvchz" alt="Disks"  loading="lazy" /></p>
<p>A formatação em si, demora bastante, porque no processo ele precisa checar e mapear bloco a bloco. Como é pela rede, é mais lento do que num drive de verdade, claro. Mas é só uma vez. Mesmo na minha rede de 10Gbps ele vai formatar numa velocidade de uns 120 MB/s, num drive de 2TB, isso vai dar na faixa de 2 horas pra um fast format. Eu esqueci que dava pra formatar com &ldquo;lazy&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkfs.ext4 -v -E lazy_itable_init=1,lazy_journal_init=1 /dev/sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Fica pra próxima, agora é esperar 2 horas. Pra garantir que monte automaticamente no boot, precisa checar o <code>node.startup</code> dele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node -o show | grep -E &#39;Target:|node.startup&#39;

node.startup = manual
node.startup = manual
node.startup = manual</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Está como &ldquo;manual&rdquo;, pra trocar pra &ldquo;automatic&rdquo; precisa fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node \
  -T iqn.2000-01.com.synology:TERACHAD.docker.7af6... \
  -o update -n node.startup -v automatic</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com o daemon iSCSI de pé e depois do login, além de <code>/dev/sda</code> se fizer <code>ls /dev/disk/by-path</code> vamos encontrar algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6exxxxx-lun-1 -&gt; ../../sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como estou usando o serviço AUTOFS e ele &ldquo;rouba&rdquo; meu diretório <code>/mnt</code>, vou montar o drive iSCSI via fstab mesmo em <code>/media/docker</code> adicionando esta linha no final de <code>/etc/fstab</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
/dev/disk/by-path/ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6e9xxxx-lun-1  /media/docker  ext4      _netdev,nofail,x-systemd.automount,x-systemd.requires=iscsid.service,noatime,nodiratime,commit=60  0 2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois é só recarregar e montar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reload
sudo mount -a</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é isso. A partir de agora eu tenho um &ldquo;drive virtual&rdquo; no meu NAS remoto. Agora, vamos mudar o Docker pra começar a usar ele. Posso reconfigurar meu Docker pra fazer cache de imagens e volumes e tudo mais direto nesse drive. Só editar o arquivo <code>/etc/docker/daemon.json</code> com o mount point:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;data-root&#34;: &#34;/media/docker/docker&#34;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não esquecer de criar o diretório no novo drive com as permissões certas (e ele aceita permissões, porque é um drive formatado com ext4 como qualquer outro):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /media/docker/docker
sudo chown root:docker /media/docker/docker
sudo chmod 771 /media/docker/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, é só reiniciar o serviço do Docker, apagar o <code>/usr/lib/docker</code> pra liberar espaço no meu NVME principal e pronto. Deve ser MUITO mais rápido que NFS, não tão rápido quanto um disco local, claro. Mesmo com 10Gbps, rede introduz latência, não tem jeito. Mas o mais pesado é mesmo o processo de build, que tem muita escrita. Pra carregar uma imagem pronta, é pra ser super rápido. Mas pelo menos usando um protocolo de bloco em vez de protocolo de arquivos, é pra diminuir a latência numa ordem de pelo menos 10x ou mais.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xh40fu275xx9q6os00m6j5gyr9w9" alt="docker build"  loading="lazy" /></p>
<p>E é mais rápido mesmo? <strong>SIM</strong>. Nesta foto de tela do docker build rodando, com NFS os tempos à direita estavam sendo de CENTENAS DE SEGUNDOS pra vários comandos. Agora está de novo abaixo de 1 segundo como deveria. Pra todos os efeitos e propósitos, não estou sentindo diferença se fosse um HD externo USB 3.2 Gen 2.</p>
<p>A diferença MASSIVA que um drive virtual BLOCK-LEVEL faz, comparado com um protocolo NFS/SMB FILE-LEVEL é brutal, o protocolo é um overhead gigante. Aliás, é mais ou menos assim que funciona quando você vai na AWS e contrata storage EBS (Elastic BLOCK Store), por isso se chama &ldquo;BLOCK&rdquo;, porque é um protocolo de BLOCO como iSCSI e não NFS. Espero que tenha aprendido como faz diferença entender como protocolos e sistemas de arquivos funcionam. Se não assistiu meus vídeos, tem <a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;pp=gAQB"target="_blank" rel="noopener">esta playlist</a> que explica BLOCOS em detalhes.</p>
<p>Mas sim, iSCSI tem desvantagens: sendo um drive, não pode, ou não deveria, ser compartilhado (pense dois PCs compartilhando o mesmo pen drive, ia dar muita m&amp;rda). Block devices foram feitos pra serem usados por um único dispositivo. Justamente, pra compartilhar, é que existem protocolos como SMB ou NFS: pra gerenciar acessos concorrentes ao mesmo arquivo na rede.</p>
<p>Outra desvantagem: o drive virtual é um grande BLOB de bits. Sem &ldquo;montar&rdquo; não tem como ver os arquivos. No servidor NAS, ele não mostra os arquivos. É como um arquivo .vhd de Virtual Box. Precisa &ldquo;ligar&rdquo; e &ldquo;montar&rdquo; no &ldquo;PC&rdquo;. Por isso que ainda vou usar NFS pras coisas de sempre, como Videos ou Downloads. E o drive virtual é exclusivamente pro cache de Docker, neste caso. Aí funciona bem. Nenhum outro PC acessa. Se eu tivesse um segundo Linux que precisasse de cache de Docker, precisaria criar uma segunda LUN só pra ele. Lembre-se, é um drive. Virtual ou não, ele se comporta como um drive de verdade pro seu sistema.</p>
]]></content:encoded><category>homelab</category><category>armazenamento-e-backup</category><category>performance</category></item><item><title>Aumentando Resolução de Anime velho pra 4K com I.A.</title><link>https://akitaonrails.github.io/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a/</guid><pubDate>Sat, 19 Apr 2025 15:30:00 -0300</pubDate><description>&lt;p&gt;Mais de um ano atrás eu estava brincando com ErsGAN, redes adversariais generativas pra tarefa de fazer &amp;ldquo;upscaling&amp;rdquo; (aumentar resolução) de arquivos de animes velhos que eu tenho.&lt;/p&gt;
&lt;p&gt;Pra quem coleciona, o problema é que muito anime dos anos 90 pra trás nunca saíram e nem nunca vão sair em Blu-Ray (1080p) nem UHD (4K). Só os mais famosos recebem tratamento de &amp;ldquo;remaster&amp;rdquo; (pegar as fitas master originais e recapturar em mais resolução). Então muito anime velho está preso na era de DVD (480p) ou VHS (480i).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Mais de um ano atrás eu estava brincando com ErsGAN, redes adversariais generativas pra tarefa de fazer &ldquo;upscaling&rdquo; (aumentar resolução) de arquivos de animes velhos que eu tenho.</p>
<p>Pra quem coleciona, o problema é que muito anime dos anos 90 pra trás nunca saíram e nem nunca vão sair em Blu-Ray (1080p) nem UHD (4K). Só os mais famosos recebem tratamento de &ldquo;remaster&rdquo; (pegar as fitas master originais e recapturar em mais resolução). Então muito anime velho está preso na era de DVD (480p) ou VHS (480i).</p>
<p>Nem todo anime velho dá pra aumentar resolução, pode ficar &ldquo;lavado&rdquo; demais. Quem baixa torrent já deve ter visto upscalings mal feitos. Mas eu queria poder testar isso eu mesmo, na minha máquina local, com meus arquivos velhos. Eis um pequeno clip de exemplo:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4)
    </video>
</div>
<p>É um pedaço do 1o episódio do lendário Rurouni Kenshin de 1996. Sim, hoje temos um remake, que estão refazendo do zero. Mas pra quem tem nostalgia pelo original, não tem substituição. A qualidade de DVD não está horrenda, dá pra assistir de boa ainda hoje assim mesmo. Mas fico curioso pra ver como fica em 4K.</p>
<p>Não fiz nenhuma pesquisa longa, mas um ano atrás eu tinha esbarrado no <a href="https://github.com/xinntao/Real-ESRGAN"target="_blank" rel="noopener"><strong>Real ERSGAN</strong></a>. Esse projeto está descontinuado faz uns 4 anos, então não sei o que é o mais moderno hoje. Mas os modelos pré-treinados deles ainda são úteis.</p>
<p>Ainda tem projetos que usam esse modelo, o que eu conheço é o <a href="https://github.com/k4yt3x/video2x"target="_blank" rel="noopener"><strong>Video2K</strong></a> Em Windows parece que tem até uma interface gráfica bonitinha, mas a vantagem pra Linux é que ele roda em Docker. Um exemplo seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --rm --gpus all \
  -v &#34;$PWD/videos_in&#34;:/input \
  -v &#34;$PWD/videos_out&#34;:/output \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i /input/old_anime.mp4 -o /output/old_anime_4K.mkv \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se tiver com sua NVIDIA configurada com o Container Toolkit, Cuda e tudo mais (pergunte ao ChatGPT), o Docker consegue mapear o dispositivo direto pra dentro do container. Isso facilita muito não deixar meu Linux sujo cheio de dependências e configuraçõe soltas por aí. Não faço mais nada desse tipo fora de containers.</p>
<p>Mas eu esbarrei com um problema. Se você estiver usando GPU AMD, só assim já deve funcionar, porque essa imagem Docker na verdade usa Vulkan e não CUDA. No meu caso eu tenho uma GPU integrada AMD primária e uma NVIDIA RTX 4090 secundária. Eu tentei passar direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ docker run --gpus all --privileged \
  --device=/dev/nvidia0 \
  --device=/dev/nvidiactl \
  --device=/dev/nvidia-uvm \
  --device=/dev/nvidia-modeset \
  --runtime=nvidia \
  -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json \
  -e VK_LAYER_PATH=/usr/share/vulkan/explicit_layer.d \
  -v &#34;$HOME/Downloads/Video2K/videos_in&#34;:/input \
  -v &#34;$HOME/Downloads/Video2K/videos_out&#34;:/output \
  --rm -it \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i &#34;/input/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57).mkv&#34; \
  -o &#34;/output/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57)_upscaled.mkv&#34; \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas não vai de jeito nenhum. Se eu só disser &ldquo;&ndash;gpus all&rdquo; ele vai pegar a iGPU por padrão. Fazer o upscaling de um video acaba consumindo 100% da minha CPU, todas as 32 threads, mas minha GPU NVIDIA fica parada olhando sem fazer nada. Se eu tentar passar &ldquo;/dev/dri/card0&rdquo; ou &ldquo;/dev/nvidia0&rdquo;, ele dá erro e não consegue começar.</p>
<p>Então a partir daqui a solução é só pra quem tem NVIDIA:</p>
<h3>Dockerfile CUDA<span class="hx:absolute hx:-mt-20" id="dockerfile-cuda"></span>
    <a href="#dockerfile-cuda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Desta vez resolvi não ser preguiçoso e organizar tudo no projetinho no Github. Clone direto de lá <a href="https://github.com/akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA"target="_blank" rel="noopener">Batch Anime Upscaler Video2K Docker CUDA</a> Só fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone git@github.com:akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA.git
cd Batch-Anime-Upscaler-Video2K-Docker-CUDA
mkdir input # coloque seus videos velhos aqui
mkdir output

docker build --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -t anime-upscaler:latest .</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se nada der errado, isso vai construir uma imagem de Docker com tudo que você precisa, daí é colocar seus videos no subdiretório que falei, ou outro que quiser e configurar no comando pra rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> docker run --gpus all --rm \
  -v &#34;./input&#34;:/input \
  -v &#34;./output&#34;:/output \
  -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é só isso. Demora bastante. Com meu Ryzen 7950X3D mais RTX 4090, ele processa a uma taxa de uns 13 frames por segundo. Então menos da metade da velocidade de playback. Se um video tiver 20 min, vai levar mais de 40 minutos pra processar cada um. É bom reservar um bom tempo, e por isso também é bom só pra videos que você realmente queira assistir.</p>
<p>O script original do Video2K suporta vários parâmetros, que eu deixei expostos como variáveis de ambiente no Dockerfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>MODEL=&#34;${MODEL:-realesr-animevideov3}&#34;
SCALE=&#34;${SCALE:-4}&#34;
TILE=&#34;${TILE:-0}&#34;
DENOISE=&#34;${DENOISE:-1.0}&#34;
NUM_PROC=&#34;${NUM_PROC:-1}&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí dá pra mudar mexendo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --gpus all --rm \
  -e DENOISE=0.5 \
  -v ... \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como podem ver, por padrão usamos o modelo pré-treinado &ldquo;realesr-animevideov3&rdquo; que é menos detalhado que o modelo de 6 bilhões de parâmetro mas é bem mais estável entre frames, que é mais importante pra um video. A maioria dos outros modelos que ele suporta, como &ldquo;RealESRGAN_x4plus&rdquo; ou &ldquo;ESRGAN_x4&rdquo; servem pra fotos e imagens mas não pra video. Video exige coerência temporal, que é bem difícil.</p>
<p>No máximo vale testar o modelo &ldquo;realesr-general-x4v3&rdquo; que dizem ser um pouco mais leve, caso sua máquina esteja capengando muito pra rodar o padrão. Mas aí vai ter que modificar o Dockerfile pra fazer download desse modelo e re-buildar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># === OPTIONAL: Preload additional Real-ESRGAN models ===
# Uncomment any models you want available in the container at build time

# Anime 6-block model (better for anime stills, line art)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus_anime_6B.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth

# Original RealESRGAN 4x general-purpose model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus.pth

# RealESRGAN 2x model (lighter upscale)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x2plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x2plus.pth

# ESRNet (non-GAN model, ultra-smooth)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRNet_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRNet_x4plus.pth

# General-purpose lightweight video-friendly model (with denoise control)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.3.0/realesr-general-x4v3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-general-x4v3.pth

# Anime video model (default in your setup)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-animevideov3.pth

# Original ESRGAN (2018) model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/ESRGAN_x4.pth \
#     -O /opt/Real-ESRGAN/weights/ESRGAN_x4.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu não testei todos, mas pra quem quiser, acho que esses são os links. Mude no Dockerfile, faça o build, mude a variável MODEL e teste nos seus videos.</p>
<p>O que vale mexer são coisas como DENOISE, que está pra &ldquo;1.0&rdquo; que é máximo, mas isso pode deixar a imagem &ldquo;esticada&rdquo; demais. Vale testar valores menores, como &ldquo;0.5&rdquo;. Varia de caso a caso, só testando.</p>
<p>Outro fator é a escala, SCALE, que o default é &ldquo;4x&rdquo;, que transforma 1080p em 4K ou 720p em 1440p. Mas às vezes não precisa subir tanto, e nem adianta porque a imagem original já tem muito pouco detalhe mesmo. Talvez só 2x seja suficiente. O script suporta 2x, 3x, 4x mas tem teto na resoluçãod e 4K, então não adianta tentar transformar video que já é 4K em 8K por exemplo.</p>
<p>Minha RTX 4090 e meu Ryzen 9 são bem potentes. Processando só um video eu não vejo ele suando nem um pouco:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2dz1abq9b1wteho3r1onowmij14v" alt="btop"  loading="lazy" /></p>
<p>Claramente daria pra rodar mais videos em paralelo. Tem que tomar cuidado quando é desbalanceado. Se a CPU subir pra perto de 100% pra separar os frames, não adianta estar sobrando na GPU. Os dois tendo sobra, dá pra fazer o script subir mais de 1 processo na GPU e mandar frames em paralelo com a opção &ldquo;-e NUM_PROC=2&rdquo;. Olha como agora ele já usa mais da GPU e a CPU ainda tá sobrando. Talvez 3 seja o máximo, mas tem que testar, não é constante o tempo todo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qur7lw4ox5s3nymh025u6q446831" alt="btop x2"  loading="lazy" /></p>
<p>Isso pode diminuir o tempo total dramaticamente. Você pode escolher entre aumentar os processo na GPU pelo script ou ter dois diretórios separados de video e subir dois containers paralelos desse programa pra rodar ao mesmo tempo, o que funcionar melhor pra você.</p>
<h2>Resultado Final<span class="hx:absolute hx:-mt-20" id="resultado-final"></span>
    <a href="#resultado-final" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos ver como ficou aquele primeiro clip com upscale 4x e denoise 1.0:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4)
    </video>
</div>
<p>Dá uma diferença ENORME, mas se ficou &ldquo;BOM&rdquo; vai ser BEM subjetivo. Eu particularmente não achava o original ruim de assistir. Talvez se mexer no DENOISE pra um pouco menos ou SCALE pra 2x em vez de 4x já fique bom, não sei.</p>
<p>Pra comparar, eis outro clip na versão original antiga:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4)
    </video>
</div>
<p>E de novo, como fica depois do upscale 4x:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4)
    </video>
</div>
<p>Pelo menos está funcionando como deveria, a partir daqui é fazer algum tuning. Se alguém tiver mais dicas ou conhecer modelos ou projetos mais novos de upscaling open source, mande nos comentários abaixo!</p>
<p>Antes que alguém pergunte, porque não usar DLSS 3 da NVIDIA, que eles usam pra aumentar resolução de games? Resposta: porque eles não expõe essa funcionalidade como uma API que dá pra acessar, é só pros drivers proprietários deles em profile de jogo. Eles ligam e tunam caso a caso. Não tem API geral na lib CUDA.</p>
<h2>Update pós-publicação<span class="hx:absolute hx:-mt-20" id="update-pós-publicação"></span>
    <a href="#update-p%c3%b3s-publica%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Resolvi testar o modelo RealERSGAN_x4plus_anime_6B, que é bem mais pesado que o realesr-animevideov3 que subi no repositório.</p>
<p>Mude o Dockerfile e adicione isto depois do wget do outro modelo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth \
     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse modelo não tem como rodar em paralelo nem na minha RTX 4090, que fica constantemente em 100%. Se antes ia a uns 13 frames por segundo, com esse vai só a 3 frames por segundo. É 4x mais pesado e somente pra GPUs realmente parrudas. Se não tiver algo assim, fique na &ldquo;realesr-animevideov3&rdquo; mesmo ou no mais leve ainda (menos qualidade) &ldquo;realesr-general-x4v3&rdquo;. Esses modelos dependem muito da potência da GPU e principalmente, quantidade de VRAM.</p>
<p>Pra rodar adicione &ldquo;-e MODEL=RealESRGAN_x4plus_anime_6B&rdquo; no comando &ldquo;docker run&rdquo;.</p>
<p>Veja como ficou o mesmo clip de antes usando este modelo:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4)
    </video>
</div>
<p>Pra ter gasto 4x mais recursos da minha máquina pra processar, o resultado final eu não achei que ficou tão diferente, e em algumas partes eu acho que ficou mais defeitos até. Então é isso, o resultado vai variar, tem que testar. Depende muito da qualidade do anime original, mas não é porque existe um modelo maior e teoricamente &ldquo;melhor&rdquo; que todo resultado vai ficar melhor.</p>
]]></content:encoded><category>midia-generativa</category><category>performance</category><category>containers</category></item><item><title>Configuração da BIOS do meu PC - X670E Aorus Xtreme</title><link>https://akitaonrails.github.io/2025/04/17/configuracao-da-bios-do-meu-pc-x670e-aorus-xtreme/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/17/configuracao-da-bios-do-meu-pc-x670e-aorus-xtreme/</guid><pubDate>Thu, 17 Apr 2025 18:50:00 -0300</pubDate><description>&lt;p&gt;Esta é uma anotação pra mim mesmo, pra eu não esquecer e talvez ajude alguém.&lt;/p&gt;
&lt;p&gt;Eu sei que toda placa-mãe vem pré-configurada em &amp;ldquo;defaults&amp;rdquo; seguros e conservadores, o mais estável e não o mais performático. Então se não entrar na BIOS pra mexer, vai estar deixando performance na mesa.&lt;/p&gt;
&lt;p&gt;Eu tenho uma CPU AMD Ryzen 9 7950X3D. O processador é um pacote com um pacote que funciona do dois &amp;ldquo;dies&amp;rdquo;, não sei se é &amp;ldquo;chip&amp;rdquo; um jeito de visualizar. Cada um deles tem 8 cores, totalizando os 16 cores e cada core com 2 threads, então 32 threads possíveis em paralelo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esta é uma anotação pra mim mesmo, pra eu não esquecer e talvez ajude alguém.</p>
<p>Eu sei que toda placa-mãe vem pré-configurada em &ldquo;defaults&rdquo; seguros e conservadores, o mais estável e não o mais performático. Então se não entrar na BIOS pra mexer, vai estar deixando performance na mesa.</p>
<p>Eu tenho uma CPU AMD Ryzen 9 7950X3D. O processador é um pacote com um pacote que funciona do dois &ldquo;dies&rdquo;, não sei se é &ldquo;chip&rdquo; um jeito de visualizar. Cada um deles tem 8 cores, totalizando os 16 cores e cada core com 2 threads, então 32 threads possíveis em paralelo.</p>
<p>Mas tem uma diferença, cada die tem caches L3 diferentes. Um dos dies tem só 32MB e o outro tem 96MB, então programas mais pesados se beneficiam se rodarem nos cores do die que tem mais L3, e dá pra configurar isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gcbhrzj8wuc38z3lonisck0ooeck" alt="lstopo"  loading="lazy" /></p>
<p>Além disso ele tem clock base acho que de 3Ghz com capacidade de dar turbo boost até 5.7Ghz. Não pode tudo rodar nessa velocidade senão esquenta muito rápido e pode dar &ldquo;thermal throttle&rdquo; e aí ele derruba tudo pra baixo de 3Ghz. Por isso que, principalmente em notebooks que não tem capacidade térmica adequada, o melhor não é dar overclocking e sim &ldquo;undervolting&rdquo; (cortar a energia pra ir menos rápido). Porque é melhor todo mundo um pouco menos rápido do que todo mundo super rápido mas imediatamente super-aquecer e todo mundo ficar lento. É complexo isso.</p>
<p>Pra complicar mais eu tenho 96GB de RAM DDR5 6000. Capaz do máximo teórico de 6Ghz, mas isso é teórico porque pode dessincronizar tudo e causar instabilidades até crashear um kernel panic. Por isso o default da placa mãe é a velocidade base acho que de 4Ghz e CPU no máximo indo até 4Ghz mais ou menos também.</p>
<p>Mas dá pra subir isso um pouco sem deixar instável. Em resumo eu fiz o seguinte:</p>
<h2>PBO - Precision Boost Overdrive<span class="hx:absolute hx:-mt-20" id="pbo---precision-boost-overdrive"></span>
    <a href="#pbo---precision-boost-overdrive" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Os CPUs Ryzen tem PBO pra dar turbo boost no clock dos cores. Ele costuma vir ou desativado ou numa configuração conservadora, então seu CPU de 5.7Ghz pode estar operando só em 4Ghz ou menos e nunca entregando o máximo.</p>
<p>Pra ajustar isso, se quiser ser avançado, precisa ajustar parâmetros como PPT (Package Power Tracking) pra 200W ou mais. TDC (Thermal Design Current) pra 160A ou mais. EDC (Electrical Design Current) pra 200A ou mais. E pode configurar uma otimizador de curva, por core, pra negativo (-10 ou -20) pra reduzir voltagem, o que pode permitir clocks de boost mais altos.</p>
<p>Mas isso é muito avançado, e precisa saber bem mais de elétrica antes de
tentar na não. Melhor não arriscar e não mexer nisso. A tela a seguir, não deixe em Avançado e sim em Auto mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vxeurat4imakwzdl6artkzuqymmq" alt="PBO Advanced"  loading="lazy" /></p>
<p>Em vez disso é melhor mexer em PBO Enhancement Presets, que é essa tela abaixo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1vgigv3ka64xs6nclq9z2hh21w7" alt="PBO Presets"  loading="lazy" /></p>
<p><strong>70/80/90</strong> se refere à temperatura máxima que permitimos os cores de chegarem. Isso vai depender se você tem uma boa solução de resfriamento. Seja um AIO com resfriamento líquido ou um <a href="https://noctua.at/en/nh-d15-g2"target="_blank" rel="noopener"><strong>Noctua NH-D15 G2</strong></a> que é o que eu uso. Ventoinhas da Noctua são hiper silenciosas, eu quase nunca ouço e tem menos manutenção e possibilidade de falhas do que refriamento líquido. Eu prefiro menos peças móveis pra garantir.</p>
<p>[<img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4d10atf2k9rb5pntb9eec5uzqk7u" alt="Noctua NH-D15 G2"  loading="lazy" /></p>
<p>Agora esses <strong>Level 1 a 5</strong> são as tunagens de PPT/TDC/EDC que eu falei, mas já pré-definidas e testadas pelo fabricante. 1 é menos agressivo, o mais estável já 5 é o mais agressivo, pode ser instável. O certo é começar em <strong>90 level 5</strong> e ir diminuindo até achar o mais estável pro seu sistema. Isso vai variar, por exemplo, quando mais RAM tiver, menos agressivo parece que pode ser, especialmente quanto mais rápido essa RAM for.</p>
<p>No meu caso, logo no boot do meu Manjaro Linux, já deu kernel panic, que se parece com algo assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f25kqct9urmereleunhurknw40d4" alt="kernel panic"  loading="lazy" /></p>
<p>Então voltei pra BIOS e fui testando diferentes níveis e, pra minha configuração, consegui chegar só até <strong>90 Level 2</strong>. Esse tem sido estável e até agora não tive nenhum crash aleatório.</p>
<p>Além disso, recentemente (começo de 2025) fabricantes de placa-mãe soltaram atualizações de firmware que abriram acesso a uma funcionalidade chamada <strong>X3D Turbo Mode</strong>. Se você ver isso, não pense que é mais performance grátis. Na realidade, a menos que use a máquina mais pra games, vai sentir até que ela ficou mais lenta.</p>
<p>Como expliquei antes, Ryzen 9 tem 1 CCD (core complex), que é o die com 96MB de cache L3 (V-Cache). Turbo mode tenta forçar mais programas pro CCD que <strong>NÃO</strong> tem V-Cache (o de 32MB), e que por isso consegue ter um boost maior de clock mas tem mais latência. Isso é bom pra games - que normalmente não se beneficiam de ter mais cache e sim mais clock.</p>
<p>Mas ao mesmo tempo, programas que precisam do V-Cache vão acabar caindo mais vezes no Core com <strong>MENOS</strong> cache L3 e aí vão performar pior que antes. Exemplos disso são programas de modelagem 3D como Blender, justamente o que eu quero usar mais agora. Por isso teste, mas em princípio pode deixar desligado.</p>
<p>Essa opção no meu firmware se chama &ldquo;X3D Turbo Mode&rdquo; mas em outros lugares pode ser &ldquo;3D V-Cache Performance Optimizer&rdquo;, é a mesma coisa.</p>
<h2>XMP/EXPO RAM<span class="hx:absolute hx:-mt-20" id="xmpexpo-ram"></span>
    <a href="#xmpexpo-ram" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora, a RAM DDR5 boota no clock base de uns 4.8Ghz. Mas dependendo do modelo da RAM ela pode ir muito mais, no meu caso pra 6Ghz. Toda memória moderna também tem diversas configurações como a frequência, latência, voltagem e mais. De novo, não vale a pena tentar parametrizar isso na mão. É melhor usar perfis pré-definidos (profiles). A Intel chama esse suporte de <strong>XMP</strong> e a AMD chama de <strong>EXPO</strong>.</p>
<p>Esqueci de tirar foto da minha BIOS, mas essa foto do Google Images um pouco mais antiga é a mesma coisa. XMP/EXPO vem desligado (conservador) mas é só escolher o profile que já existe:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ypwo0rsbr1bmu6w6ujm7b1qyjlof" alt="XMP/EXPO Profile"  loading="lazy" /></p>
<p>Note que logo embaixo tem coisas como <strong>Low Latency Support</strong> e <strong>High Bandwidth Support</strong>. Teoricamente pra diminuir a latência e aumentar a banda de transferência. Tudo que soa muito bom, mas no meu caso não funciona. Se eu tento ligar esses dois, meu sistema não dá boot. Fui obrigado a limpar a CMOS pra reiniciar a BIOS sem isso.</p>
<p>O problema: possivelmente muita memória (96GB). Talvez se você tiver menos, 32GB, por aí, isso funcione, mas parece que quanto mais se tem, mais instável fica se tenta ir rápido demais.</p>
<p>Outro problema, em DDR5, quanto mais memória, mais lento é o Cold Boot, o boot inicial quando o PC estava desligado. A BIOS precisa fazer uma checagem nas memórias pra garantir esses parâmetros, tempo de resposta e tudo mais. E isso acontece em <strong>TODO</strong> boot. Pode levar alguns minutos e alguns podem achar até que a máquina travou, mas é só essa checagem.</p>
<p>Na BIOS tem uma opção chamada <strong>Memory Context Restore</strong> onde ele usa os parâmetros que gravou da última vez que fez essa checagem. Mas não recomendo, pra mim inclusive nem boota mais depois de habilitar isso. Teoricamente ele iria cortar o tempo de checagem e reusar dados antigos, mas esses dados podem não bater (os valores podem flutuar) e isso torna o sistema todo instável. Melhor esperar um minuto a mais no boot do que ficar com o sistema instável.</p>
<p>Em configurações avançadas de memória tem mais uma coisa que devemos mexer: <strong>DF C-States</strong>, que precisa estar <strong>DESABILITADO</strong>. Em teoria essa funcionalidade é pra reduzir a latência entre CPU e memória. Mas em sistemas com muita RAM (96GB ou mais como a minha) isso também pode causar instabilidade a custo de não muito ganho notável. Prefiro ficar estável nesse caso.</p>
<p>Outra configuração pra mudar é <strong>Power Supply Idle Control</strong> tem que mudar pra &ldquo;Typical Current Idle&rdquo;, e até agora (pode surgir um update de correção da firmware), é mandatório pra processadores Ryzen (mesmo AM5). isso previne estado de dormir &ldquo;Deep Power State&rdquo;, o que pode causar kernel panics aleatórios.</p>
<p>Mesma coisa pra <strong>Global C-State Control</strong> que também precisa desabilitar pelo mesmo motivo: evitar &ldquo;deep sleep states&rdquo; (C6/C10) quando a CPU fica em &ldquo;idle&rdquo; (sem fazer nada) por muito tempo. Essas funcionalidades existem pra economizar energia, mas parece que AMD tem bugs quando a CPU é forçada a esse estado e quando acorda, volta instável ou crasheando. Então ele vai consumir mais energia quando não tá fazendo nada (por volta de menos de 20W, em vez de perto de zero), mas pelo menos não vai ficar instável. Se quiser economizar energia, melhor é dar shut-down e desligar a força mesmo, quando não estiver usando.</p>
<h2>Resumo da BIOS (pra mim)<span class="hx:absolute hx:-mt-20" id="resumo-da-bios-pra-mim"></span>
    <a href="#resumo-da-bios-pra-mim" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>PBO Enhancement: <strong>90 Level 2</strong> (clock boost um pouco mais agressivo que o default)</li>
<li>Power Supply Idle Control: <strong>Typical Current Idle</strong> (prevenir o bug de crash em idle)</li>
<li>Global C-State Control: <strong>Desabilitado</strong> (evita dormir profundo, causando instabilidade)</li>
<li>DF C-States: <strong>Desabilitado</strong> (também evita dormir, melhorando latência da Infinity Fabric)</li>
<li>XMP/EXPO: <strong>Profile XMP1</strong> se tiver (aumenta clock da memória)</li>
</ul>
<p>Tem 2 coisas importantes, especialmente se estiver usando GPUs discretas:</p>
<ul>
<li>Above 4G Decoding e Re-Size Bar Support. No meu caso ambas estão ligadas por padrão, se na sua estiver desligada, ligue.</li>
</ul>
<p>No Linux, dá pra checar se está ligada. Rode o seguinte:</p>
<p><code>sudo lspci -vv | grep -A 15 VGA</code></p>
<p>Tem que aparecer &ldquo;Resizable BAR&rdquo; assim:</p>
<p><code>Capabilities: [XX] PCI Express, ... Resizable BAR: Supported (Enabled)</code></p>
<p>Se não aparecer, vá pra BIOS e procure pra habilitar. Sem isso você não está usando o máximo da sua placa de video. Acho que em iGPUs (que são GPUs que já vem embutida junto com a CPU) aí não tem essa opção mesmo.</p>
<h2>Kernel Linux-Zen<span class="hx:absolute hx:-mt-20" id="kernel-linux-zen"></span>
    <a href="#kernel-linux-zen" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em sempre estive usando a kernel padrão LTS mais estável, agora era Linux66, se não me engano. Mas lembrei que posso usar a kernel Zen, que é a mesma kernel padrão do Arch com tunings de performance pra melhorar latência, especialmente em multi-thread. Ele muda pro scheduler CFS com melhorias. Teoricamente, melhora a responsividade geral do sistema. Acho que vale a pena usar. No Arch/Manjaro é assim:</p>
<p><code>yay -S linux-zen-versioned-bin linux-zen-versioned-headers-bin</code></p>
<p>Isso vai desinstalar a kernel antiga e, se estiver usando GPUs NVIDIA, também vai desinstalar o pacote de drivers. Por isso precisa instalar o seguinte pacote também:</p>
<p><code>yay -S nvidia-dkms</code></p>
<p>E depois, com tudo instalado, rodar o seguinte comando:</p>
<p><code>sudo dkms autoinstall</code></p>
<p>Isso vai instalar de novo os binários dos drivers da nvidia pro novo kernel Zen. No caso do Manjaro, não precisa mexer no GRUB porque no meu caso ele está configurado pra <code>GRUB_DEFAULT=saved</code> em <code>/etc/default/grub</code>. Isso significa que quando reboota, ele vai gravar qual kernel eu escolhi por último, e no próximo reboot, se eu não escolher nada, vai carregar de novo o último kernel que eu escolhi. Prefiro assim do que deixar &ldquo;hard-coded&rdquo; qual kernel é o padrão.</p>
<p>Feito isso, no próximo boot só checar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ uname -r
6.14.2-zen1-1-zen</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Outra coisa é checar se <strong>CPPC</strong> está ativo. Isso é <strong>Collaborative Processor Performance Control</strong> que é uma funcionalidade que expõe pra kernel do OS um ranking de cores (qual tem boost mais alto, quem tem mais cache, etc). Lembra o X3D Turbo Mode que falei antes? Mesmo conceito: pra facilitar o OS escolher pra qual core mandar qual programa pra rodar melhor.</p>
<p>Nem todo firmware de placa-mãe expõe isso como uma opção, no caso do meu X670E da Gigabyte, ele não existe. Significa que provavelmente já vem ligado por padrão. Pra checar se está ligado, basta ir no terminal do Linux e rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_highest_perf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se voltar algum resultado, é porque está ligado e o firmware está informando pra kernel sobre as características de cada core, assim o scheduler tem mais informação pra gerenciar pra onde mandar cada thread.</p>
<p>Outra forma de checar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ls /sys/devices/system/cpu/cpu0/cpufreq/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Precisa retornar uma lista contendo nomes como estes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>amd_pstate_highest_perf
amd_pstate_nominal_perf
amd_pstate_preferred_core</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Que, novamente, é sinal que o Linux está reconhecendo CPPC direito.</p>
<p>O próprio Manjaro, além das kernels estáveis, tem as versões &ldquo;linux-rt&rdquo; que é pra sistemas real-time. Tecnicamente, não só é pra ter latência mais baixa, mas sim latências estáveis e mais previsíveis. <strong>NÃO USE ESSE TIPO DE KERNEL</strong>. Isso é pra sistemas de fábrica ou coisas assim que precisam de determinismo do processamento e não tem tolerância pra latência aleatória. Não significa que vai rodar mais responsivo, pode até ser que rode mais lento, mas o importante é ser um lento previsível.</p>
<p>Outros podem falar da kernel <strong>Linux Liquorix</strong>, que muitos comparam com a Zen. Mas enquanto a Zen é baseada no próprio kernel que o Arch usa, o do Liquorix é baseada na versão de Debian/Ubuntu. Então não é recomendado usar no Arch/Manjaro. Além disso ele é mais experimental e teoricamente menos estável também. Use se estiver num Ubuntu ou derivados da vida. Em Arch, melhor ficar no Zen.</p>
<h2>check_ryzen_perf.sh<span class="hx:absolute hx:-mt-20" id="check_ryzen_perfsh"></span>
    <a href="#check_ryzen_perfsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se tiver um sistema Ryzen, copie o seguinte script e rode na sua máquina:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🔍 Verifying Ryzen 7950X3D Tuning on Linux&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPU Driver: &#34;</span>
</span></span><span class="line"><span class="cl">grep . /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ EPP Mode: &#34;</span>
</span></span><span class="line"><span class="cl">cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPPC Detected: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_highest_perf <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Preferred Core Ranking: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_prefcore_ranking <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Running Kernel: &#34;</span>
</span></span><span class="line"><span class="cl">uname -r
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\n✅ All key Linux-side Ryzen tuning options appear active.\n&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Na minha máquina ele agora dá este resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>🔍 Verifying Ryzen 7950X3D Tuning on Linux
✅ CPU Driver: amd-pstate-epp
✅ EPP Mode: performance
✅ CPPC Detected: Yes
✅ Preferred Core Ranking: Yes
✅ Running Kernel: 6.14.2-zen1-1-zen

✅ All key Linux-side Ryzen tuning options appear active.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dizendo que, teoricamente, tudo de performance parece estar ativo e funcionando. Caso um deles não esteja, pergunte ao ChatGPT o que fazer pra sua configuração.</p>
<h2>Checar versão da BIOS<span class="hx:absolute hx:-mt-20" id="checar-versão-da-bios"></span>
    <a href="#checar-vers%c3%a3o-da-bios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muitos fabricantes de placa-mãe oferecem software, pra Windows, pra automaticamente baixar e atualizar o firmware. Mas no caso da Gigabyte, não tem suporte pra Linux. Pra checar que versão está instalada agora, sem precisar entrar na BIOS, no Linux tem este comando:</p>
<p><code>sudo dmidecode -s bios-version</code></p>
<p>No meu caso, agora volta F33 (versão de março de 2025). Agora, pra comparar se essa é a versão mais nova, só indo manualmente na porcaria do site da Gigabyte que, no meu caso, é <a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support"target="_blank" rel="noopener">este link</a>. Pro seu modelo específico, e fabricante, lógico, esse link vai variar.</p>
<p><a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-1x/support#support-dl"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/72l1if05q22u1ogowfzws9rtz95j" alt="x670e support page"  loading="lazy" /></a></p>
<p>Pra piorar, parece que o conteúdo é carregado assincronamente por javascript, então se tentar usar <code>wget</code>ou <code>curl</code> pela linha de comando, não vai ter carregado o conteúdo ainda.</p>
<p>Resolvi fazer um script idiota pra resolver isso. Primeiro, caso não use <code>mise</code> como eu (veja meu projeto no Github <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener">Omakub-MJ</a> pra ter um setup igual o meu), instale Node manualmente assim:</p>
<p><code>sudo pacman -S nodejs npm</code></p>
<p>Crie algum diretório pro projeto e de lá faça:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>npm init -y
npm install -g playwright
npx playwright install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crie um arquivo <code>check_bios_playwright.js</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">chromium</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;playwright&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">chromium</span><span class="p">.</span><span class="nx">launch</span><span class="p">({</span> <span class="nx">headless</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load support page
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s1">&#39;domcontentloaded&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Click the BIOS tab manually (it triggers JS to fetch content)
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;id=bios-count&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Wait for BIOS version element to load
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60000</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Grab the first BIOS version listed
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">biosVersion</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;🌐 Latest BIOS Version on Gigabyte site:&#39;</span><span class="p">,</span> <span class="nx">biosVersion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora crie um script bash <code>check_bios_update.sh</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get directory of this script (absolute path)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the Playwright JS script in the same directory</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span>node <span class="s2">&#34;</span><span class="nv">$SCRIPT_DIR</span><span class="s2">/check_bios_playwright.js&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🖥  Current BIOS: </span><span class="nv">$CURRENT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🌐 Latest BIOS:  </span><span class="nv">$LATEST</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ You are up to date.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;⬆  BIOS update available!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Faça um <code>chmod +x</code> pra tornar executável e, se tudo der certo, terá uma resposta como esta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[sudo] password for akitaonrails:
🖥  Current BIOS: F33
🌐 Latest BIOS:  F33
✅ You are up to date.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É um tiro de canhão pra matar uma mosca, mas isso vai usar o Chromium headless pra puxar a página, carregar o javascript e aí usar seletores de CSS pra achar a versão da BIOS, comparar com o da sua máquina e ver se está atualizado.</p>
<p>Caso não esteja, não tem jeito, tem que ir manualmente no site, baixar o arquivo ZIP, extrair num pen drive e bootar na BIOS pra atualizar manualmente. Mas pelo menos consigo checar rapidamente de tempos em tempos só com uma linha de comando agora.</p>
<h2>Checagem automática de versão de BIOS<span class="hx:absolute hx:-mt-20" id="checagem-automática-de-versão-de-bios"></span>
    <a href="#checagem-autom%c3%a1tica-de-vers%c3%a3o-de-bios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Resolvi que não quero lembrar rodar esse script manualmente, então a melhor coisa é criar um serviço systemd de usuário, e checar automaticamente toda semana. Se tiver versão nova, criar uma notificação ao GNOME pra eu poder ver visualmente e automaticamente mandar abrir a página de download pra mim.</p>
<p>Primeiro, preciso fazer meu usuário ter acesso ao comando dmidecode pra checar a versão atual da minha BIOS sem precisar digitar senha com <code>sudo</code>, pra isso vamos criar uma nova regra de sudoers assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo visudo -f /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vai abrir um arquivo vazio onde preencho assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo /usr/sbin/dmidecode -s bios-version</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E não esquecer de colocar a permissão correta nesse arquivo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod 0440 /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso não vai mais pedir senha pra rodar esse comando, já que vamos colocar num serviço em background rodando automaticamente. Obviamente não é seguro permitir rodar <code>sudo</code> sem senha, por isso limitamos exclusivamente pra permitir isso somente nesse binário específico e nada mais.</p>
<p>No mesmo diretórios dos outros arquivos com playwright instalado e tudo mais, crio um <code>bios-update-check.sh</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Path to your Playwright BIOS scraper script</span>
</span></span><span class="line"><span class="cl"><span class="nv">JS_CHECKER</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/bios-check/check_bios_playwright.js&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_BIN</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>which node<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># URL of your motherboard&#39;s BIOS support page</span>
</span></span><span class="line"><span class="cl"><span class="nv">URL</span><span class="o">=</span><span class="s2">&#34;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support#support-dl-bios&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get latest BIOS version from Gigabyte site</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span><span class="nv">$NODE_BIN</span> <span class="s2">&#34;</span><span class="nv">$JS_CHECKER</span><span class="s2">&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send <span class="s2">&#34;BIOS Check&#34;</span> <span class="s2">&#34;⚠️ Failed to fetch latest BIOS version from Gigabyte&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send -u normal <span class="s2">&#34;🔔 BIOS Update Available!&#34;</span> <span class="s2">&#34;Current: </span><span class="nv">$CURRENT</span><span class="s2"> → Latest: </span><span class="nv">$LATEST</span><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Opening the download page...&#34;</span> --app-name<span class="o">=</span><span class="s2">&#34;BIOS Checker&#34;</span>
</span></span><span class="line"><span class="cl">  xdg-open <span class="s2">&#34;</span><span class="nv">$URL</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É a mesma coisa do script anterior mas agora usando <code>notify-send</code> pra mandar mensagens pro Gnome (em KDE é diferente) e usando <code>xdg-open</code> pra abrir a URL no navegador default do sistema. Configure diferente pro seu sistema. Não esquecer de tornar executável e testar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>chmod &#43;x ~/.local/bin/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora é criar o serviço systemd pro meu usuário (diferente de criar um pra sistema que roda com mais permissões. Só preciso disso rodando no nível do meu usuário), então crio o arquivo <code>~/.config/systemd/user/bios-check.service</code> com o seguinte conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Weekly BIOS version checker

[Service]
Type=oneshot
ExecStart=/home/akitaonrails/.local/bin/bios-check/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora o serviço de timer pra rodar toda semana chamado <code>~/.config/systemd/user/bios-check.timer</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Run BIOS version check weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=default.target</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E habilitar o timer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user daemon-reexec
systemctl --user daemon-reload
systemctl --user enable --now bios-check.timer</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por convenção no Systemd, um timer [blabla].timer vai executar o serviço com mesmo nome [blabla].service. Por isso precisa dos dois. Se você habilitar o serviço direto, ele vai rodar só uma vez e sair. Pra saber se tá ativado certo faça o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>✦ ❯ systemctl --user list-timers

NEXT                          LEFT LAST PASSED UNIT             ACTIVATES
Mon 2025-04-21 00:00:00 -03 2 days -         - bios-check.timer bios-check.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que você pode automatizar algumas coisas no seu sistema e aproveitar seu Gnome melhor.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Só comecei essa jornada desta vez porque me ocorreu que fazia meses que eu não atualizava o firmware da placa-mãe e aí aproveitei pra tunar algumas coisas que não tinha feito antes. Coisas como PBO estava no automático (então eu estava deixando performance pra trás, lembro que da última vez tinha ficado instável mas eu não fui atrás pra saber porque), coisas como XMP/EXPO já tava ligado. Mas agora eu acho que realmente liguei tudo que podia.</p>
<p>Também fazia anos que eu não experimentava a kernel Linux-Zen, então já aproveitei pra testar agora. E como falei, por enquanto tá estável. Como comecei a aprender Blender e puxar mais CPU e memória, achei que valia a pena gastar um tempo tentando extrair o máximo da minha máquina. Modelagem 3D é totalmente CPU/RAM, mais que GPU.</p>
<p>Ah sim, falando em Blender, uma última coisa. Lembra que meu Ryzen tem 1 CCD, que é aquele 1 die com 96MB de cache L3. Melhor coisa a fazer é criar afinidade do Blender com esse die e evitar que ele caia no core com só 32MB de cache L3.</p>
<p>No Linux, pra fazer isso, é esta linha de comando:</p>
<p><code>taskset -c 0-15 blender-4.3 &amp;</code></p>
<p>O comando taskset vai dar dica pro scheduler que o blender deve ficar nas threads de 0 a 15 (onde tem V-Cache) e não de 16 a 31 (que só tem 32MB). Dessa forma, em teoria, estou dando as maiores chances ao Blender de funcionar na melhor performance possível nesta máquina. Programas pouco importantes como o navegador, terminal, se cair no core de 32MB, não tem problema.</p>
<p>Eu faço a mesma coisa no libvirt/qemu quando rodo Windows emulado: eu configuro o libvirt pra subir a VM atrelado ao CCD de 96MB e deixo o outro core pro Linux host. Aliás, falando em BIOS, pra rodar VMs tem que habilitar SVM e IOMMU, que costumam vir desligados porque suporte a máquinas virtuais rouba um pouco da performance da CPU. Máxima performance é com eles <strong>desligados</strong>. Se você não vai usar VMs, deixe desligado.</p>
<p>E se alguém notou que estou rodando explicitamente o Blender 4.3 em vez do mais novo Blender 4.4 é porque alguns addons ainda não funcionam na versão mais nova, então é melhor ficar uma versão pra trás pra ter mais compatibilidade com addons que não atualizam rápido.</p>
]]></content:encoded><category>hardware</category><category>performance</category><category>linux</category></item><item><title>[Akitando] #149 - Configurando Docker Compose, Postgres, com Testes de Carga - Parte Final da Rinha de Backend</title><link>https://akitaonrails.github.io/2023/12/16/akitando-149-configurando-docker-compose-postgres-com-testes-de-carga-parte-final-da-rinha-de-backend/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/12/16/akitando-149-configurando-docker-compose-postgres-com-testes-de-carga-parte-final-da-rinha-de-backend/</guid><pubDate>Sat, 16 Dec 2023 09:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/-yGHG3pnHLg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não podia terminar o ano com pendências, então eis a parte final da Saga da Rinha de Backend. Neste video vou aproveitar os temas da rinha pra demonstrar em mais detalhes como configurar um Docker Compose de verdade, como funciona testes de carga com Gatling, como usar esses dados pra configurar coisas como o Postgres melhor. Também vamos ver como avaliar exatamente quanto pesa rodar SQL num Postgres, como determinar coisas com tamanho de pool de conexões e muito mais!&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/-yGHG3pnHLg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Não podia terminar o ano com pendências, então eis a parte final da Saga da Rinha de Backend. Neste video vou aproveitar os temas da rinha pra demonstrar em mais detalhes como configurar um Docker Compose de verdade, como funciona testes de carga com Gatling, como usar esses dados pra configurar coisas como o Postgres melhor. Também vamos ver como avaliar exatamente quanto pesa rodar SQL num Postgres, como determinar coisas com tamanho de pool de conexões e muito mais!</p>
<p>Hoje é dia de aprender ferramentas que todo programador deveria mesmo saber no dia a dia de projetos de verdade.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:01:03 - CAP 01 - Minhas Pendências da Rinha - Como Começar?</li>
<li>00:03:28 - CAP 02 - Entendendo Docker Compose - PHP do Lauro</li>
<li>00:07:38 - CAP 03 - Aprendendo a Lidar com Poucos Recursos - Configurando NGINX</li>
<li>00:15:33 - CAP 04 - Gargalos de Docker - Network Mode: Host</li>
<li>00:21:41 - CAP 05 - Prefira Docker em Linux - Problemas em Mac e Windows?</li>
<li>00:26:51 - CAP 06 - Algumas Dicas de Docker Compose - Node do Lucas Weis</li>
<li>00:30:21 - CAP 07 - Básico de Testes de Carga - Gatling</li>
<li>00:44:26 - CAP 08 - Comparando com Network Mode HOST - Faz diferença??</li>
<li>00:52:53 - CAP 09 - Entendendo PostgreSQL - Banco era Gargalo??</li>
<li>01:01:12 - CAP 10 - Full Text Search no PostgreSQL - Faz diferença??</li>
<li>01:07:35 - CAP 11 - Estratégia de SQL com EXPLAIN ANALYZE - SQL por baixo dos panos</li>
<li>01:16:11 - CAP 12 - Quantas Conexões Configuro no Banco? - Medindo com pgAdmin</li>
<li>01:23:34 - CAP 13 - Discutindo sobre Linguagens - Kotlin e Crystal</li>
<li>01:31:05 - CAP 15 - Como Linguagens afetam Recursos do Banco? - Só a saber, sobre threads vs fibers</li>
<li>01:35:00 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3"target="_blank" rel="noopener">Repositório Oficial da Rinha</a></li>
<li><a href="https://github.com/lukas8219/rinha-be-2023-q3"target="_blank" rel="noopener">GitHub Versão Node.js do Lucas Poole</a></li>
<li><a href="https://github.com/lauroappelt/rinha-de-backend-2023"target="_blank" rel="noopener">GitHub Versão PHP do Lauro Appelt</a></li>
<li><a href="https://github.com/akitaonrails/rinhabackend-rails-api"target="_blank" rel="noopener">GitHub - akitaonrails/rinhabackend-rails-api</a></li>
<li><a href="https://docs.docker.com/network/drivers/host/"target="_blank" rel="noopener">Docker Network Mode Host</a></li>
<li><a href="https://gatling.io/docs/gatling/tutorials/installation/"target="_blank" rel="noopener">Gatling</a></li>
<li><a href="https://github.com/tsenart/vegeta"target="_blank" rel="noopener">Vegeta</a></li>
<li><a href="https://www.haproxy.org/"target="_blank" rel="noopener">HAProxy - The Reliable, High Perf. TCP/HTTP Load Balancer</a></li>
<li><a href="https://www.envoyproxy.io/"target="_blank" rel="noopener">Envoy proxy - home</a></li>
<li><a href="https://caddyserver.com/docs/quick-starts/reverse-proxy"target="_blank" rel="noopener">Reverse proxy quick-start — Caddy Documentation (caddyserver.com)</a></li>
<li><a href="https://github.com/jesseduffield/lazydocker"target="_blank" rel="noopener">GitHub - jesseduffield/lazydocker: The lazier way to manage everything docker</a></li>
<li><a href="https://www.commandprompt.com/education/how-to-insert-bulk-data-in-postgresql/"target="_blank" rel="noopener">How to Insert Bulk Data in PostgreSQL - CommandPrompt Inc.</a></li>
<li><a href="https://www.pgadmin.org/"target="_blank" rel="noopener">pgAdmin - PostgreSQL Tools</a></li>
<li><a href="https://start.spring.io/"target="_blank" rel="noopener">Spring Initializer</a></li>
<li><a href="https://kotlinlang.org/"target="_blank" rel="noopener">Kotlin Programming Language (kotlinlang.org)</a></li>
<li><a href="https://crystal-lang.org/"target="_blank" rel="noopener">The Crystal Programming Language (crystal-lang.org)</a></li>
<li><a href="https://luckyframework.org/"target="_blank" rel="noopener">Lucky - Web framework for Crystal (luckyframework.org)</a></li>
<li><a href="https://aws.amazon.com/pt/what-is/elk-stack/"target="_blank" rel="noopener">O que é a pilha ELK? | Explicação sobre a pilha Elasticsearch, Logstash, Kibana | AWS (amazon.com)</a></li>
<li><a href="https://aws.amazon.com/pt/what-is/elasticsearch/"target="_blank" rel="noopener">O que é Elasticsearch – Explicação sobre o mecanismo Elasticsearch – AWS (amazon.com)</a></li>
<li><a href="https://aws.amazon.com/elasticache/"target="_blank" rel="noopener">Redis and Memcached-Compatible Cache – Amazon ElastiCache – Amazon Web Services</a></li>
<li><a href="https://aws.amazon.com/pt/msk/"target="_blank" rel="noopener">Apache Kafka totalmente gerenciado | Amazon MSK | Amazon Web Services</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PostgreSQL.Replication.ReadReplicas.html"target="_blank" rel="noopener">Working with read replicas for Amazon RDS for PostgreSQL - Amazon Relational Database Service</a></li>
<li><a href="https://stackoverflow.blog/2020/10/14/improve-database-performance-with-connection-pooling/"target="_blank" rel="noopener">Improve database performance with connection pooling - Stack Overflow</a></li>
<li><a href="https://brandur.org/postgres-atomicity"target="_blank" rel="noopener">How Postgres Makes Transactions Atomic (brandur.org)</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Antes de ir pro assunto de hoje, só confirmando que sim, o último video foi um experimento de fazer um episódio totalmente gerado por IA. Maioria sacou, mas muitos demoraram bastante ou nem se tocaram. Mas não se preocupem, foi só uma vez pra ver como fica. Não é nem um pouco prático fazer videos via ferramentas de IA como o HeyGen. Eu expliquei o processo na descrição do video, que vocês sempre pulam. Então leiam lá.</p>
<p>Assunto de hoje, finalmente, vai ser a prometida Parte 2 da Rinha de Backend. Sim, o video das 16 Linguagens em 16 dias foi lançado em Setembro e eu prometi uma parte 2. Estamos em dezembro, quase virando o ano e só agora estou soltando. Mas é assim que este canal funciona: me deu vontade de falar de outras coisas antes.</p>
<p>Objetivo deste video é complementar algumas dicas e informações que podem auxiliar nos seus estudos. Em particular algumas das técnicas que diferenciam projetinho de tutorial de projeto de verdade. Então, vamos lá &hellip;</p>
<p>(&hellip;)</p>
<p>Um dos motivos de porque demorei tanto pra fazer esse episódio é por causa destes dois itens que eu tinha colocado pra falar neste episódio: Resolvendo a Rinha em 5 minutos e O que é um framework web? Esses dois itens seriam um video inteiro de 2 horas e sinceramente, não estou com paciência pra esse tema porque já tem documentação suficiente online. Abra o site de qualquer framework web, seria o primeiro tutorial de como usa. Eu só estaria repetindo o que já existe online. Em vez disso vou só resumir alguns pontos principais.</p>
<p>Resolvendo a Rinha em 5 minutos. Sim, eu poderia fazer um speed run e editar um video demonstrando passo a passo como fazer uma versão das APIs. Eu acho inútil. Speed run de código só pra mostrar que sei fazer, realmente não me motiva. É óbvio que eu sei fazer. Flex desnecessário. Aliás, o video de 16 linguagens em 16 dias já foi flex o suficiente.</p>
<p>Se você é iniciante e tem dificuldade de resolver esse exercício, obviamente ainda falta estudar mais e treinar mais. O que eu sempre repito? Não existe uma única forma de resolver um problema de código. Não existe um único passo a passo de tutorial ou curso que consiga cobrir todas as permutações. O que vocês tem que fazer? Abrir o GitHub do evento e ir no diretório de participantes. Por isso achei este evento interessante: pela quantidade de versões diferentes que os participantes publicaram pra resolver o mesmo problema.</p>
<p>Vou dar um exemplo. Fiz clone do repositório principal da rinha na minha máquina. Peguem o link na descrição abaixo. Indo no diretório de participantes, temos 94 versões. Eu analisei a maioria delas no outro video, depois assistam lá. Mas digamos que esteja estudando PHP e queira ver como se resolve esse problema em PHP. A partir do diretório de participantes, só abrir um terminal e digitar <code>grep &quot;php&quot; */README.*</code> e olha só, aparecem 4 sub-diretórios, dois do Lauro Appelt e dois de um tal de Predator. No video anterior eu expliquei uma das versões do Lauro, que é feito em Hyperf com Swoole.</p>
<p>Vamos abrir o README de um dos diretórios e olha só, temos o link do repositório com o código em PHP do Lauro. Podemos clonar em qualquer outro lugar e fuçar pra ver como foi que ele implementou a versão dele. A parte mais interessante desse evento, é que um dos requerimentos foi pra todo participante criar Dockerfiles e Docker Compose pra tudo. Então é super fácil de executar, sem precisar adivinhar como configurar o ambiente pra cada projeto.</p>
<p>Hoje em dia eu considero de boa educação, boa etiqueta, que todo projeto, não importa quão pequeno ou quão grande, sempre deve ter um Dockerfile e um docker-compose. O Dockerfile demonstra como fazer pra rodar sua aplicação e o docker compose demonstra como subir as dependências desse projeto, como banco de dados. Vamos olhar o Dockerfile do projeto do Lauro.</p>
<p>Primeiro, ele inicia baixando uma imagem preparada pelo pessoal do framework Hyperf, que já trás tudo que você vai precisar. Em seguida, faz algumas configurações de timezone. Maioria das imagens boota com tempo UTC de Greenwich, timezone zero, como padrão. Pra ter os horários corretos da sua região geográfica, precisa colocar algo como &ldquo;America/Sao_Paulo&rdquo;, sempre. Ninguém vai adivinhar.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
FROM hyperf/hyperf:8.2-alpine-v3.18-swoole

ARG timezone

ENV TIMEZONE=${timezone:-&#34;America/Sao_Paulo&#34;} \
    APP_ENV=prod \
    SCAN_CACHEABLE=(true)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse trecho seguinte grandão aqui é opcional e eu não testei o suficiente pra saber se era ou não necessário pra melhorar os tempos do benchmark. Tudo que ele está fazendo é criando um arquivo de variáveis de configuração, pra mexer em configurações como tamanho máximo de upload, que é desnecessário neste projeto, ou limite de memória, que talvez possa ser útil ou não, não medi pra saber.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
RUN set -ex \

    &amp;&amp; php -v \
    &amp;&amp; php -m \
    &amp;&amp; php --ri swoole \

    &amp;&amp; cd /etc/php* \

    &amp;&amp; { \
        echo &#34;upload_max_filesize=128M&#34;; \
        echo &#34;post_max_size=128M&#34;; \
        echo &#34;memory_limit=1G&#34;; \
        echo &#34;date.timezone=${TIMEZONE}&#34;; \
    } | tee conf.d/99_overrides.ini \

    &amp;&amp; ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
    &amp;&amp; echo &#34;${TIMEZONE}&#34; &gt; /etc/timezone \

    &amp;&amp; rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
    &amp;&amp; echo -e &#34;\033[42;37m Build Completed :).\033[0m\n&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não saia copiando e colando achando que vai automaticamente melhorar alguma coisa. Vá na documentação e entenda item a item daqui. Daí rode uma versão sem isso, e outra versão com isso, rodando o mesmo benchmark, daí compare se faz alguma diferença. O fato de coisas que não importam pra este desafio, como tamanho máximo de upload, me diz que o Lauro copiou o Dockerfile de exemplo no site da imagem desse Hyperf, e colou aqui.</p>
<p>Finalizando, adicionamos drivers de postgres pra PHP, mudamos o diretório de trabalho pra /app, que é pra onde vamos copiar os arquivos do projeto na sequência. O que eu achei estranho é que não tem um último comando de ENTRYPOINT indicando como rodar a aplicação, também senti falta de rodar &ldquo;composer install&rdquo; pra baixar as bibliotecas. Isso é uma peculiaridade desta versão do Lauro, guardem isso que já já explico porque tá faltando.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RUN set -ex \
    &amp;&amp; apk --no-cache add \
    &amp;&amp; apk add php82-pdo_pgsql

WORKDIR /app

COPY . /app</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vamos ver o docker-compose.yaml. Docker compose é a descrição de uma mini-infraestrutura. Do que instalar, configurar e subir Redis, Postgres, Nginx, tudo na mão, podemos automatizar tudo num único comando. É muito mais fácil pra um novo desenvolvedor que entra no seu projeto, aprender como as coisas funcionam, se tiver um docker compose já pronto. Funciona com uma documentação viva da infra do projeto. É de bom tom, todo projeto de verdade, ter docker compose configurado que funciona.</p>
<p>Nunca fez um docker compose na vida? Como faz? Simples, começa pegando qualquer um dos 90 e tantos exemplos que tem neste evento como base. Vamos abrir o do Lauro e ir de baixo pra cima. Olha só, já sabemos que esse projeto depende de Redis estar de pé. Esse nome &ldquo;redis&rdquo; na primeira linha é qualquer nome que quiser. Por convenção colocamos o nome do que vamos rodar, mas poderia ser &ldquo;cache_server&rdquo; ou algo assim.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>    redis:
        container_name: rinha-backend-redis
        image: redis:latest
        command: redis-server --save &#34;&#34; --appendonly no --maxclients 20000
        network_mode: host
        deploy:
            resources:
                limits:
                    cpus: &#39;0.1&#39;
                    memory: &#39;0.2GB&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A única linha obrigatória mesmo é o nome da imagem. Docker, por padrão, vai baixar tudo de dockerio.com. Se quiser baixar de outro lugar, tem que colocar a URL inteira, senão só o nome e a versão da imagem é suficiente. Aqui está &ldquo;latest&rdquo;, que é a versão mais recente. Não gosto de usar latest porque se amanhã mudar a versão, e sua app não foi adaptada e teve mudanças que quebram compatibilidade, seus desenvolvedores vão começar a ter problemas. Se forem muito júniors, não vão associar que foi porque mudou a versão do redis e por isso as coisas quebraram do nada. É melhor colocar a versão fixa, como 7.2.3-alpine3.19, que significa Redis versão 7.2.3 rodando em cima da distro Alpine versão 3.19.</p>
<p>As outras opções são específicas da Rinha, em particular os limites de recursos na seção de deploy. Esse foi um dos critérios, que achei inteligente. Tínhamos no máximo 1.5 CPUs e 3 Gigabytes de RAM pra usar. À primeira vista, parece muito pouco. Não é fácil de imaginar como esses recursos encaixam em cada componente. Se você é um iniciante e tem uma máquina fraca, já aprendeu a ter que lidar com isso: como faz pra tirar o máximo proveito do seu Dual Core com 2 gigabytes de RAM? Todo programador que ficou bom, começou tendo que fazer rodar programas pesados em máquinas fracas.</p>
<p>Se você é um iniciante e já começou com oportunidade de usar máquinas novas e parrudas pra trabalhar, qualquer coisa acima de um quad core, acima de 8 gigabytes de RAM, tipo um Macbook da vida, e não teve que lidar com máquinas fracas, cuidado. Você vai pode acabar ficando complacente, preguiçoso. Vai achar que o mundo é igual sua bolha: que tem CPU sobrando, que tem RAM sobrando, e nunca vai entender o valor de otimizar as coisas. Pra que otimizar? Se tá tudo sobrando? Na hora que ficar difícil, você vai ser o tipo que desiste fácil.</p>
<p>Por isso eu repito que na minha teoria de Aprendendo a Aprender, tem que ter a Dor de Aprender. Não importa se você cai, importa como se levanta. Não importa quantas vezes cai, importa que toda vez se levanta mais rápido. Quem nunca sentiu dor, nunca aprendeu nada. E nesta rinha, a versão disso é essa limitação de recursos, senão, claro, o participante com uma máquina como a minha, com 64 giga de RAM, Ryzen 9 de 32 threads, facilmente conseguiria gerar resultados com números muito melhores que todo mundo. Mas com esse limite, o participante com um quad core de 4 giga de RAM poderia ultrapassar minha máquina, se soubesse administrar melhor os recursos limitados.</p>
<p>E isso de limitar recursos no Docker, não foi feito pra concursos. Foi feito pro caso de alguém realmente precisar rodar dois processos numa mesma máquina, e caso um deles seja fominha demais e consuma recursos demais, não deixar o outro processo morrendo de fome. Se o servidor tem 4 giga de RAM, eu subo dois programas, cada um no seu container de docker. Digamos que o primeiro resolva alocar toda a memória disponível, mesmo que não vá usar. Daí quando o segundo container tentar rodar, vai crashear, por falta de recursos.</p>
<p>Existem processos que alocam mais memória do que deveriam? Sim, por exemplo, um Node.js tem recurso de cluster, que eu mencionei no episódio anterior. O que muita gente faz? Vai no Google pesquisar e só copia um trecho parecido com esse de algum stackoverflow da vida. Esse script roda &ldquo;os.cpus.length&rdquo; e vê que sua máquina tem 4 cores, ou seja, 4 cpus, então faz 4 forks da aplicação. Cada fork pode ocupar, digamos, meio gigabyte. Então 4 forks daria 2 gigabytes. Como sua máquina tem 8 gigabytes, você nem se preocupa. Funciona tudo direitinho.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">numForks</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;os&#39;</span><span class="p">).</span><span class="nx">cpus</span><span class="p">().</span><span class="nx">length</span> <span class="o">||</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="nx">cluster</span><span class="p">.</span><span class="nx">isPrimary</span> <span class="o">&amp;&amp;</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CLUSTER</span> <span class="o">===</span> <span class="s1">&#39;true&#39;</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">    <span class="nx">logger</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`index.js: Primary </span><span class="si">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">pid</span><span class="si">}</span><span class="sb"> is running`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">numForks</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cluster</span><span class="p">.</span><span class="nx">fork</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aí você aloca um servidor num cloud qualquer, com 8 giga de RAM. Mas resolve pegar um CPU potente, de 16 cores. Seu script de cluster vai ver 16 cpus e subir 16 forks. Cada fork ocupando meio giga de RAM. Ou seja, vai ocupar todos os 8 giga de uma só vez. E você não tem 8 giga sobrando, seu sistema operacional e outros serviços ocupam RAM. Alguns desses forks vão crashear logo no boot.</p>
<p>O certo é consertar esse script e trocar a chamada de os.cpus e usar uma variável de ambiente no lugar. Assim podemos declarar essa variável, como CLUSTER_WORKERS, no yaml do docker compose, e ter controle sobre quantos processos realmente vai subir. Como desenvolvedor você sempre precisa ter controle sobre o que exatamente vai rodar, quantos recursos vai usar. Um bom jeito de se exercitar é acertando script de docker compose.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">numForks</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CLUSTER_WORKERS</span><span class="p">)</span> <span class="o">||</span> <span class="mi">4</span><span class="p">;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Continuando, depois do redis tempos nginx. Esse foi outro pré-requisito nas instruções da rinha: ter um proxy reverso pra servir de balanceador de carga entre, no mínimo,  duas instâncias da sua aplicação. Quando um estudante segue um tutorial de web qualquer, ele só entende que tem um único processo, o servidor da aplicação. Sobe numa porta como 4000 e no navegador coloca localhost:4000 e pronto, acabou.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>    nginx:
        container_name: rinha-backend-nginx
        image: nginx:alpine
        volumes:
            - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
        network_mode: host
        depends_on:
            - api-01
            - api-02
        deploy:
            resources:
                limits:
                    cpus: &#39;0.1&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é o nível zero no aprendizado. O nível logo acima desse hiper básico, tem que aprender que em projetos de verdade, subimos múltiplos processos em paralelo, seja na mesma máquina, seja em múltiplas máquinas. Se for na mesma máquina, um sobe na porta 3000, outro na porta 3001, outro na 3002. Em qualquer dos casos, sempre colocamos um balanceador de cargas na frente, na forma de um proxy reverso. Explico isso no episódio &ldquo;Tornando sua Web App Mais Rápida&rdquo;.</p>
<p>Alguns mais experientes poderiam criticar a escolha de NGINX, sendo que existem ou programas mais especializados pra isso como HAProxy ou mais modernos como o Envoy ou Caddy. Mas de jeito nenhum é uma escolha ruim, especialmente porque é um dos que tem mais documentação, é simples de usar, e pra esta rinha, está mais que bom. Nesta seção de docker compose temos duas coisas importantes, a primeira é a imagem. No caso estamos usando a versão de Alpine, que é uma das mais leves. Mais importante é o mapeamento de volume.</p>
<p>Precisamos mapear o arquivo de configuração que fica no nosso projeto do lado de fora, para que o container tenha acesso quando bootar. Lembrem-se, um container é só uma forma de mentir pro processo que roda dentro, pra ele achar que está sozinho, numa máquina vazia. Ele não enxerga nenhum arquivo de fora se nós não declararmos pra dentro, da seguinte forma.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>volumes:
	- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que tem nesse arquivo de configuração? Vamos dar uma olhada. Se nunca viu, parece complicado, mas vamos por partes. Esta primeira parte do topo tem a ver com a escalabilidade do serviço. Nginx funciona numa arquitetura de master e workers. Esse &ldquo;worker_processes&rdquo; como &ldquo;auto&rdquo; de automático quer dizer que ele vai avaliar quantas cpus sua máquina tem e subir 1.5 vezes. Então, se tiver 4 cpus, ele vai subir 6 workers.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">worker_processes</span> <span class="k">auto</span><span class="p">;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por que não 1 worker por 1 cpu? Primeiro, uma cpu não é exclusiva de 1 processo. Quem controla qual processo vai rodar em qual cpu durante um período x de tempo, é o scheduler da kernel. Se seu processo é leve e precisa de pouco tempo pra fazer o que precisa, não tem porque bloquear uma cpu inteira só pra ele. Dá pra dividir com outros processos. Depois assista meus episódios sobre concorrência e paralelismo pra entender como schedulers funcionam.</p>
<p>Em seguida temos worker_connections como 4096. Isso indica quantas conexões simultâneas cada worker vai tentar aguentar. Não confunda conexões simultâneas com processamento paralelo. Se realmente vier 4096 requisições web, exatamente no mesmo instante, esse parâmetro só significa que o worker vai aceitar as 4096 conexões, mas uma boa parte vai ficar esperando numa fila, até ser atendido. Se demorar mais do que certo tempo, que podemos configurar, ele pode rejeitar e devolver erro de timeout. Como o nome diz, erro de tempo expirado.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">events</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">worker_connections</span> <span class="mi">4096</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No primeiro episódio que fiz da rinha eu expliquei como muitos ficaram tentando mexer nesse número meio que no chute. Só o Leandro, que fez a versão em Ruby, entendeu que o truque não era aumentar esse número e sim diminuir. Eu explico o raciocínio no outro video, então não deixem de assistir depois.</p>
<p>O Nginx é só o tirador de pedidos, lembram da analogia do caixa de restaurante fast food? Ele tira os pedidos, mas quem realmente executa, os cozinheiros, são estes servidores declarados nesta seção upstream. No caso apontando pra localhost, portas 9501 e 9502. Se já mexeu com docker compose antes, isso poderia parecer um pouco estranho.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">http</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">access_log</span> <span class="n">off</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">error_log</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">null</span> <span class="n">emerg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">upstream</span> <span class="n">api</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">server</span> <span class="nl">localhost</span><span class="p">:</span><span class="mi">9501</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">server</span> <span class="nl">localhost</span><span class="p">:</span><span class="mi">9502</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">keepalive</span> <span class="mi">400</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Normalmente, pra subir um nginx via docker compose, eu teria uma configuração mais ou menos assim: declararia dois serviços web, as instâncias da minha aplicação, como app1 e app2. Ambos carregando na porta 8080. Daí, na configuração do nginx, essa seção upstream estaria apontando pra app1 porta 8080 e app2 também porta 8080.</p>
<p>Mas como que dá pra ter dois serviços subindo na mesma porta? Não tem conflito? E não tem, porque na prática é como se cada container representasse um servidor externo separado. E sua aplicação está carreagando num servidor fora, só que virtual. Daí, cada servidor tem uma porta 8080 livre. Cada instância pode se pendurar na porta 8080 do seu respectivo &ldquo;servidor virtual&rdquo;. Acontece que o nginx e esses servidores virtuais se comunicam na mesma rede, também virtual, daí o nginx consegue fazer o balanceamento entre esses dois serviços.</p>
<p>No episódio anterior eu expliquei a descoberta que alguns de nós fizemos durante nossos experimentos, o Vinicius Ferraz fez a descoberta de que se usarmos o modo de rede de ponte virtual, que é o padrão, isso introduzia um gargalo pesado e os resultados dos benchmarks ficavam bem errados. Mas se colocarmos o modo de rede pra host, ou seja, sem virtualizar a rede, aí os tempos finalmente ficam corretos.</p>
<p>Não sei se notaram, mas na configuração do redis que mostrei antes, ou nesse de nginx, note que ambos já tem a opção network_mode igual a host, indicando que é pra pendurar o container nas portas da rede de verdade do sistema operacional nativo. Nesse modo não podemos pensar que temos &ldquo;servidores virtuais separados&rdquo; mas sim que estamos subindo realmente tudo na mesma máquina, na rede de verdade. Portanto, precisamos nos preocupar com conflitos de portas. Por isso no upstream do nginx, apontando pra dois servidores em localhost, cada um deles precisa estar numa porta diferente, 9501 e 9502.</p>
<p>É muito importante entender o que é modo de rede ponte e a diferença com modo de rede host. Estudem se não sabiam disso. Mas já que falamos do servidor de aplicação. Note que na configuração do nginx, tem esse seção de depends_on. Ele quer dizer: só suba o serviço nginx quando detectar que os serviços api-01 e api-02 estejam de pé. Então vamos pular pro começo do arquivo do docker compose, pra ver como essa aplicação está configurada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>services:

    #api1
    api-01:
        container_name: rinha-backend-api-01
        build:
            context: .
            dockerfile: Dockerfile
        environment:
            HTTP_PORT: 9501
            APP_NAME: skeleton
            APP_ENV: dev
            DB_DRIVER: pgsql
            DB_HOST: localhost
            DB_PORT: 5432
            DB_DATABASE: rinha-backend-db
            DB_USERNAME: rinha
            DB_PASSWORD: postgres
            REDIS_HOST: localhost
            REDIS_PORT: 6379
        volumes:
            - ./:/app
        depends_on:
            - db
            - redis
        command: bash -c &#34;php bin/hyperf.php start&#34;
        network_mode: host
        deploy:
            resources:
                limits:
                    cpus: &#39;0.4&#39;
                    memory: &#39;0.3GB&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Precisamos dar um jeito de configurar a mesma aplicação duas vezes, como api-01 e depois como api-02 e, como estamos usando modo de rede host, precisamos declarar portas direrentes pra cada uma, que normalmente fazemos via variáveis de ambiente, como esse HTTP_PORT logo no começo. O jeito mais fácil é configurando esse bloco de configuração do serviço api-01 e depois fazendo um copy e paste embaixo pra ser o api-02, só mudando o HTTP_PORT pra 9502.</p>
<p>Existem outras formas de fazer isso. Como exemplo, que pegar o docker compose do projeto de Node.js do Lucas Poole, porque ele ainda está usando o modo de rede padrão, via ponte virtual, daí não precisou se preocupar em configurar portas diferentes, só precisou fazer hostnames diferentes. Vamos dar uma olhada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>services:
  app1: &amp;app
    # image: lukas8219/rinha-be-2023-q3:latest
    build: .
    hostname: app1
    environment:
      DB_URL: &#34;postgres://postgres:12345678@postgres:5432/postgres&#34;
      DB_POOL: 35
      PINO_LOG_LEVEL: &#34;debug&#34;
      REQ_TIMEOUT: &#34;2000&#34;
      NODE_ENV: production
      CLUSTER: true
      CLUSTER_WORKERS: 5
    expose:
      - &#34;8080&#34;
    depends_on:
      - postgres
    ulimits:
      nproc: 1000000
      nofile:
        soft: 1000000
        hard: 1000000
    deploy:
      resources:
        limits:
          cpus: &#39;0.5&#39;
          memory: &#39;0.6GB&#39;
  app2:
    &lt;&lt;: *app
    hostname: app2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele declarou o serviço de nome &ldquo;app1&rdquo; e em seguida anexou uma etiqueta com esse &amp; comercial, chamando de &ldquo;app&rdquo;. Descendo pro próximo serviço no mesmo arquivo, observem, temos app2. E na linha seguinte é como se fosse um &ldquo;include&rdquo; ou &ldquo;import&rdquo;, dizendo pra copiar tudo que tinha no app1. A única coisa que muda é o nome do hostname. Entenderam? É assim que podemos reusar as mesmas configurações entre diferentes serviços. Isso é uma características de arquivos de configuração em formato YAML, não é coisa do docker compose. Qualquer outro programa que use YAML, dá pra fazer a mesma coisa.</p>
<p>Como falei antes, o projeto do Lucas não está usando o modo de rede host e sim o de ponte, por isso na configuração do app2, ele mantém o mesmo número de porta. Se fosse no modo host, precisaria declarar pelo menos um número de porta diferente, pra não conflitar no localhost. Se nunca viu isso antes, sei que pode ser confuso, mas se lembre dessa diferença entre ponte de rede virtual e rede host nativa.</p>
<p>A parte mais importante na configuração da nossa aplicação de API é este parâmetro &ldquo;build&rdquo;. Aqui está mais completo do que precisa. Bastaria ter escrito o valor &ldquo;.&rdquo;, mas o Lucas preferiu declarar o contexto como &ldquo;ponto&rdquo; e o arquivo como sendo &ldquo;Dockerfile&rdquo; mesmo, que é o padrão.</p>
<p>É aqui que ele vai usar o arquivo de Dockerfile que já vimos antes, pra construir a imagem pra este container. Todos os outros serviços, nós apontamos pra imagens já prontas no serviço do site Docker Hub. Aquelas imagens foram todas feitas com algum Dockerfile também. A diferença é que nossa aplicação não foi publicada no Docker Hub, estamos construindo ela aqui, então apontamos direto pro Dockerfile. No futuro, quando a aplicação estiver terminada, podemos dar upload da última imagem gerada e passar a apontar pra ela via parâmetro image em vez de build. No arquivo do Lucas, tem até comentado essa opção, porque pra participar da rinha, precisava ter publicado a imagem de verdade.</p>
<p>O Dockerfile do Lauro, que mostrei antes, usa de base a imagem do repositório do Hyperf e também incluímos coisas como o PDO de Postgres. Todos esses elementos podem ser configurados via variáveis de ambiente, que é o que estamos declarando na seção &ldquo;environment&rdquo;, como o HTTP_PORT, que indica em qual porta o servidor de Hyperf vai subir, ou DB_HOST ou REDIS_HOST que declara o nome do servidor que vai ter o banco de dados e o cache. Neste caso, em modo de rede host, vai ser tudo localhost.</p>
<p>No projeto do Lauro, quando fizemos as otimizações de performance que expliquei no outro episódio, ele fez alguns ajustes depois e preferiu não deixar tudo 100% automatizado. Lembram que falei que no Dockerfile dele estava faltando o comando ENTRYPOINT, que define que executável iniciar no container? Pra saber o que fazer, tem que ler o README dele. Ao subir o Docker Compose, os containers vão estar de pé, mas internamente quem vai rodar é este comando de &ldquo;sleep infinity&rdquo;, que é um truque só pro container ficar de pé e não morrer.</p>
<p>A razão disso foi porque ele não adicionou os próximos passos no Dockerfile, não sei por que, mas o README pede pra rodar manualmente, via &ldquo;docker exec&rdquo; o comando de &ldquo;composer install&rdquo; pra instalar as dependências. Como ele mapeia o diretório inteiro como &ldquo;/app&rdquo; pra dentro do container, se não me engano, &ldquo;composer install&rdquo; vai baixar e gravar as dependências no sub-diretório &ldquo;/app/vendor&rdquo;. Por isso tanto faz rodar em qualquer um dos dois containers, o outro vai enxergar automaticamente, já que compartilham do mesmo volume no disco.</p>
<p>Só depois de fazer isso que, via docker exec de novo, podemos rodar o hyperf em cada container, e vai subir direitinho. Durante desenvolvimento, dependências como bibliotecas de node, ou neste caso, do composer, não queremos deixar dentro da imagem, porque ocupam muito espaço e mudam o tempo todo. Seria trabalhoso toda vez reconstruir a imagem. O melhor é mapear um volume pra dentro do container e mandar instalar nesse volume. Só quando for gerar a imagem final pra publicar, aí sim, era bom mudar pra gravar as dependências dentro da imagem. Senão quem for baixar depois, vai precisar fazer isso manualmente, o que não é ideal.</p>
<p>Feito tudo isso, do terminal, basta subir tudo com &ldquo;docker compose up&rdquo;. Isso vai baixar as imagens que ainda não tem, construir essa da aplicação e subir tudo certinho, na ordem certa, graças aos depends_on que todos os serviços declaram. Olhem só, da primeira vez demora um pouquinho pra subir. Vou acelerar aqui um pouquinho e &hellip; pronto. Como expliquei antes, só pra esse projeto do Lauro, temos que rodar o composer install manualmente, aceleramos mais um pouco e &hellip; pronto. Finalmente, podemos subir o Hyperf em cada um dos containers.</p>
<p>Pra testar que tudo deu certo, podemos consultar o endpoint da API de contagem de pessoas, que é usado pelo script de benchmark pra mostrar quantas pessoas foram inseridas no teste de carga. Basta chamar do próprio terminal com &ldquo;curl http://localhost:9999/contagem-pessoas&rdquo;. E.. deu pau. O que aconteceu?</p>
<p>Se estiver rodando em alguma distro Linux, ia retornar bonitinho. Mas se estiver rodando em Mac ou Windows, como neste meu caso, via WSL 2, aquela configuração de network mode host não vai funcionar. Por que?</p>
<p>Se não entendem como um container funciona por baixo, não deixe de assistir o episódio sobre containers. Em resumo, Docker precisa da kernel Linux pra funcionar. Docker não é uma máquina virtual, é uma infraestrutura que pede pra kernel mentir pros processos, fazendo eles acreditarem que estão rodando sozinhos na máquina.</p>
<p>No caso de Mac, o problema é que Macs não rodam kernel de Linux. Apesar de rodarem programas que parecem de Linux, na realidade são todos recompilados pra serem compatíveis com a versão Darwin do BSD UNIX. Macs são UNIX e não Linux. Eu já contei essa história no episódio sobre as diferenças de Mac, Windows e Linux. Programas de Linux não rodam nativamente em Macs, todos precisam ser no mínimo recompilados. Mac e Linux são binariamente incompatíveis.</p>
<p>Windows, obviamente, também não é uma distro Linux. A kernel do Windows é fundamentalmente diferente de Linux e Macs. Mas hoje em dia existe o WSL 2, que é basicamente uma máquina virtual Linux super leve, rodando por cima do Windows usando a infraestrutura de hypervisor do Hyper-V. Essa é a única solução pra Macs e Windows: rodar Linux numa máquina virtual e, dentro dela, rodar Docker.</p>
<p>Tanto no caso de Mac, quanto Windows, uma das opções mais populares é baixar o pacote Docker Desktop, que vai instalar a máquina virtual pro Docker e uma interface gráfica, que é opcional, pra facilitar gerenciar os diversos elementos de Docker, como redes virtuais, imagens e containers. E aqui está o problema de porque não funciona localhost:9999 em modo de rede host. Segundo a própria documentação: o driver de rede modo host só funciona em Linux, rodando na VM do Docker Desktop, o container não vai receber nenhum endereço IP e a publicação de portas vai ser ignorada, tornando o container inacessível.</p>
<p>Quer dizer que Docker Desktop de Windows e Mac são uma droga, então? Recapitulando, porque estávamos usando modo de rede host? Porque no cenário de teste de carga, o modo padrão gera gargalo, então mudamos pro modo host. Isso é só pra situação de teste de carga. Pro dia a dia, enquanto você está programando, em situações normais, não vai sentir diferença nenhuma. Nesse caso, não precisa usar esse modo host e deixar tudo no padrão. No modo ponte virtual, Docker de Mac e Windows vai funcionar direitinho.</p>
<p>No caso do WSL 2, você pode instalar o Docker ou Podman dentro do WSL em vez de usar uma VM separada, como o Docker Desktop faz.</p>
<p>Falando de Docker Desktop, uma pequena tangente, se você quiser muito uma interface gráfica pra gerenciar imagens, containers e tudo mais, experimente outra opção: o Portainer, que por si só é uma aplicação que roda em container Docker, então é trivial de instalar, e te dá uma interface web de administração muito mais avançada do que Docker Desktop. Mas também é legal aprender a ficar na linha de comando e experimentar coisas como Lazy Docker. Dá pra fazer tudo só com o comando &ldquo;docker&rdquo;, mas às vezes não lembramos como fazer coisas que não se usa todo dia, tipo, &ldquo;como era mesmo, pra apagar imagens que não estão sendo usadas?&rdquo;, daí tanto Portainer como Lazy Docker podem ajudar. Dêem uma fuçada.</p>
<p>Na verdade, é muito mais fácil usar o NAT de portas, que é a opção padrão. Vamos pegar o arquivo de nginx do projeto do Lucas, que não optou pelo modo de rede host. Olha a declaração de servidores de upstream. Em vez de estar localhost com duas portas diferentes, tempos aqui dois hostnames diferentes usando as mesmas portas. É como se  estivéssemos declarando dois servidores separados, cada um com uma cópia da aplicação na porta 8080.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">    <span class="n">upstream</span> <span class="n">api</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">server</span> <span class="nl">app1</span><span class="p">:</span><span class="mi">8080</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">server</span> <span class="nl">app2</span><span class="p">:</span><span class="mi">8080</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">keepalive</span> <span class="mi">500</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No docker compose, podemos declarar o app1, expor a porta 8080, etiquetar com &amp; comercial &ldquo;app&rdquo; e lá embaixo declarar o app2 usando a mesma configuração, só sobrescrevendo o hostname. Todo o resto fica igual. Não precisamos nos preocupar em conflito de portas, porque é como se fossem servidores em máquinas diferentes. Essa é a vantagem do NAT de portas e por isso ele é o modo padrão. É muito mais intuitivo. Se não ficou claro isso, assista de novo meu episódio sobre containers e a série de redes.</p>
<p>A versão em PHP do Lauro é uma das mais complicadinhas pra subir, por causa daqueles passos extras no README, que ele preferiu não automatizar. Pra prosseguir a explicação, vamos usar o projeto de Node do Lucas daqui pra frente. O dele é mais simples, basta executar &ldquo;docker compose up&rdquo;, esperar tudo baixar e pronto. Mesmo em Docker de Windows ou Mac não deve ter problemas porque a versão dele usa NAT de portas padrão, então tudo deveria funcionar plug and play, que é o ideal de um docker compose.</p>
<p>Antes que eu me esqueça, se não está acostumado a usar docker compose, sempre se lembre que se não iniciar o comando &ldquo;up&rdquo; com a opção &ldquo;-d&rdquo; de daemon, ele vai ficar prendendo seu terminal. Com control-c dá pra derrubar todo mundo. E garanta que os containers desligaram executando &ldquo;docker compose down&rdquo;. Se modificar seu código e subir depois com &ldquo;up&rdquo; mas sentir que não apareceram suas modificações, dá pra forçar o build da imagem da aplicação com &ldquo;docker compose up &ndash;build&rdquo; ou &ldquo;docker compose up &ndash;force-build&rdquo;.</p>
<p>De outro terminal, dá pra ver os containers que estão de pé fazendo &ldquo;docker ps&rdquo;. Olha só como aparece bonitinho, daí dá pra abrir o shell em qualquer um deles usando &ldquo;docker exec -it&rdquo;. Enquanto estiver trabalhando num projeto, pode só ficar derrubando com o comando &ldquo;down&rdquo; e subindo com o comando &ldquo;up&rdquo;. Mas quando terminar o projeto e não for mais mexer nele, vale derrubar uma última vez com &ldquo;docker compose down &ndash;rmi all&rdquo;. Essa opção vai apagar os containers e as imagens. Imagens de docker usam muito espaço em disco e se ficar esquecendo de limpar de projetos que não vai mais usar, vai estar desperdiçando espaço. Depois pesquise sobre como limpar containers e imagens que não está mais usando.</p>
<p>Voltando, chegou a hora de entender um pouco esse negócio de teste de carga. Entenda, foda-se que linguagem você escolheu, que framework usa, as toneladas de javascript que enfiou no front-end. Svelte, Tailwind, nada disso importa. A única coisa que interessa é que entre seu navegador favorito e sua aplicação, trafegam mensagens em formato texto, obedecendo o protocolo HTTP. Mensagens divididas em cabeçalhos e corpo de texto.</p>
<p>O teste que sugeri antes, curl http://localhost:9999/contagem-pessoas é um exemplo disso. O Curl ou Wget são navegadores de linha de comando. Navegadores simples, que não tem front-end e por isso não precisam interpretar nem html, nem css, nem javascript. Eles só listam o que recebem. Fiquei pensando se perdia tempo explicando curl, como mandar e receber mensagens HTTP. Mas isso seria uma tangente desnecessária. Todo desenvolvedor web, tem que saber como enviar e receber mensagens HTTP manualmente, seja via curl na linha de comando, seja via web developer tools em qualquer navegador. Sem saber isso, sinto muito, você nem começou a entender web ainda. Volte vários passos, estude mais, só depois retorne.</p>
<p>Estou sendo bem cuzão quanto a isso porque é sério: HTTP é simples, os cabeçalhos principais dá pra saber de cabeça, os encodings de mensagem, como URL encode, são simples. E se não entender como funciona essa troca de mensagens, tudo daqui pra frente não vai fazer nenhum sentido. Sem saber isso você não entende nem o que é uma API. Sem saber isso não tem nem como começar a falar de problemas de segurança triviais como request forgery, nem otimizações de performance como conditional get requests. 80% da parte mais interessante e mais importante, você nunca vai ter acesso. Então pare aqui e vá estudar antes.</p>
<p>Isso tudo dito, vamos pular direto pro Gatling, que é a ferramenta de stress test, ou teste de carga, que a rinha escolheu. Eles até deram uma pequena patrocinada, acho que com alguns prêmios pros ganhadores. É uma ferramenta que aceita scripts feitos em Scala ou Kotlin. Primeiro de tudo é ter Java instalado na sua máquina. Cuidado, saiu não faz muito tempo o Java 21, e dependendo da sua distro, com um Arch, que instala tudo que tem de novo, vai ter problemas. Escolha manualmente o OpenJDK 17 ou versões mais antigas como versão 11.</p>
<p>Como eu sei que estou com o java correto? Porra, abre o terminal e digita &lsquo;java -version&rsquo;. Se tiver mais de uma jdk instalada, no caso de um Arch, tem o script &lsquo;archlinux-java set java-17-openjdk&rsquo; pra forçar usar a versão 17, por exemplo. Consulte a documentação do pacote de JDK da sua distro. Se for Windows ou Mac, baixe direto o instalador da versão 17 e já era.</p>
<p>Depois disso precisa baixar o Gatling propriamente dito. Só ir no site e baixar o zip. Depois deszipe onde quiser. Eu pessoalmente prefiro deszipar no diretório &ldquo;/opt/gatling&rdquo; mas você pode deszipar na sua home. Enfim, o importante é entrar no repositório da rinha, no diretório chamado &ldquo;stress-test&rdquo;. Lá vai achar o script principal, &ldquo;run-test.sh&rdquo;. Tem versão de Powershell também, mas honestamente, não perdi meu tempo com isso.</p>
<p>Edite esse script e garanta que a variável GATLING_BIN_DIR está apontando pro diretório bin que veio dentro do zip que baixou. Olhe a linha de baixo, é como ele executa o gatling, passando o script da simulação que é esse RinhaBackendSimulation, que já vamos ver. Em seguida declara os diretórios principais de trabalho. Fica tudo dentro de &ldquo;stress-test/user-files&rdquo;. Em resources temos planilhas com dados de teste. Vamos dar uma olhada.</p>
<p>O arquivo &ldquo;pessoas-payloads.tsv&rdquo; tem os dados de pessoas falsas que ele vai enviar pra API de cadastrar pessoas. Como podem ver, cada linha é um JSON, e os dados são super aleatórios mesmo.  Você acha que todo mundo envia dados bonitinhos pra sua aplicação? Pelo contrário, com o tanto de bots que tem por aí, sua aplicação está recebendo dados parecidos com esse o tempo todo. Nunca assuma que sua aplicação vai ser usada só por pessoas inteligentes que sabem ler e escrever direito.</p>
<p>Pelo contrário, assuma que a maior parte do uso vai vir de bots zoados ou pessoas zoadas que male male sabem escrever o próprio nome e vão preencher tudo errado. Sua aplicação precisa estar preparada para os piores inputs, sempre. Esse é um bom exemplo de ataque pra ver se coisas inválidas acabam conseguindo burlar suas validações, ou o mais comum, você esqueceu de implementar algumas validações.</p>
<p>O segundo arquivo é o &ldquo;termos-busca.tsv&rdquo; que vai ser usado na API de procura por termos. É outra lista de bobagens aleatórias. Mesma coisa, é pra estressar suas APIs e ver como se comportam. Precisa ter validações conforme explicado nas instruções da rinha. Pra um projeto de verdade, é bom ter teste de carga com dados completamente aleatórios como esses, mas também é bom ter dados um pouco mais próximos do que pessoas medianas mandariam, pra ficar mais fácil de analisar depois. Criar dados de teste é, em si, uma arte também.</p>
<p>Testes com inputs aleatórios como esse são chamados de Fuzz Test. Fuzz é tipo barulho, bagunça, que é um jeito de dizer aleatório. Se você só fizer testes com dados que parecem de verdade, está perdendo a oportunidade de ver os limites das validações.</p>
<p>Finalmente, temos o script do teste de carga propriamente dito, neste arquivo RinhaBackendSimulation.scala. Mesmo se você nunca escreveu Scala antes, pra fazer scripts simples com esse não é difícil. Qualquer bom programador deve ser capaz de no mínimo ler e entender sem grandes dificuldades. Vamos lá, por partes.</p>
<p>Gatling tem o conceito de Simulation e nossa classe herda dela. Em seguida começamos definindo pra onde mandar nossa carga. No caso a URL base é localhost porta 9999, onde configuramos o NGINX no Docker Compose, lembram? Como nossa aplicação não tem tratamento pra nenhum cabeçalho, só isso já basta. Só de brincadeira, colocaram um user agent fake aqui.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scala" data-lang="scala"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">RinhaBackendSimulation</span>
</span></span><span class="line"><span class="cl">  <span class="k">extends</span> <span class="nc">Simulation</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">val</span> <span class="n">httpProtocol</span> <span class="k">=</span> <span class="n">http</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">baseUrl</span><span class="o">(</span><span class="s">&#34;http://localhost:9999&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">userAgentHeader</span><span class="o">(</span><span class="s">&#34;Agente do Caos - 2023&#34;</span><span class="o">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Seguimos declarando uma variável &ldquo;criacao E consulta pessoas&rdquo; que é um &ldquo;scenario&rdquo;. Imagino que essa função &ldquo;scenario&rdquo; tenha na classe abstrata Simulation que estou estendendo. Ela devolve um objeto onde podemos sair encadeando várias chamadas de configuração. A API de configuração é bem legível. Olha só: no cenário criação e talvez consulta de pessoas, alimente com o arquivo formato tsv &ldquo;pessoas-payloads.tsv&rdquo; e considere  sendo circular, ou seja, se chegarmos no fim do arquivo, recomece da primeira linha de novo. Pra cada linha execute o bloco a seguir.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scala" data-lang="scala"><span class="line"><span class="cl">  <span class="k">val</span> <span class="n">criacaoEConsultaPessoas</span> <span class="k">=</span> <span class="n">scenario</span><span class="o">(</span><span class="s">&#34;Criação E Talvez Consulta de Pessoas&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">feed</span><span class="o">(</span><span class="n">tsv</span><span class="o">(</span><span class="s">&#34;pessoas-payloads.tsv&#34;</span><span class="o">).</span><span class="n">circular</span><span class="o">())</span>
</span></span><span class="line"><span class="cl">    <span class="o">.</span><span class="n">exec</span><span class="o">(</span>
</span></span><span class="line"><span class="cl">		<span class="o">....</span> <span class="c1">// bloco
</span></span></span><span class="line"><span class="cl">    <span class="o">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesse bloco começamos chamando outro método da simulação, &ldquo;http&rdquo;, que imagino que controla uma chamada http. Configuramos com uma chamada POST pra endpoint &ldquo;/pessoas&rdquo;, onde o corpo vai ser o payload, que é uma linha do arquivo de carga que estamos lendo. Como único cabeçalho configuramos &ldquo;Content-type&rdquo; pra ser o mime-type &ldquo;application/json&rdquo;. Todo framework que se preza, checa content type.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scala" data-lang="scala"><span class="line"><span class="cl"><span class="o">.</span><span class="n">exec</span><span class="o">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">http</span><span class="o">(</span><span class="s">&#34;criação&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">post</span><span class="o">(</span><span class="s">&#34;/pessoas&#34;</span><span class="o">).</span><span class="n">body</span><span class="o">(</span><span class="nc">StringBody</span><span class="o">(</span><span class="s">&#34;#{payload}&#34;</span><span class="o">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">header</span><span class="o">(</span><span class="s">&#34;content-type&#34;</span><span class="o">,</span> <span class="s">&#34;application/json&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 201 pros casos de sucesso :)
</span></span></span><span class="line"><span class="cl">  <span class="c1">// 422 pra requests inválidos :|
</span></span></span><span class="line"><span class="cl">  <span class="c1">// 400 pra requests bosta tipo data errada, tipos errados, etc. :(
</span></span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">check</span><span class="o">(</span><span class="n">status</span><span class="o">.</span><span class="n">in</span><span class="o">(</span><span class="mi">201</span><span class="o">,</span> <span class="mi">422</span><span class="o">,</span> <span class="mi">400</span><span class="o">))</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Se a criacao foi na api1 e esse location request atingir api2, a api2 tem que encontrar o registro.
</span></span></span><span class="line"><span class="cl">  <span class="c1">// Pode ser que o request atinga a mesma instancia, mas estatisticamente, pelo menos um request vai atingir a outra.
</span></span></span><span class="line"><span class="cl">  <span class="c1">// Isso garante o teste de consistencia de dados
</span></span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">check</span><span class="o">(</span><span class="n">status</span><span class="o">.</span><span class="n">saveAs</span><span class="o">(</span><span class="s">&#34;httpStatus&#34;</span><span class="o">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">checkIf</span><span class="o">(</span><span class="n">session</span> <span class="k">=&gt;</span> <span class="n">session</span><span class="o">(</span><span class="s">&#34;httpStatus&#34;</span><span class="o">).</span><span class="n">as</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span> <span class="o">==</span> <span class="s">&#34;201&#34;</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">header</span><span class="o">(</span><span class="s">&#34;Location&#34;</span><span class="o">).</span><span class="n">saveAs</span><span class="o">(</span><span class="s">&#34;location&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa função &ldquo;check&rdquo; não tenho muita certeza do que faz e não tive paciência pra ir ler na documentação, mas não me parece importante pra agora. Deve ser algo como, &ldquo;considere que está certo se o resultado da requisição voltar com código de status 201, 422 ou 400&rdquo;. Se voltar um crash com erro 500, ou página não encontrada que é 404, ou outra coisa, considere como um erro e reporte em vermelho.</p>
<p>Depois temos um novo check, pedindo pra salvar alguma coisa chamada &ldquo;http status&rdquo;. Podemos ver que vai ser usado na chamada checkIf abaixo. Essa linha parece dizer o seguinte: execute o bloco seguinte se o código http de status que gravamos na linha anterior for código 201, que é o código http correto quando um recurso é criado no servidor. Se for esse o caso, o bloco seguinte deve salvar o valor que voltar no cabeçalho de Location, dentro dessa sessão chamada &ldquo;location&rdquo;. Lembre-se disso em design de APIs: sempre que um recurso é criado no servidor, retornando código 201, também deve retornar um cabeçalho chamado Location com a URL completa para este novo recurso. É isso que estamos guardando.</p>
<p>Saindo desse bloco, o cenário dá uma pausa de 1 a 30 milissegundos. Em seguida, caso uma sessão de location tenha sido gravado, ele pede pra executar uma nova requisição http para esta location. Se for encontrado, o servidor deveria retornar código http 200 e o json do registro. Se for retorno 200 a simulação deve considerar como verde OK. Se voltar qualquer outro código, deve considerar vermelho knock out.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">    <span class="p">.</span><span class="nf">pause</span><span class="p">(</span><span class="mf">1.</span><span class="n">milliseconds</span><span class="p">,</span> <span class="mf">30.</span><span class="n">milliseconds</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nf">doIf</span><span class="p">(</span><span class="n">session</span> <span class="o">=&gt;</span> <span class="n">session</span><span class="p">.</span><span class="nf">contains</span><span class="p">(</span><span class="s">&#34;location&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">exec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nf">http</span><span class="p">(</span><span class="s">&#34;consulta&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s">&#34;#{location}&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como o comentário explica, o Zanfrancheschi, que desenhou o desafio da rinha, fez a coisa certa. Pro desafio ser minimamente interessante, só criar uma API que grava uma nova pessoa no banco, seria muito simples. Qualquer um que já tenha terminado pelo menos um tutorial de curso deveria conseguir. Mas, subir duas instâncias da mesma aplicação e colocar embaixo de um proxy reverso cria uma situação um pouco mais interessante.</p>
<p>Digamos que você tenha feito otimização prematura. Vamos tentar ser mais espertos. Ao receber o JSON via POST em /pessoas. Guardamos em memória, sem mandar insert pro banco, pra gravar assincronamente mais tarde, e assim podemos responder imediatamente código http 201. Como geramos um UUID aleatório antes de mandar pro banco, dá pra gerar a URL de Location. Seria algo como &ldquo;http://localhost:9999/pessoas/:uuid&rdquo;</p>
<p>O script do Gatling dá uma pausa de 1 a 30 milissegundos e manda puxar essa URL que voltou no cabeçalho de Location. Se antes desses 30 milissegundos, nossa aplicação não conseguir realmente gravar no banco, tem chance da consulta cair na segunda instância. Se o registro não teve tempo de ser gravado, não vai ser encontrado e vamos receber 404 not found. A simulação iria registrar um erro. Então, só segurar o registro em memória pra responder mais rápido iria quebrar essa condição de pesquisa do teste, tem que ser mais esperto que isso.</p>
<p>A expectativa da rinha, com os limites de CPU e RAM impostos, o requerimento de colocar duas ou mais instâncias, e impor uma carga pesada, enganaria programadores mais calejados a caírem no erro de tentar otimização prematura, colocando coisas como caches ou filas, como expliquei no episódio passado. Se fizer do jeito mais ingênuo que é primeiro escrever no banco e depois devolver código 201 com a URL de Location, vai tudo funcionar corretamente. Mas será que aí o tempo de espera de gravar no banco não vai fazer o NGINX dar timeout em muitas requisições?</p>
<p>E não, foi isso que descobrimos na exploração do episódio de 16 linguagens: todos nós subestimamos o banco de dados Postgres. Acontece que pra casos simples como esse, de inserts simples, ele é tão rápido quanto um servidor de cache como Redis. Qualquer otimização extra não faz diferença. A otimização de usar modo de rede host em vez de NAT de portas virtuais fez mais diferença do que tentar otimizar o Postgres com alguma solução de cache. Já vou voltar nisso, vamos só terminar de ler o arquivo de simulação.</p>
<p>O cenário de criação de pessoas, seguido da consulta dessa pessoa recém-criada, é o cenário mais comprido. Nem é difícil, só comprido. Os outros dois cenários são mais curtos. No &ldquo;busca pessoas&rdquo;, o cenário se alimenta do arquivo tsv &ldquo;termos-busca.tsv&rdquo;, novamente em modo circular, e pra cada linha do arquivo, faz uma requisição GET no endpoint &ldquo;/pessoas&rdquo; passando a variável &ldquo;t=&rdquo;, concatenando o parâmetro de busca.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">  <span class="n">val</span> <span class="n">buscaPessoas</span> <span class="o">=</span> <span class="nf">scenario</span><span class="p">(</span><span class="s">&#34;Busca Válida de Pessoas&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nf">feed</span><span class="p">(</span><span class="nf">tsv</span><span class="p">(</span><span class="s">&#34;termos-busca.tsv&#34;</span><span class="p">).</span><span class="nf">circular</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nf">exec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">http</span><span class="p">(</span><span class="s">&#34;busca válida&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s">&#34;/pessoas?t=#{t}&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// qq resposta na faixa 2XX tá safe
</span></span></span><span class="line"><span class="cl">    <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Acho que se não configurar explicitamente, o cenário considera que respostas 200 são OK e qualquer coisa diferente é erro. Por fim, temos o cenário de busca inválida de pessoas, que é o mesmo endpoint, só não passando nenhum termo de procura. Segundo a instrução da rinha, isso deveria retornar código de erro 400, que é o que o script vai checar mesmo. Se devolver 400, marca como verde, OK.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">  <span class="n">val</span> <span class="n">buscaInvalidaPessoas</span> <span class="o">=</span> <span class="nf">scenario</span><span class="p">(</span><span class="s">&#34;Busca Inválida de Pessoas&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nf">exec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="nf">http</span><span class="p">(</span><span class="s">&#34;busca inválida&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s">&#34;/pessoas&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// 400 - bad request se não passar &#39;t&#39; como query string
</span></span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nf">check</span><span class="p">(</span><span class="n">status</span><span class="p">.</span><span class="nf">is</span><span class="p">(</span><span class="mi">400</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com os 3 cenários declarados, agora o script define um bloco que chama de &ldquo;setup&rdquo;. Literalmente, configurar como os cenários devem rodar. Em todos os casos, começa dizendo &ldquo;comece com 2 usuários constantes por segundo, durante 10 segundos&rdquo;, na sequência fala &ldquo;prossiga agora com 5 usuários por segundo, durante 15 segundos&rdquo;. É uma carga leve, só de warm up, ou aquecimento mesmo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">setUp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="n">criacaoEConsultaPessoas</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	  <span class="nf">constantUsersPerSec</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">10.</span><span class="n">seconds</span><span class="p">),</span> <span class="c1">// warm up
</span></span></span><span class="line"><span class="cl">	  <span class="nf">constantUsersPerSec</span><span class="p">(</span><span class="mi">5</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">15.</span><span class="n">seconds</span><span class="p">),</span> <span class="c1">// are you ready?
</span></span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	  <span class="nf">rampUsersPerSec</span><span class="p">(</span><span class="mi">6</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="mi">600</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">3.</span><span class="n">minutes</span><span class="p">)</span> <span class="c1">// lezzz go!!!
</span></span></span><span class="line"><span class="cl">	<span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">).</span><span class="nf">protocols</span><span class="p">(</span><span class="n">httpProtocol</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em seguida começa a carga de verdade, olha só &ldquo;ramp&rdquo;, que quer dizer ir aumentando, como numa rampa mesmo. Vá aumentando, começando com 6 usuários por segundo, até atingir 600 usuários por segundo, acelerando constantemente durante 3 minutos, mantendo um crescimento linear. Agora sim, isso vai gerar uma carga pesada até o final. Entenderam?</p>
<p>Mas não é só isso, em paralelo vai rodar também os cenários de busca e busca inválida. Ambos fazem um aquecimento pequeno pelos mesmos 25 segundos. Daí já começa, ramp de 6 usuários por segundo, até atingir 100 usuários fazendo buscas válidas por segundo, durante os mesmos 3 minutos. E por fim, ramp de 6 usuários por segundo até 40 fazendo buscas inválidas por segundo, durante 3 minutos.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">	<span class="n">buscaPessoas</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	  <span class="nf">constantUsersPerSec</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">25.</span><span class="n">seconds</span><span class="p">),</span> <span class="c1">// warm up
</span></span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	  <span class="nf">rampUsersPerSec</span><span class="p">(</span><span class="mi">6</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="mi">100</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">3.</span><span class="n">minutes</span><span class="p">)</span> <span class="c1">// lezzz go!!!
</span></span></span><span class="line"><span class="cl">	<span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="n">buscaInvalidaPessoas</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">	  <span class="nf">constantUsersPerSec</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">25.</span><span class="n">seconds</span><span class="p">),</span> <span class="c1">// warm up
</span></span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	  <span class="nf">rampUsersPerSec</span><span class="p">(</span><span class="mi">6</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="mi">40</span><span class="p">).</span><span class="nf">during</span><span class="p">(</span><span class="mf">3.</span><span class="n">minutes</span><span class="p">)</span> <span class="c1">// lezzz go!!!
</span></span></span><span class="line"><span class="cl">	<span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em cada cenário, só configuramos que busca é um get pra &ldquo;/pessoas&rdquo; ou criação é um post pra &ldquo;/pessoas&rdquo;, mas como ele sabe pra onde mandar essas requisições? Pra isso serve aquela configuração que fizemos lá no começo do script, declarando que a URL base é &ldquo;localhost&rdquo; porta 9999 lembram? No final do setup, plugamos essa configuração. E pronto, é isso! Vamos ver rodando.</p>
<p>Pra ficar claro, tenho um terminal dividido em dois, usando split horizontal do TMUX. Na metade de cima, estamos no projeto do Lucas Poole em Node. Podemos chamar &ldquo;docker compose up&rdquo; e esperar os serviços todos subirem. Na metade de baixo, estou no diretório de &ldquo;stress-test&rdquo; do repositório da rinha, onde tem o script &ldquo;run-test.sh&rdquo;, que vamos rodar agora. Pronto, passa aqueles 25 segundos de aquecimento e aí começa a brincadeira. Agora é esperar os 3 minutos dos cenários.</p>
<p>Lembram que o script configura 3 cenários? Estão aqui: Busca Inválida, Busca Válida e Criação. Em cima tem um resumo, embaixo tem um pouco mais detalhado. O principal: OK são requisições que devolveram códigos http válidos de resposta, como 200, 400 ou 422. Tudo que devolveu erro, código 500, timeout, acho que ele considera como KO ou Knock Out. São as requisições perdidas.</p>
<p>Voltando, depois de algum tempo rodando, quando a carga apertar mais pro fim, em vários dos projetos participantes, vamos ver este erro sendo reportado: java.io.IOException: Premature Close. Esse é o erro que o Vinicius Ferraz descobriu como consertar mudando de network_mode bridge pra host. Olha só o que essa mensagem está dizendo: mais de 9 mil requisições perdidos nesse exceção. 9 mil, num total de pouco mais de 100 mil requisições, é um número considerável, quase 10% de erro.</p>
<p>Vamos abrir o gráfico que o Gatling gera. No mundo ideal gostaríamos de ver somente essa barra verde, sem nada vermelho do lado. Mas temos vermelho. Olhando aqui na tabela veja só, de um total de 108 mil requisições, tivemos quase 99 mil que deram OK, mas quase 10 mil em vermelho, que deram knock out. Já volto nessa tabela, vamos olhar mais pra baixo os gráficos.</p>
<p>Lembram como configuramos pra iniciar os três cenários em paralelo, pra ir subindo de 6 usuários pra mais de 600 simultâneos por segundo? Um ramp up? Por isso o formato do gráfico é um triângulo. Começa com 6 usuários por segundo e vai subindo, ramping up, com aceleração constante, aumentando a velocidade linearmente, até mais de 600 usuários. Por mais de 2 minutos, mais de dois terços do tempo, cresce sem problemas. Mas aí olha essa parte final.</p>
<p>O final é cheio de &ldquo;sobes e desces&rdquo;, esse serrilhado. Aqui a aplicação tá claramente engasgando. Muitas requisições em fila não conseguem ser atendidos, ou demoram demais pra responder, ou dão timeout de tanto esperar. Por isso, no último gráfico, começamos a ter várias áreas vermelhas. Lembrando que muitos desses vermelhos são os IOException de Java que mencionei antes.</p>
<p>Mas será que realmente faz tanta diferença assim? Pra demonstrar, podemos modificar o código do Lucas pra usar network mode host. Começamos mudando o &ldquo;index.js&rdquo; que carrega o node.js na porta 8080, hardcoded, e trocar por uma variável de ambiente chamada &ldquo;HTTP_PORT&rdquo;, que podemos configurar no docker compose.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gu">@@ -62,6 +62,7 @@ app.get(&#39;/contagem-pessoas&#39;, (_, res) =&gt; {
</span></span></span><span class="line"><span class="cl"> app.use(errorHandler);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> const numForks = Number(process.env.CLUSTER_WORKERS) || 1;
</span></span><span class="line"><span class="cl"><span class="gi">+const httpPort = Number(process.env.HTTP_PORT) || 8081;
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> if(cluster.isPrimary &amp;&amp; process.env.CLUSTER === &#39;true&#39;){
</span></span><span class="line"><span class="cl">     logger.info(`index.js: Primary ${process.pid} is running`);
</span></span><span class="line"><span class="cl"><span class="gu">@@ -74,8 +75,8 @@ if(cluster.isPrimary &amp;&amp; process.env.CLUSTER === &#39;true&#39;){
</span></span></span><span class="line"><span class="cl">         logger.info(`index.js: worker ${worker.process.pid} died: code ${code} signal ${signal}`);
</span></span><span class="line"><span class="cl">     });
</span></span><span class="line"><span class="cl"> } else {
</span></span><span class="line"><span class="cl"><span class="gd">-    const serverApp = app.listen(8080, () =&gt; {
</span></span></span><span class="line"><span class="cl"><span class="gd">-        logger.info(`index.js:${process.pid}:Listening on 8080`);
</span></span></span><span class="line"><span class="cl"><span class="gi">+    const serverApp = app.listen(httpPort, () =&gt; {
</span></span></span><span class="line"><span class="cl"><span class="gi">+        logger.info(`index.js:${process.pid}:Listening on ${httpPort}`);
</span></span></span><span class="line"><span class="cl">     });
</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora adicionamos essa variável no docker compose e colocamos network_mode host em todos os serviços, como neste trecho de código. Veja como também mudamos a URL de conexão com o banco de dados pra ser localhost. Não vai mais ter NAT pra um host virtual chamado &ldquo;postgres&rdquo;. A rede virtual do Docker possui um DNS interno, que traduz nomes como &ldquo;postgres&rdquo; pro endereço virtual, no modo de rede virtual. Em modo host, o container se pendura direto no localhost, por isso temos que nos preocupar com conflito de portas agora.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gu">@@ -1,17 +1,19 @@
</span></span></span><span class="line"><span class="cl"> version: &#39;3.4&#39;
</span></span><span class="line"><span class="cl"> services:
</span></span><span class="line"><span class="cl"><span class="gd">-  app1: &amp;app
</span></span></span><span class="line"><span class="cl"><span class="gi">+  app1:
</span></span></span><span class="line"><span class="cl">     # image: lukas8219/rinha-be-2023-q3:latest
</span></span><span class="line"><span class="cl">     build: .
</span></span><span class="line"><span class="cl">     hostname: app1
</span></span><span class="line"><span class="cl">     environment:
</span></span><span class="line"><span class="cl"><span class="gd">-      DB_URL: &#34;postgres://postgres:12345678@postgres:5432/postgres&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+      DB_URL: &#34;postgres://postgres:12345678@localhost:5432/postgres&#34;
</span></span></span><span class="line"><span class="cl">       DB_POOL: 35
</span></span><span class="line"><span class="cl">       PINO_LOG_LEVEL: &#34;debug&#34;
</span></span><span class="line"><span class="cl">       REQ_TIMEOUT: &#34;2000&#34;
</span></span><span class="line"><span class="cl">       NODE_ENV: production
</span></span><span class="line"><span class="cl">       CLUSTER: true
</span></span><span class="line"><span class="cl">       CLUSTER_WORKERS: 5
</span></span><span class="line"><span class="cl"><span class="gi">+      HTTP_PORT: 8080
</span></span></span><span class="line"><span class="cl"><span class="gi">+    network_mode: host
</span></span></span><span class="line"><span class="cl">     expose:
</span></span><span class="line"><span class="cl">       - &#34;8080&#34;
</span></span><span class="line"><span class="cl">     depends_on:
</span></span><span class="line"><span class="cl"><span class="gu">@@ -27,8 +29,32 @@ services:
</span></span></span><span class="line"><span class="cl">           cpus: &#39;0.5&#39;
</span></span><span class="line"><span class="cl">           memory: &#39;0.6GB&#39;
</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A última coisa, depois de ajustar o docker compose, é ajustar a configuração do nginx. Repetindo, não tem o DNS interno da rede virtual, tudo é localhost, então temos que mudar os servidores upstream do proxy reverso pra ser localhost. Subimos uma instância da aplicação na porta 8080 e outra na 8081, pra não conflitar.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gu">@@ -11,8 +11,8 @@ http {
</span></span></span><span class="line"><span class="cl">     error_log /dev/null emerg;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">     upstream api {
</span></span><span class="line"><span class="cl"><span class="gd">-        server app1:8080;
</span></span></span><span class="line"><span class="cl"><span class="gd">-        server app2:8080;
</span></span></span><span class="line"><span class="cl"><span class="gi">+        server localhost:8080;
</span></span></span><span class="line"><span class="cl"><span class="gi">+        server localhost:8081;
</span></span></span><span class="line"><span class="cl">         keepalive 500;
</span></span><span class="line"><span class="cl">     }
</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, não mostrei configuração dos demais serviços no docker compose mas já deve ter dado pra entender, né? Fica de exercício repetir essa modificação em um dos projetos da rinha. Eu mesmo contribuí pull requests em vários dos projetos participantes justamente pra adicionar essa pequena otimização. Se eu consegui, vocês também conseguem. Agora podemos subir com &ldquo;docker compose up&rdquo; de novo e, se não tiver nenhuma mensagem de erro, no outro terminal podemos rodar o Gatling de novo. Não vou mostrar rodando tudo de novo, vamos direto pro final.</p>
<p>A última coisa que o script de Gatling faz é chamar o endpoint &ldquo;/contagem-pessoas&rdquo;. Da última vez tinha voltado que conseguiu gravar no banco uns 40 mil registros. Mas agora, olha a diferença, devolveu  quase 47 mil registros. É uma diferença considerável. Se voltarmos nos resultados que estavam aparecendo no terminal, olhem, não tem mais aquela mensagem de Premature Close do IOException de Java. Vamos abrir os gráficos.</p>
<p>Vou colocar o anterior na esquerda e o novo na direita. Mesmo aplicativo, única coisa que mudou na direita é que subimos docker compose com os serviços em modo de rede host. De cara já podemos ver que a barrinha vermelha sumiu. Ótimo sinal. Apareceu um pouco de laranja. O que antes tava dando vermelho, agora demora mais, mas vai até o fim e responde. Isso que é laranja. Olha a tabelinha mais pra baixo, antes conseguíamos só um total de 108 mil requisições, agora conseguimos mais de 115 mil requisições. Mais importante: antes teve um total de quase 10 mil knockouts, mas agora foram só uns 150. Diferença brutal.</p>
<p>Dando um zoom aqui do lado, tem um conceito que acho importante que todos entendam. Praticamente todo iniciante pensa em tempo de resposta médio. Média é esta coluna aqui pro fim, &ldquo;mean&rdquo;. O que é média? É tabular quanto que levou pra responder cada uma dessas 115 mil requisições, somar tudo e dividir pelo total de requisições. Esse tempo deu 879 milissegundos. Quase 1 segundo por requisição. Se tinha alguma dúvida, esse tempo de resposta é absurdamente lento. Qualquer coisa acima de meio segundo é inaceitável em qualquer aplicação. O certo é sempre manter menos que 100 ou 50 milissegundos.</p>
<p>Veja logo do lado, desvio padrão de 1.7 segundos. O desvio padrão é maior que a média. Só um desvio padrão pro lado e já estamos falando de mais de 2 segundos por requisição. É uma ordem de grandeza inteira. O que isso diz sobre sua aplicação? Nada importante. Vamos entender os números das colunas anteriores, que são mais interessantes.</p>
<p>Olha aqui, 99th percentile. Essa coluna diz o tempo que levou pra responder o 1% das requisições mais lentas nesse teste. Isso dá mais ou menos 1.150 requisições. Soma o tempo delas, divide por 1.150 e isso dá quase 8 segundos. E faz sentido, provavelmente são as últimas mil e tantas requisições desse teste de carga, quando nos aproximamos dos 600 usuários por segundo que configuramos no cenário do Gatling.</p>
<p>Descemos pro 95th percentile, os 5% mais lentos. Já cai pra pouco mais de 5 segundos por requisição. Vamos pro 75th percentile, os 15% mais lentos, já cai pra faixa de 1 segundo por requisição. Veja como os tempos caem drasticamente. No 50th percentile, a média da metade mais lenta de requisições, é na faixa de 2 milissegundos. Vamos olhar nos gráficos lá embaixo pra ter uma noção melhor disso.</p>
<p>Olha esse primeiro gráfico, vejam com os tempos de resposta por requisição se mantiveram super baixo, lá na casa dos 2 milissegundos por quase todo o teste de carga. Lembrando que configuramos o Gatling pra ir subindo de 6 usuários por segundo até 600 ao longo de 3 minutos. Por mais de 2 minutos, nosso setup segurou super bem, quase não varia esse tempo, mesmo aumentando quantidade de pessoas. Mas aí começa o desespero. Veja como os tempos de resposta aumentam drasticamente, salta pra faixa de 5 segundos, tenta cair, mas volta a subir até quase 10 segundos.</p>
<p>No gráfico logo abaixo temos o número de requisições por segundo. De novo, como estamos subindo número de usuários linearmente, o número de requisições também sobe linearmente e, se tivesse recursos sobrando, esse gráfico seria perfeitamente linear até o fim. Mas naquele mesmo trecho, onde os tempos de resposta sobem, começa a ficar serrilhado, até dar um crash drástico na quantidade de requisições por segundo que conseguimos responder.</p>
<p>O gráfico em azul é o número de requisições que conseguimos aceitar, já o gráfico abaixo é o número de respostas que nossa aplicação conseguiu devolver. Verde são os OK, vermelho são os knockouts. Graças ao modo de rede host, veja como quase não tem vermelho, mas se olharmos o gráfico anterior, veja como tinha muito mais vermelho nessa faixa de respostas.</p>
<p>Voltando pra tabelinha, entendam: média de tempo de resposta por requisição não diz nada. Média de 879 milissegundos por requisição. Não diz nada. Agora, lendo o gráfico sabemos que a requisição que mais demorou pra ser respondida demorou mais de 9 segundos, que é essa coluna de máximo. Sabemos que apesar de 5% das requisições ter demorado pra caramba, na faixa de 5 segundos, a grande maioria conseguiu ter resposta na faixa de 2 milissegundos, o que é excelente.</p>
<p>O que causou essa lentidão de segundos na faixa do 95th percentile, não é problema de código, mas limitação de recursos da infraestrutura mesmo. Não tem muito mais que se possa fazer em código pra encurtar tempo de forma significativa. Eu sei, porque eu tentei, em 16 linguagens, lembram do outro video? É mais fácil aumentar os recursos, um pouco mais de CPU, um pouco mais de RAM.</p>
<p>Por outro lado, se no 50th percentile os tempos estivessem na faixa de 1 segundo ou mais. Aí sim, o problema seria definitivamente no código, um gargalo que precisaria ser corrigido. Vocês precisam rodar benchmarks assim em diversos tipos de projetos, diversos tipos de endpoints, pra começar a ter noção de que tipos de tempos de resposta mínimo, máximo, 95th percentile e 50th percentile, são considerados bons ou ruins.</p>
<p>Esses números são importantes pra ajudar a decidir se vale a pena perseguir otimização de código ou simplesmente alocar mais hardware. Seu &ldquo;achômetro&rdquo;, sem números, não vale nada. &ldquo;Ah, eu acho que temos que reescrever esse código em outra linguagem&rdquo;. Me mostre os números. Se não é capaz de gerar estes números, eu sei que também é incapaz de reescrever qualquer coisa pra ficar melhor.</p>
<p>Achei bacana esta rinha ter dado ênfase em um desafio de backend baseado em benchmark e teste de carga. Eu acho que todo bom programador sempre tem uma ferramenta de benchmark guardado na manga pra usar justamente em momentos de decisão como o que falei antes: vale a pena parar pra otimizar? Quanto de melhoria estamos esperando ter ao custo de quanto tempo pra arrumar? Só aumentar máquina resolveria? Faça medições pra saber.</p>
<p>Eu não conhecia o Gatling, mas achei uma ferramenta muito boa, especialmente porque gera gráficos fáceis de ler e de mostrar pros outros. Existem outras ferramentas que eu usava mais antes, como o antigo Apache Bench ou o WRK que acho que fala &ldquo;work&rdquo;. Pra mim esses dois eram tão essenciais quanto saber usar wget ou curl. Recentemente mexi um pouco no tal de Vegeta também, que parece ser mais na categoria do Gatling. E tem dezenas de outros, open source e comerciais. Não deixem de fuçar as opções e testar todas que puder.</p>
<p>Espero que tenham entendido até aqui, porque agora acho que vai ficar um pouco mais complicado, no sentido que vou discutir conceitos de mais alto nível. Se começar a achar que complicou, é a mesma coisa de sempre, anotem os termos que não entenderam e pesquisem. Depois voltem e vejam se conseguem acompanhar o raciocínio até o fim. Até agora foi mais um aquecimento.</p>
<p>Eu deixei de lado banco de dados até agora pra poder atacar esse assunto de uma só vez agora. Eu já fiz um video falando mais sobre como bancos de dados funcionam por baixo, então recomendo que assistam lá se não viram ainda. Ter na cabeça essa estrutura vai ser importante pra entender o que vou explicar agora.</p>
<p>Como disse no primeiro video da rinha, o erro que quase todo mundo cometeu neste desafio foi subestimar o Postgres. Se fosse MySQL teria sido a mesma coisa: nós teríamos achado que seria o gargalo. Isso porque na nossa experiência em projetos de verdade, na maior parte do tempo, o gargalo costuma ser o banco de dados. Por que?</p>
<p>Porque em sistemas mais realistas, com centenas de tabelas, milhares de índices, milhares de relacionamentos entre tabelas, qualquer operação de insert ou update costuma mexer não só em uma linha de uma única tabela, mas pode acionar triggers, gatilhos, pra rodar procedures em outras tabelas. Pode acionar gatilho pra atualizar múltiplos índices. Então nenhuma operação é &ldquo;simplesmente&rdquo; só mexer uma linha, normalmente são múltiplas operações acontecendo em múltiplos lugares diferentes. Isso custa tempo, às vezes pode levar dezenas de milissegundos.</p>
<p>Mesma coisa queries, selects. Em sistemas complexos normalmente temos queries com vários inner joins, outer joins, sub-selects, unions. Toda consulta, na realidade, precisa acionar diversos índices, fazer alguns table scans, então pode demorar, talvez dezenas de milissegundos. Pra piorar temos o custo por conexão. No caso de um postgres, cada conexão ao banco pode custar na faixa de 2 a 4 megabytes. 100 conexões poderiam exigir quase meio gigabyte de RAM.</p>
<p>Por isso precisamos usar pools de conexão pra reciclar conexões. Quando uma requisição termina de ser respondida, o framework devolve a conexão livre pro pool. Assim, outra requisição pode reusar a mesma conexão, sem precisar alocar mais 4 megabytes extras toda vez. Quando estamos falando de milhares de requisições por segundo, megabytes se tornam gigabytes muito rápido, se não tomarmos cuidado.</p>
<p>Lembram do nosso gráfico? Eu disse que uma resposta levar 2 milissegundos é muito bom. Mas se levar 10 milissegundos só pra inserir um registro numa tabela, não vai ser ruim? Vai sim. Por isso em projetos de verdade usamos diversas estratégias pra economizar processamento no banco o máximo possível. Usamos pelo menos dois tipos de estratégia: uma pra leitura, onde cacheamos resultados, pra evitar toda hora repetir as mesmas queries pesadas, e estratégia de filas de jobs assíncronos, pra evitar metralhar o banco com operações demais de gravação e modificação.</p>
<p>Lembrando: toda operação de modificação bloqueia um ou mais registros em uma ou mais tabelas no banco. Por cima desses locks temos transações, logs transacionais pra, caso de aconteça alguma pane, a gente não tenha perda de dados, sem deixar nada corrompido. Toda essa proteção custa processamento e custa tempo. Toda pesquisa sendo feita em tabelas que estão constantemente sendo modificadas significa que uma operação de leitura fica concorrendo com uma operação de escrita por esses locks. Ou seja, se não tivermos controle e recebermos um volume grande demais de escritas, ao mesmo tempo várias leituras, tudo vai ficar lento até parar.</p>
<p>Por isso precisamos desafogar o banco de dados. Dá pra criar servidores separados de réplicas somente de leitura. Dessa forma, podemos separar um banco mestre só pra escrita e bancos só de leitura, pra onde podemos mandar as queries. Mas réplicas não saem de graça: quanto mais réplicas colocar, maior o peso pra manter eles sincronizados. Quando falamos em cache estamos falando em serviços em paralelo como Memcache ou Redis. Até na AWS existe o serviço ElastiCache, que é uma interface pra memcache ou redis como serviço.</p>
<p>No caso de filas, alguns usam Nats ou Redis como gerenciador de fila. Funcionam, mas não recursos avançados. Não oferecem muitas das garantias de um sistema de filas maior. Assim como um banco relacional deve oferecer as proteções ACID, uma fila de verdade precisa de garantias, por exemplo, garantindo que um elemento na fila nunca vai ser duplicado. Filas de verdade são coisas como o lendário RabbitMQ ou o mais popular Apache Kafka. AWS também tem um serviço, o SQS, que é o Simple Queue Service.</p>
<p>No caso do cache a ideia é simples: primeiro pergunta pro cache se ele tem a resposta. Se não tiver, aí manda a query pro banco de dados, daí guarda o resultado no cache pra próxima vez. Dessa forma, o banco não precisa ficar sendo redundante e gastar processamento pra responder o que já acabou de responder. O Cache é uma memória temporária pra diminuir a carga de pesquisa.</p>
<p>No caso de filas a ideia é um pouco mais complicada: em vez de metralhar o banco de dados com operações de modificação, jogue todas essas operações numa fila. Configure um número limitado e controlado de trabalhadores, workers, que ficam escutando essa fila, e vão puxando essas operações. O número de workers é o número de conexões que sabemos que o banco aguenta sem começar a ficar muito lento.</p>
<p>Tudo isso eu já expliquei no episódio de &ldquo;Tornando sua App Web mais rápida&rdquo; e também no episódio onde sugiro como o site Ingresso.com poderia melhorar sua arquitetura. E sabendo de tudo isso, claro, como as instruções da rinha pediam pra limitarmos o uso de recursos do Docker pra no máximo 1.5 CPU e 3 giga de RAM, e sabendo que vai rodar um teste de carga com milhares de inserts e pesquisas simultâneas, o primeiro instinto foi pensar: bora já garantir e colocar cache nas pesquisas e filas e jobs pros inserts.</p>
<p>Eu fiz isso na minha primeira versão de Ruby on Rails, que vocês podem ver no meu GitHub, e como vimos no episódio anterior, diversos projetos que eu avaliei fizeram algo similar. Alguns colocaram só cache de redis. Alguns tentaram adicionar algum tipo de fila. Mas o fato é que se pararmos pra pensar friamente no que exatamente é este projeto e qual é a carga, já dá pra saber que qualquer coisa mais sofisticada assim era matar uma mosca com bazuca.</p>
<p>Esses projetinho inteiro tem uma única tabela. Só uma. Não tem relacionamento nenhum, não tem chave estrangeira, não tem joins, nada. Lembram daquela planilha de dados aleatórios que o script de Gatling usa pra fazer os inserts? Quantas linhas tinha? 100 mil? Eu diria, &ldquo;só&rdquo; 100 mil, e durante os 3 minutos de carga, qual é o total de requisições? Umas 100 mil mesmo. Qual é a contagem final de registros inseridos? Faixa de 45 a 50 mil registros. Entenda, isso é absurdamente pouco. Ordem de poucos milhares de registros é quase nada. Qualquer computador furreca dos anos 80 sabe lidar com milhares de registros.</p>
<p>Um banco com uma única tabela de míseras 50 mil linhas, sofrendo não mais que 100 mil tentativas de inserts, é algo miseravelmente pequeno. Bancos de dados de verdade tem centenas de tabelas, a maioria com centenas de milhares de linhas, muitas com milhões, dezenas de milhões de linhas. Isso sim começa a parecer &ldquo;grande&rdquo;. Uma única tabela é um hello world.</p>
<p>Portanto, apesar de ser divertido de implementar, nem cache de leitura, nem fila de jobs de escrita era realmente necessário. Bastava usar o banco normalmente. Um Postgres é mais do que capaz de fazer inserções simples assim de forma extremamente rápida. Na verdade vai ser na mesma ordem de grandeza de tempo do que escrever num banco de cache como Redis, por isso não faz diferença nesse caso. Só faz diferença usar Redis quando a escrita for ordens de grandeza mais rápido porque o Postgres está tendo que lidar com operações muito pesadas em cima de um volume realmente grande de dados.</p>
<p>Entenderam? Muita gente ainda pensa que Redis ou Memcache é automaticamente mais rápido do que qualquer banco de dados. Isso não é verdade. Um banco de dados quase vazio vai ser tão rápido quanto um Redis. Mesma coisa se você exagerar e colocar coisa demais no Redis, ele vai ficar lento também. Nada é automático em programação. Se você encarar dessa forma, tudo que fizer sempre vai sair uma droga. &ldquo;Ah, mas o Redis não devia cuidar de tudo sozinho e ser mais rápido??&rdquo; Lógico que não, idiota.</p>
<p>Mas havia de fato um pequeno fator de preocupação. Na API de pesquisa por termos, a rinha pede pra pesquisar ou no campo de nome ou no campo de apelido ou no campo de tecnologias. Ficaria um select com ILIKE &ldquo;%blabla%&rdquo; OR ILIKE &ldquo;%blabla%&rdquo;, que é simplesmente um dos piores tipos de pesquisa que se pode fazer em SQL.</p>
<p>Por causa desses porcentos no começo e no fim, significa que não temos como usar um índice. Índices funcionam pra pesquisas exatas. Pesquisa parcial, o único jeito é fazer pattern matching em todas as linhas da tabela. Uma vez pra cada campo depois de &ldquo;OR&rdquo;. Como temos três campos pra pesquisar, vão ser três table scans por todas as linhas da tabela até aquele ponto. Isso pode ficar pesado muito rápido sem você perceber. Se num determinado momento a tabela tiver 10 mil linhas, vamos ter que fazer 30 mil operações de comparação. E quanto maior fica a tabela, mais longa fica a pesquisa. E qual a solução?</p>
<p>Recomendo assistirem depois o episódio do código vazado do algoritmo do Twitter, onde eu faço uma tangente pra explicar o que é full-text search, ou pesquisa por embeddings em espaço vetorial. Não vou repetir o que expliquei lá, mas é o que está por baixo de como um Google ou Bing conseguem responder qualquer pesquisa tão rápido e com os resultados mais relevantes logo no topo da primeira página de resposta. Todo banco de dados moderno tem suporte a algo similar, e isso é chamado de &ldquo;full text search&rdquo;.</p>
<p>No caso do Postgres existem duas técnicas mais conhecidas: GIN e GIST. GIN significa Generalized Inverted Index ou Índice Invertido Generalizado e é mais recomendado pra estruturas de dados complexas como arrays, JSONB, hstore. É otimizado pra pesquisas de múltiplas chaves e permite indexação dos valores individuais de um array, chaves de um JSONB e assim por diante. O índice GIN é construído ao extrair chaves dos valores indexados e criando uma lista de postagem, uma lista ordenada de ponteiros para as linhas indexadas.</p>
<p>GIST vem de Generalized Search Tree ou Árvore de Procura Generalizada. GIST é uma técnica de indexação de uso mais geral e requer a implementação de métodos de procura customizadas. Pode ser usado pra indexar praticamente qualquer tipo de dados no Postgres, incluindo dados espaciais, tipos de dados customizados e qualquer tipo de texto. Índices GIST são caracterizados por uma estrutura hierárquica de nós.</p>
<p>Podemos somar GIST com o suporte a uma extensão de trigramas. LEu explico sobre n-grams no video do Twitter. Vamos mostrar na prática o que todo esse mambo jambo significa. Pra isso vou abrir o código que cria a tabela no banco. O Lucas fez bem em adicionar esse código no Node.js. Ele inicia importando a biblioteca &ldquo;pg&rdquo; e cria um Pool de conexões usando a URL de conexão que vem da variável de ambiente DB_URL no docker compose.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">pg</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;pg&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">logger</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;./logger&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">URL</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_URL</span> <span class="o">||</span> <span class="s1">&#39;postgres://postgres:12345678@localhost:5432/postgres&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">pool</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">pg</span><span class="p">.</span><span class="nx">Pool</span><span class="p">({</span> <span class="nx">connectionString</span><span class="o">:</span> <span class="nx">URL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">max</span><span class="o">:</span> <span class="p">(</span><span class="nb">Number</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">DB_POOL</span><span class="p">)</span> <span class="o">||</span> <span class="mi">200</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">idleTimeoutMillis</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">connectionTimeoutMillis</span><span class="o">:</span> <span class="mi">10000</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí registra um evento no pool, o evento &ldquo;connect&rdquo;, que diz: assim que conectar, execute a função anônima a seguir, que vai enviar uma query pro pool. Essa query são comandos em SQL. Como temos duas instâncias de Node subindo ao mesmo tempo, em ambos vai executar essa função, então precisamos escrever os comandos pra, caso já exista o que queremos criar, só ignora e segue em frente.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">pool</span><span class="p">.</span><span class="nx">once</span><span class="p">(</span><span class="s1">&#39;connect&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">logger</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`database.js: Connected  to db </span><span class="si">${</span><span class="nx">URL</span><span class="si">}</span><span class="sb">`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">logger</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Creating table &#34;pessoas&#34; if not exists`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">pool</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">        CREATE EXTENSION IF NOT EXISTS pg_trgm;
</span></span></span><span class="line"><span class="cl"><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">        CREATE OR REPLACE FUNCTION generate_searchable(_nome VARCHAR, _apelido VARCHAR, _stack JSON)
</span></span></span><span class="line"><span class="cl"><span class="sb">            RETURNS TEXT AS $$
</span></span></span><span class="line"><span class="cl"><span class="sb">            BEGIN
</span></span></span><span class="line"><span class="cl"><span class="sb">            RETURN _nome || _apelido || _stack;
</span></span></span><span class="line"><span class="cl"><span class="sb">            END;
</span></span></span><span class="line"><span class="cl"><span class="sb">        $$ LANGUAGE plpgsql IMMUTABLE;
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por exemplo, o primeiro comando diz: &ldquo;se já não existir, crie a extensão pg_trgm&rdquo;, que é a extensão que dá suporte a tokenizar textos em trigramas. Em seguida, &ldquo;crie ou substitua caso já exista, a função generate_searchable, que recebe os parâmetros nome, apelido e stack, todos de tipo varchar&rdquo;. Lembram? Varchar é variable characters, uma lista de caracteres de tamanho variável, basicamente uma string.</p>
<p>A função retorna um valor de tipo texto, e esse texto vai ser a concatenação dos campos nome, apelido e stack, tudo junto. Mas pra que isso? Paciência que já vai entender. Na sequência, caso não exista, criamos uma tabela com nome &ldquo;pessoas&rdquo;, que é a tabela principal. Primeiro campo se chama &ldquo;id&rdquo; de tipo UUID. Opcionalmente podemos declarar que se não passarmos um valor explicitamente, pra gerar um uuid aleatório usando a função gen_random_uuid(), além disso a constraint é que esse campo deve ser único e não nulo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="n">pessoas</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">id</span><span class="w"> </span><span class="n">uuid</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="n">gen_random_uuid</span><span class="p">()</span><span class="w"> </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">apelido</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">nome</span><span class="w"> </span><span class="nb">TEXT</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">nascimento</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">stack</span><span class="w"> </span><span class="n">JSON</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">searchable</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="k">GENERATED</span><span class="w"> </span><span class="n">ALWAYS</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="n">generate_searchable</span><span class="p">(</span><span class="n">nome</span><span class="p">,</span><span class="w"> </span><span class="n">apelido</span><span class="p">,</span><span class="w"> </span><span class="n">stack</span><span class="p">))</span><span class="w"> </span><span class="n">STORED</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em seguida, declaramos o campo apelido como sendo tipo texto, também único e não nulo. Campo nome deve ser tipo texto e não nulo. Campo nascimento deve ser tipo Date e não nulo. Campo stack deve ser tipo JSON, que é uma das vantagens de usar Postgres, porque ele permite tipos de dados mais complexos como JSON. Não precisa de MongoDB pra isso. E, finalmente, declaramos uma coluna especial, de nome &ldquo;searchable&rdquo; e tipo text.</p>
<p>Só que esse campo declaramos como um campo &ldquo;generated&rdquo;. É um campo cujo valor vai ser gerado automaticamente em toda operação de insert ou update e vai usar o retorno desta função que declaramos antes, o generate_searchable. Lembram o que ela faz? Devolve os valores de nome, apelido e stack concatenados. É isso que esse campo vai ter. Mas por que?</p>
<p>O próximo comando SQL é, caso não exista, pra criar um novo índice chamado idx_pessoas_searchable, na tabela pessoas, usando GIST. Ao GIST dizemos pra indexar aquela coluna &ldquo;searchable&rdquo; usando o operador de classe &ldquo;gist_trgm_ops&rdquo;, que é especificamente desenhado pra tokenizar esse campo em trigrams, que são conjuntos de 3 caracteres, quebrados, ou tokenizados, de cada palavra da string. É isso que permite pesquisas parciais.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="n">idx_pessoas_searchable</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="k">public</span><span class="p">.</span><span class="n">pessoas</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">gist</span><span class="w"> </span><span class="p">(</span><span class="n">searchable</span><span class="w"> </span><span class="k">public</span><span class="p">.</span><span class="n">gist_trgm_ops</span><span class="w"> </span><span class="p">(</span><span class="n">siglen</span><span class="o">=</span><span class="s1">&#39;64&#39;</span><span class="p">));</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O objetivo dessa coluna é evitar ter que fazer nome like blabla &ldquo;OR&rdquo; apelido like blabla &ldquo;OR&rdquo; stack like blabla. Não precisamos mais de &ldquo;OR&rdquo;. Basta fazer searchable like blabla e pronto. Se qualquer um desses campos conter o blabla, vai retornar. &ldquo;OR&rdquo; custa caro, sempre que puder evitar, evite, mesmo que signifique desperdiçar espaço. Pode ser um tradeoff que vale a pena. Pesquisa com porcento, wildcard, também custa caro. Agora vamos ver a função no projeto do Lucas pra pesquisa de termos. Fica mais pra baixo, a função &ldquo;findByTerm&rdquo;.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span><span class="p">.</span><span class="nx">findByTerm</span> <span class="o">=</span> <span class="kr">async</span> <span class="kd">function</span> <span class="nx">findByTerm</span><span class="p">(</span><span class="nx">term</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">    SELECT
</span></span></span><span class="line"><span class="cl"><span class="sb">        id,
</span></span></span><span class="line"><span class="cl"><span class="sb">        apelido,
</span></span></span><span class="line"><span class="cl"><span class="sb">        nome,
</span></span></span><span class="line"><span class="cl"><span class="sb">        to_char(nascimento, &#39;YYYY-MM-DD&#39;) as nascimento,
</span></span></span><span class="line"><span class="cl"><span class="sb">        stack
</span></span></span><span class="line"><span class="cl"><span class="sb">    FROM
</span></span></span><span class="line"><span class="cl"><span class="sb">        pessoas
</span></span></span><span class="line"><span class="cl"><span class="sb">    WHERE
</span></span></span><span class="line"><span class="cl"><span class="sb">        searchable ILIKE $1
</span></span></span><span class="line"><span class="cl"><span class="sb">    LIMIT 50;`</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">pool</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="nx">query</span><span class="p">,</span> <span class="p">[</span><span class="sb">`%</span><span class="si">${</span><span class="nx">term</span><span class="si">}</span><span class="sb">%`</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Olha só, definimos o template da query com um string. Select os campos id, apelido, etc, from tabela pessoas, where searchable like $1, limitado a 50 linhas de resposta. Esse &ldquo;$1&rdquo; vai ser substituído por esse valor aqui embaixo, que é o termo concatenado com porcentagem no começo e no fim. Ué, mas isso não era lento de pesquisar? Vamos entender.</p>
<p>Todo banco de dados relacional possui um comando pra explicar como que uma query vai ser executada, a estratégia de pesquisa. Pra ver isso, podemos conectar com o cliente de linha de comando do postgres, o psql. Então podemos nos conectar com &ldquo;docker exec -it node-postgres-1 psql -U postgres -d postgres&rdquo; e pronto.</p>
<p>Vale lembrar que estou conectando no postgres logo depois de rodar o teste de carga, portanto a tabela já está com mais de 46 mil linhas inseridas com sucesso. Vamos começar analisando uma query simples. Eu mando &ldquo;explain analyze&rdquo; seguido da query &ldquo;select * from pessoas where apelido = &lsquo;foo&rsquo;&rdquo;. Como esperado, essa coluna apelido já tem um índice, então ele corretamente diz que vai fazer um &ldquo;index scan&rdquo; no índice &ldquo;pessoas_apelido_index&rdquo;. Pesquisa utilizando índice costuma ser muito rápido e podemos ver o Execution Time aqui no final dizendo que levou só 0.037 milissegundos, 37 microssegundos, super rápido.</p>
<p>Esses outros números aqui em cima, por exemplo em cost, ou custo, o primeiro é o custo relativo pra puxar a primeira linha do resultado, esse 0.41 e esse 8.43 é o custo relativo pra puxar o resto das linhas. Mesma coisa esse actual time, é o tempo pra puxar a primeira linha e depois o tempo pra puxar o resto das linhas. Uma das razões pra ele distinguir a primeira linha do resultado pro resto é pra dar noção da latência que sua aplicação vai ter entre mandar fazer a pesquisa e o primeiro resultado que volta no streaming e que já poderia ser mostrado.</p>
<p>Como esta query usa um índice e a quantidade de linhas é absurdamente pequeno, leva 16 microssegundos até puxar a primeira linha e pouco mais de 16 microssegundos pra puxar todo o resto. É muito rápido mesmo. Mas agora vamos ver quão mais lento seria a pesquisa por termos do jeito que a grande maioria dos programadores iniciantes faria. Olha só: <code>explain analyze select * from pessoas where apelido ilike '%foo%' or nome ilike '%foo%' or stack::text ilike '%foo%'</code>.</p>
<p>Olha o tempo que leva pra executar essa query: 42 milissegundos, ou 42 mil microssegundos. Comparado com a query simples anterior estamos falando de um tempo de resposta mais de mil vezes mais lento. Sim, 42 milissegundos ainda é relativamente rápido, mas considerando que eu falei que essa tabela é absurdamente pequena, e queries baseadas em índice conseguem acabar na ordem de microssegundos, 42 milissegundos é um tempo longo.</p>
<p>Olha só a estratégia que ele diz que essa query vai ser obrigada a usar: &ldquo;seq scan&rdquo; que é scan sequencial, literalmente um full table scan. Imagine um loop com for indo da linha 0 até a linha 46 mil e tanto e checando os campos linha a linha. É o pior caso pra uma pesquisa em banco, que é checar tudo, sem poder usar índices. Você quer evitar esse tipo de query como diabo foge da cruz, porque quanto maior ficar sua tabela, mais lento sua pesquisa vai ficar. Com índice não, você pode dobrar o tamanho da tabela e o tempo da query quase não muda.</p>
<p>Será que tudo isso que fizemos de criar o campo searchable concatenando os campos nome, apelido e stack e depois indexando usando Gist com Trigrams faz alguma diferença? Não podemos só chutar e dizer que sim sem testar. Então vamos lá: explain analyze <code>select * from pessoas where searchable ilike '%foo%'</code> e olha só, é mais rápido sim.</p>
<p>Caímos de 42 milissegundos pra só 4 milissegundos. É pelo menos 10 vezes mais rápido. E olha a estratégia de pesquisa. Não tem mais um sequential scan como antes. Em vez disso tempos um Bitmap Heap Scan. Essa estratégia é dividida em duas e começa primeiro com um Bitmap Index Scan, que vai usar o índice que fizemos usando Gist com Trigram. Nessa fase teremos uma certa quantidade de linhas de resultado. Essas linhas vão ser marcadas num mapa de bits, um bitmap, e na segunda fase de pesquisa, ela vai filtrar só as linhas marcadas, usando essa condição de rechecagem que mostra na estratégia.</p>
<p>Ou seja, ainda tem uma fase de scan de linhas, mas não é na tabela inteira, só nas linhas retornadas na primeira etapa de pesquisa pelo índice. Neste caso, o índice sozinho não dá a resposta toda, por isso existe o Bitmap Heap Scan. É mais lento do que um Index Scan, como no primeiro exemplo que testamos, mas é bem mais rápido do que um full sequential scan.</p>
<p>Portanto sim, valeu a pena tudo isso de GIST. E é uma boa dica pra qualquer projeto. Em projetos maiores, onde estaríamos usando coisas como cache em Redis, também poderíamos delegar pesquisa full text pra outro serviço externo. Eu sempre recomendo o bom e velho ElasticSearch. O Elastic é como se fosse um banco de dados NoSQL especializado em full text search. Povo de monitoramento e devops também costuma integrar ElasticSearch com serviços como Kibana, Logstash, pra fazer pesquisa em logs, por exemplo.</p>
<p>Elastic é pra quando seu projeto crescer e recursos como GIST já não estarem segurando a carga de processamento no banco. Nunca faça overengineering nos primeiros dias de um projeto. Só especialize em serviços externos quando tiver carga pra justificar isso. Quanto mais serviços externos, mais difícil vai ser pra dar manutenção em tudo. É um trade-off. Tudo tem seu tempo, e não é no dia 1.</p>
<p>De qualquer forma, vocês começaram a entender? Uma operação simples SQL num Postgres sem tuning nenhum, rodando na minha máquina, é da ordem de microssegundos. Muito menos de 1 milissegundo. Bancos de dados foram feitos pra serem rápidos. Assistam meus episódios sobre Árvores e Banco de Dados. É muito mais do que só apendar uma linha nova no final de um arquivo.</p>
<p>Outra técnica que eu tinha deixado pra comentar neste episódio foi sobre o tal SQL Bulk Insert. Muitos dos projetos ganhadores com do Vinicius em Rust ou do André e Albert em C#, fizeram o truque de acumular os inserts ou numa fila externa ou em memória e um job em background ir pegando grupos de inserts e mandando de uma só vez pro banco. Mas realmente não tem muito o que comentar. O conceito é bem simples.</p>
<p>Em vez de toda requisição ter que conectar no banco, mandar um insert, esperar o resultado e daí responder, podemos fazer ele responder sem saber se vai gravar com sucesso ou não. E ir acumulando. Depois que acumular uma quantidade razoável, sei lá, uns 100 inserts, daí manda tudo de uma vez só, dentro de uma única transaction. Só de não ter que criar uma nova transação pra cada insert individual, fora mandar pela rede tudo de uma vez, economiza bastante tempo. Mas como já repeti mil vezes neste episódio, 100 mil tentativas de inserts simples é pouca carga. Acho que pra quase nenhuma dos projetos participantes, realmente precisava ter usado essa técnica, no máximo teria feito pouca diferença não usar.</p>
<p>Bulk insert e também upserts são técnicas importantes. Povo de data warehouse, business intellingence, data science e tudo mais que precisa limpar dados e carregar volumes grandes de uma só vez, sabe que precisa fazer bulk insert, senão desperdiça tempo. Cada banco de dados tem peculiaridades de sintaxe, por isso é melhor que vocês pesquisem no google &ldquo;postgres bulk insert&rdquo; ou &ldquo;postgres batch insert&rdquo; que os primeiros links explicam direitinho. Não vou ficar repetindo o que já tem fácil em tutoriais simples.</p>
<p>Mais importante é voltarmos pro docker compose e falar da configuração de postgres. Cada participante da rinha fez essa parte de maneiras ligeiramente diferentes, mas no geral todo mundo estava preocupado em mudar este parâmetro: max_connections. A maioria chutava nessa faixa de 450 conexões. Lembram que uma conexão custa uns 2 megabytes? Se realmente usarmos todas as 450 conexões máximas, o container de postgres poderia usar quase 900 megabytes. Isso é coisa demais, considerando que temos só 3 gigabytes pra caber tudo. Por isso não adianta querer subir esse número todo.</p>
<p>Tem algumas formas diferentes de mudar essa configuração. A mais simples é desse jeito mesmo: mudando a linha de comando do postgres e adicionando o parâmetro direto. Outra forma é que nem fizemos com o nginx e criar um arquivo postgres.conf pra mapear como volume pra dentro do container. E outra forma é como criamos a tabela e índices: direto na aplicação, quando ela se conectar pela primeira vez, manda comandos SQL, e um desses comandos seria pra mandar aumentar o máximo de conexões.</p>
<p>Mas a pergunta mais importante é: quantas conexões de fato precisa suportar pro tipo de carga que nosso teste de Gatling vai executar? 450 é mesmo suficiente? Ou menos daria? E se for menos, quanto menos? Se configurar o pool de conexões da aplicação pra deixar já aberto 450 conexões, estaria desperdiçando muita RAM. Se alocar poucas conexões, daí as primeiras requisições vão gastar mais tempo esperando abrir novas conexões. Tem um balanço aqui, e justamente testes de carga servem pra validar se nossas premissas realmente valem na prática.</p>
<p>Pra facilitar nossa vida, já vimos que dá pra conectar na linha de comando do cliente psql pra fazer queries direto no banco. Mas podemos adicionar algumas ferramentas mais visuais pra facilitar tanto sua vida, quanto minha vida aqui de mostrar. A que deixei pra mostrar por último é o pgAdmin, um velho de guerra que usamos faz mais de década, desde antes de Docker ter sido inventado. É muito simples, basta adicionar este serviço no seu docker compose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@admin.com
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_LISTEN_PORT: 5050
    network_mode: host
    depends_on:
      - postgres</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Email e senha tanto faz, é só porque vai subir uma interface web de administração. Importante é declarar essa porta 5050, porque senão ele vai tentar subir na porta 80, e como estamos em modo de rede host, vai tentar subir na porta 80 de verdade do localhost, e não vai ter permissão sem ser root, então vai falhar no boot. Mude pra uma porta acima de 1000 qualquer, como 5050.</p>
<p>Agora podemos subir &ldquo;docker compose up&rdquo; e esperamos alguns segundos. Vamos acelerar e pronto, no navegador do lado podemos carregar localhost 5050 e olha só que bonito. Só colocar o mesmo email e senha que configuramos e estamos dentro. Depois fucem essa interface e leiam a documentação no site deles pra saber o que é o quê. Mas já deixo um aviso importante: apesar de útil, nunca, jamais, deixe um serviço como esse exposto na internet pública.</p>
<p>Seria uma tremenda burrice deixar isso desprotegido porque se alguém conseguir entrar, vai ter acesso ao seu banco de dados de produção. Nada de nenhum banco de dados de produção jamais pode aparecer na internet pública. É pedir pra ser invadido. Suba temporariamente em produção pra alguma tarefa em específico, mas retire do ar o quanto antes. Use em ambiente de staging, em ambiente de desenvolvimento, em rede privada da empresa, dentro de uma DMZ, mas nunca na internet pública. Sem mais, nem menos, vocês foram avisados.</p>
<p>Enfim, eu já tinha cadastrado o postgres que estávamos usando nos testes, mas deixa eu cadastrar de novo pra vocês verem como é. Dêem um nome qualquer, endereço é localhost, usuário é postgres, senha é a mesma que você configurou no docker compose e pronto, conectado. Mas e daí, o que podemos fazer com isso? Vamos no terminal do lado e rodar o teste de carga do Gatling de novo.</p>
<p>Aqui estou acelerando na edição, mas olhem só que bacana, conseguimos ver em tempo real o monitoramento de coisas como transações por segundo. Vou avançar mais pro meio do teste. Note como o total de transações está em azul mas temos, mais ou menos, meio a meio entre transações que deram commit ok e transações que deram rollback por ter falhado. Isso é simples de explicar.</p>
<p>Lembram que o teste de carga faz um total de umas 100 mil requisições, mas só 46 mil são de fato gravadas? Os outros dão erro porque campos como apelido, nome tem constraints pra serem unique, nunca duplicar. Quando tentamos inserir duplicado, rejeita e dá rollback. Por isso, mais ou menos, meio a meio.</p>
<p>Embaixo podemos ver gráficos pra ter noção do volume de dados entrando e saindo. Cada conjunto de campos, a linha em si, é uma tupla. Temos tuplas entrando, que são os inserts, temos tuplas saindo, que são resultados de queries, temos gráfico de I/O que não tenho certeza da unidade de medida, se são operações de leitura e gravação ou quantidade de bytes ou algo assim.</p>
<p>Mais importante é este primeiro gráfico no topo. Acompanhe em azul, é o total de conexões no banco neste momento. Em verde são conexões onde estão acontecendo operações, como nossos inserts ou selects. Em vermelho são conexões abertas sem fazer nada. Veja como é mais ou menos estável a quantidade de conexões, mesmo sabendo que no teste de carga estamos aumentando gradativamente o número de usuários por segundo, subindo até 600 usuários por segundo.</p>
<p>E essa é a importância de um bom pool de conexões. A mesma conexão é reusada, reciclada, entre várias requisições. Por isso não precisa de tantas. Só bem no finalzinho, bem no final mesmo, quando caímos no território do 95th percentile, talvez 75th percentile, aquele 5 a 15% que demora mais de 1 segundo pra responder, aí aumenta drasticamente o número de conexões abertas ao mesmo tempo. Nesse finalzinho dá impressão que a requisição no Node está tão lenta que não dá tempo de devolver a conexão pro pool, daí precisa ficar abrindo mais e mais conexões.</p>
<p>Essas últimas requisições que demoram mais de 5 segundos, são completamente fora da realidade. Se num produto real estivéssemos tendo tempos nessa ordem de grandeza com frequência, ou precisava ver se falta otimizar alguma coisa, ou precisa urgentemente aumentar os recursos da infra. Não devemos configurar o máximo de conexões no banco, nem o tamanho do pool de conexões baseados nesse pico final.</p>
<p>Ao contrário do que muitos poderiam imaginar, durante quase o teste de carga todo, nunca passou de 40 conexões, ativas ao mesmo tempo, era faixa de 20 conexões. Não é 450, nem 300, nem mesmo 100. Só mesmo no finalzinho, mas aí já era repescagem. Portanto, se alguém chutava que máximo de conexões ao banco seria um gargalo, chutou errado também.</p>
<p>E porque são menos de 40 ao mesmo tempo? Se voltarmos ao docker compose, na configuração da aplicação, veja na seção de variáveis de ambiente. Tem esse DB_POOL que passamos pro pool da biblioteca &ldquo;pg&rdquo; usada pelo Node. Neste caso está 35. No caso temos duas instâncias, então seria um máximo de 70. Mesmo assim, durante a maior parte do teste, nenhum dos dois pool nunca é saturado. Só no final. Aí meio que não importa muito se tivéssemos colocar mais ou não. O pico final a infra não aguenta mesmo.</p>
<p>E qual é o melhor número pra colocar no pool de conexões? Cada blog post e stackoverflow que procurar vai vir com alguma fórmula mágica pra tentar chutar. Mas não tem jeito, pra saber de verdade, só fazendo um teste de carga, monitorando via pgAdmin e, via tentativa e erro, rodar o teste de carga pra diferentes valores de tamanho de pool. Esse é o único jeito certo de saber se estamos chutando um número apertado demais ou sobrando demais. Não acredite em nada a não ser testes que você mesmo rodou pra saber.</p>
<p>Isso tudo dito, muitos de vocês assistindo que tem experiência, incluindo vários dos participantes, e eu mesmo, tínhamos certeza que o Postgres seria um problema. Mas agora que medimos e temos dados de verdade, temos certeza que estávamos errados. As técnicas em si funcionam, mas a aplicação e a carga do teste eram muito menores do que imaginávamos. Superestimamos o problema e fizemos overengineering. Típico de programadores fazer overengineering. A lição é simples: faça o código mais simples que resolve o problema dado, faça medições e, só depois disso, decida onde realmente precisa otimizar, e vá medindo pra ver se a otimização realmente melhorou, ou se não piorou.</p>
<p>E isso nos leva aos tópicos finais que faltou discutir da minha lista de pendências. Eu falei que ia comentar sobre Crystal, sobre Kotlin, daí sobre threads e fibers. Mas quero ser breve nesses pontos. Durante o video de 16 linguagens eu quis mostrar que sim, com praticamente todas as linguagens, era possível bater o máximo do teste de carga. Alguns com um pouco mais de trabalho que outros, mas todo mundo era capaz de passar ou chegar muito perto.</p>
<p>Significa que todas as linguagens são igualmente válidas? Ou igualmente performáticas? Claro que não. Vamos usar o bom senso aqui. Obviamente, todas as linguagens compiladas em binário nativo levam vantagem em performance, Rust e Go, principalmente, seguido de C++, Java e seus derivados como Kotlin ou Scala, C#, Crystal, Zig, até Bun. Essa é uma categoria clara de performance nível A.</p>
<p>Linguagens interpretadas, como PHP, Javascript e seus derivados como Typescript, Ruby, Python, todos fazem parte da mesma categoria B de performance. Não é possível atingir o mesmo nível de performance bruta da categoria A. É sim, possível escalar aplicações web pra números parecidos, ao custo de um pouco mais de hardware. Isso porque boa parte de uma aplicação web depende I/O, e I/O vai ser mais lento independente se a aplicação é compilada ou interpretada.</p>
<p>Portanto sim, a infra pra linguagens da categoria B precisa ser um pouco maior do que da categoria A. Mas não é ordens de grandeza maior, esse é o ponto. Do outro lado da equação está a facilidade ou em encontrar bons profissionais ou treinar novos profissionais, e é aí que linguagens da categoria B ultrapassam muito os da categoria A.</p>
<p>Pergunte a qualquer entusiasta de Rust e ele vai dizer que eu estou equivocado, que Rust é muito fácil, que ele aprendeu super fácil. Mas sinto dizer: eu acredito em você, mas seu perfil é diferente da maioria da população. As pessoas não são iguais. Quem acha ficar fluente em Rust algo &ldquo;fácil&rdquo; é uma fração minúscula da população. A grande maioria não tem nem a vontade e nem a capacidade pra ficar fluente em linguagens de baixo nível, que exigem um entendimento muito mais detalhado de como tudo por baixo funciona, a rede, o sistema operacional, o hardware.</p>
<p>E isso não é algo inerente só a Rust. Nos anos 80 e 90 nós sempre tivemos, Assembly, C, C++, Objective-C. Mas quais foram as linguagens que ficaram populares? Nos anos 80 eram coisas como Cobol ou dBase. Nos anos 90 eram coisas como Visual Basic ou Delphi. Delphi até era compilado nativo, mas o que atraiu as pessoas era o ferramental, a IDE, o banco de dados Interbase que vinha no pacote. Ninguém usava Delphi pra programar drivers de sistema operacional ou coisas assim.</p>
<p>Agora é a mesma coisa. Tem uma categoria de pessoas que desde sempre achava fácil programar em C, C++, até Assembly, e agora Rust, Go. Pra realmente tirar proveito desse tipo de linguagem, você precisa ser o tipo de pessoa que gosta de aprender intimamente como funciona o hardware por baixo, o sistema operacional inteiro por baixo. Pra categoria B, uma pessoa que não está nem aí e nem sabe a diferença entre memória RAM e SSD, ainda consegue produzir alguma coisa. A curva de aprendizado é nitidamente menor e por isso tem mais gente nessas linguagens da categoria B.</p>
<p>Duh, antes que apareça comentário idiota: óbvio que tem programadores avançados na categoria B também. Mas alguém medíocre, não sobrevive na categoria A. Portanto, é sim possível fazer uma web startup inteira só com Rust ou Go, mas boa sorte em manter uma equipe de dezenas ou centenas de pessoas que escala junto com o crescimento da empresa. Em breve não vai acompanhar. Por isso toda grande empresa de tecnologia usa um misto de tecnologias. Não existe empresa gigante que consegue usar uma única linguagem pra tudo.</p>
<p>E isso sem contar o ecossistema. Produtividade não vem só da linguagem. Vem das bibliotecas, produtos, serviços, documentação, cursos, livros, eventos e tudo mais que circula ao redor da linguagem. É muito mais difícil comparar uma linguagem e dizer só &ldquo;ah, é compilado&rdquo; ou &ldquo;ah, é interpretado&rdquo;. Essa é a parte que menos importa na comparação.</p>
<p>Tem uma questão cultural também. Deixa eu dar um exemplo bem besta. Todo mundo que é de Javascript está acostumado a ir no terminal e digitar algo como &ldquo;npx create-react-app my-app&rdquo;. Quem é de Ruby on Rails está acostumado a ir no terminal e digitar &ldquo;rails new my-app&rdquo;. Quem é de Python está acostumado a fazer &ldquo;django-admin startproject my-app&rdquo; e assim por diante.</p>
<p>Como que alguém de Java inicia um novo projeto? Vai no site start.spring.io, preenche um fucking formulário, que vai gerar o projeto e te dar um zip pra baixar. Daí você manualmente deszipa e esse é o começo do seu projeto. Quase em 2024, pra mim isso tem cheiro de 2004. Outras linguagens como C# tem opções de linha de comando com dotnetcore, mas como a maioria dessa comunidade prefere realmente fazer? Sim, abrir o Visual Studio Enterprise e clicar em Novo Projeto e ir preenchendo.</p>
<p>Pra gente de Javascript, Ruby, Python, PHP, isso parece super ultrapassado. Mas pra quem é de Java ou C#, parece uma super frescura ter uma linha de comando pra gerar as coisas se o site ou a IDE já fazem tudo. E é aquele caso que depende do ponto de vista. Nenhum dos dois lados nunca vai concordar nisso. Mas é esse tipo de discordância em ponto de vista que vai garantir que nunca tenhamos uma única linguagem e um único ecossistema que vai ser o padrão pra tudo. Eu detesto ter que usar uma IDE. Do que ter que usar IntelliJ ou Visual Studio, eu prefiro mudar pra qualquer linguagem que eu possa ser produtivo só da linha de comando, usando NeoVim. Não é negociável pra mim.</p>
<p>Eu já fiz um video só explicando as diferenças entre linguagens compiladas e interpretadas e explico mais a fundo sobre essas coisas, recomendo que assistam depois. Mas sobre Crystal e Kotlin em particular que eu analisei na rinha. Ambos são linguagens mais novas, com comunidade pequena. No caso de Kotlin, ele já foi criado por uma empresa grande, a JetBrains e depois apoiado pelo próprio Google. E mesmo assim a adoção não é tão grande quanto eu gostaria de ver a esta altura.</p>
<p>De novo, quem está acostumado só com Java não vê grande valor em migrar pra Kotlin. No final o bytecode é compatível, pra que mudar? Pra nós que estamos acostumados com linguagens mais modernas, o Kotlin é uma excelente opção pra quando formos obrigados a fazer alguma coisa na plataforma Java. Vale a pena ficar de olho e ter no seu arsenal. Mas sempre vai haver essa divisão. Eu sinto que Kotlin é mais atraente pra quem não é de Java, do que pra quem já é experiente em Java.</p>
<p>Crystal é muito menor e bem mais de nicho. Mesmo assim eu acho uma linguagem super competente, estável, com um ecossistema mínimo já funcional. Eu acho a sintaxe de Ruby muito superior à de um Go Lang. Me cancelem, mas é verdade. Porém, obviamente Ruby é uma categoria de performance muito inferior a Go. Mas Crystal é realmente a sintaxe de Ruby com performance de Go, mesma ordem de grandeza. Sim, Go ainda é melhor otimizado e por ser uma linguagem mais primitiva, também é mais rápido de compilar.</p>
<p>Mas pra coisas como microsserviços web, eu acho Crystal e frameworks como o Lucky, bem mais prazerosos de usar do que similares em Go ou Rust. Se produtividade for importante e evitar fadiga também, Crystal pode ser uma boa alternativa, especialmente pra equipes pequenas. A curva de aprendizado é menor. Vale ter no arsenal. E eu reforço isso depois de ter experimentado linguagens como Nim, que existe há muito mais anos, mas continua péssimo de usar, super mal documentado, com poucas bibliotecas usáveis e muitos bugs. Parece uma linguagem experimental, que eu não recomendaria pra usar em produção de jeito nenhum.</p>
<p>Dá pra ficar o dia todo só falando das diversas linguagens, e eu já comentei bastante sobre elas no episódio original da rinha, então assistam lá depois. Mas voltando ao assunto de conexões no banco, só queria deixar um comentário sobre a diferença de linguagens compiladas como Rust e interpretadas como Javascript. Ou mais especificamente, linguagens que tem capacidade de escalar com threads nativas e linguagens que não podem por causa de lock global de interpretador, que todas as linguagens da categoria B tem.</p>
<p>Eu explico isso no episódio de linguagens compiladas e interpretadas. Mas na nossa situação de uso com banco de dados costuma significar o seguinte: além de serem mais performáticos pelo fato de serem compilados, linguagens como Rust ou mesmo Java, tem a vantagem de precisar de menos conexões simultâneas. Um pool de conexões de Java é thread-safe, ou seja, pode ser utilizado por múltiplas threads nativas em paralelo.</p>
<p>Numa linguagem como Javascript ou Ruby, ou qualquer interpretada, cada thread nativa precisa ter seu próprio pool de conexões. Em resumo é mais ou menos assim: em Java poderíamos configurar o pool de conexões pra ter um máximo de 100 conexões, e é isso que a aplicação vai ter no total: 100 conexões.</p>
<p>Já num Javascript, depende. Se rodar com um processo só, vai subir só um pool de conexões, se colocar o máximo de 100, é isso que vai ter: até 100 também. O reactor do Node.js é single threaded. Toda chamada assíncrona roda numa mesma thread nativa e vai acessar um único pool de conexões.</p>
<p>No caso de Node.js em modo cluster, ele pode subir múltiplos forks de processos de Node. Cada um com um pool de conexões separado. Então se for subir 5 forks no cluster, devemos configurar o pool pra ter no máximo 20 conexões, se eu quiser manter o mesmo máximo. 5 vezes 20 é o máximo de 100, entenderam? Por isso não dá pra subir forks infinitos no cluster, porque cada fork vai tendo menos e menos conexões que pode usar no pool.</p>
<p>Estou pulando vários detalhes. Em cada linguagem vai ter alguma peculiaridade na implementação de threads, co-rotinas, pools. Você precisa entender como I/O não bloqueante funciona e como sua linguagem favorita lida com isso. Fica de lição de casa, caso ainda não tenham estudado sobre isso. A dica é começar assistindo meu antigo episódio sobre concorrência e paralelismo pra entender essa base.</p>
<p>Mas o ponto foi só pra vocês saberem que existem diferenças grandes em como se configura coisas como pool de conexões em Java e em Node.js, por exemplo. A configuração de coisas como pool de conexões vai ser diferente. A melhor forma de aprender a diferença é monitorando com um pgAdmin da vida e criando cenários de teste de carga com Gatling ou Vegeta da vida, pra ver na prática quais números funcionam e quais não funcionam.</p>
<p>Quando se é júnior, você faz tudo às cegas, no achismo, achando que vai dar tudo certo. Mas à medida que ganha experiência e vai ficando mais sênior, começa a entender que chutômetro é o caminho mais rápido pra software ruim. Sempre parece que o software faz coisas que você não previa. Pra ganhar controle sobre isso, só tem um caminho: aprendendo sobre benchmarks, profiling, carga, monitoramento, medição, estatística e finalmente começar a enxergar e interpretar corretamente o comportamento do software de acordo com diferentes níveis de carga e estresse.</p>
<p>E com isso, acho que consegui cobrir todos os tópicos que tinha deixado pendente. Não queria fechar o ano com pendências. Com isso posso finalmente dar por encerrado esse assunto de rinha. Vamos começar o ano com temas novos. Em Janeiro de 2024 eu vou tirar férias e passar três semanas no Japão, então acho que não vai ter video em Janeiro. Ainda não sei se volto a publicar antes ou depois do Carnaval, então pode ser ser que eu só volte entre Fevereiro e Março. Aproveitem pra maratonar o canal. Devo ficar postando sobre a viagem no Instagram, então me sigam lá. Se ficaram com dúvidas, mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal, e não deixem de compartilhar o video com seus amigos. A gente se vê em 2024, Feliz Natal e Bom Ano Novo! Até mais!</p>
]]></content:encoded><category>performance</category><category>containers</category><category>bancos-de-dados</category><category>akitando</category></item><item><title>[Akitando] #145 - 16 Linguagens em 16 Dias: Minha Saga da Rinha de Backend</title><link>https://akitaonrails.github.io/2023/09/20/akitando-145-16-linguagens-em-16-dias-minha-saga-da-rinha-de-backend/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/09/20/akitando-145-16-linguagens-em-16-dias-minha-saga-da-rinha-de-backend/</guid><pubDate>Wed, 20 Sep 2023 15:35:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/EifK2a_5K_U"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;A Rinha de Backend que aconteceu em Agosto de 2023 foi muito divertida. Eu só fiquei sabendo quando acabou, mas não quer dizer que não pude me divertir. Hoje quero resumir tudo que eu fiz nos 16 dias seguintes do evento, detalhes sobre os projetos dos participantes, a controvérsia do Ranking de Linguagens, quais os truques por trás dos vencedores, e como você também poderia ser um vencedor!&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/EifK2a_5K_U"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>A Rinha de Backend que aconteceu em Agosto de 2023 foi muito divertida. Eu só fiquei sabendo quando acabou, mas não quer dizer que não pude me divertir. Hoje quero resumir tudo que eu fiz nos 16 dias seguintes do evento, detalhes sobre os projetos dos participantes, a controvérsia do Ranking de Linguagens, quais os truques por trás dos vencedores, e como você também poderia ser um vencedor!</p>
<p>Finalmente vou demonstrar o que significa &ldquo;ser promíscuo&rdquo; com linguagens de programação. Vamos entender porque como de fato ler um ranking. E como podemos fazer TODO MUNDO alcançar o primeiro lugar do Rust!</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:01:41 - CAP 01 - As Regras - Requerimentos da Rinha</li>
<li>00:09:46 - CAP 02 - Os Participantes - Vencedores da Rinha</li>
<li>00:15:25 - CAP 03 - Dia 1: Entrando na Rinha - Minha versão em Ruby on Rails</li>
<li>00:21:08 - CAP 04 - MrPowerGamerBR entra em cena - A chegada dos Piratas!</li>
<li>00:27:55 - CAP 05 - Tentando com Crystal - Aprendendo Lucky Framework</li>
<li>00:34:01 - CAP 06 - Dia 8: Gerenciando Baratie - Fluxo de Restaurante</li>
<li>00:38:37 - CAP 07 - Consertando Meu Rails - Aprendendo com Erros</li>
<li>00:42:54 - CAP 08 - Aprendendo com Node.js - Muita Refatoração</li>
<li>00:49:48 - CAP 09 - Apanhando de Erlang - Segredos de Elixir</li>
<li>00:55:51 - CAP 10 - Explorando Go Lang - Essa foi fácil</li>
<li>00:57:31 - CAP 11 - Dando Moral pra NATS - C# Vencedor</li>
<li>00:58:15 - CAP 12 - Dia 12: Explorando PHP Moderno - De Node a Swoole</li>
<li>01:00:12 - CAP 13 - Elevando Python - Pequeno Erro</li>
<li>01:02:21 - CAP 14 - &ldquo;foi lá, E FEZ&rdquo; - A Saga de Lean4</li>
<li>01:06:28 - CAP 15 - Pré-Feriado: Tentando NIM - A decepção</li>
<li>01:08:47 - CAP 16 - Diário de Bordo: Resumo - Chegando na Grand Line</li>
<li>01:13:05 - CAP 17 - Feriadão! - Encontrando o One Piece??</li>
<li>01:19:16 - CAP 18 - Testando o One Piece! Todos ao Primeiro Lugar!</li>
<li>01:27:16 - CAP 19 - Conclusão: Por que todo mundo não usa Rust?? - Escala de Mercados</li>
<li>01:37:33 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Repo Oficial da Rinha: <a href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3/tree/main"target="_blank" rel="noopener">https://github.com/zanfranceschi/rinha-de-backend-2023-q3/tree/main</a></li>
<li>MrPowerGamerBR: # Os Resultados da RINHA DE BACKEND estão ERRADOS, e eu posso provar <a href="https://www.youtube.com/watch?v=XqYdhlkRlus"target="_blank" rel="noopener">https://www.youtube.com/watch?v=XqYdhlkRlus</a></li>
<li>Raciocínio Automatizado com Leonardo de Moura, Pesquisador na Microsoft Research <a href="https://dev.to/elixir_utfpr/raciocinio-automatizado-com-leonardo-de-moura-pesquisador-na-microsoft-research-4d1k"target="_blank" rel="noopener">https://dev.to/elixir_utfpr/raciocinio-automatizado-com-leonardo-de-moura-pesquisador-na-microsoft-research-4d1k</a></li>
<li>Rinha versão Algebraic <a href="https://github.com/meoowers/rinha"target="_blank" rel="noopener">https://github.com/meoowers/rinha</a>
Repositórios da Sofia <a href="https://github.com/algebraic-sofia?tab=repositories"target="_blank" rel="noopener">https://github.com/algebraic-sofia?tab=repositories</a></li>
</ul>
<h2>Tweets<span class="hx:absolute hx:-mt-20" id="tweets"></span>
    <a href="#tweets" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Tweet: Início da minha versão de Rails <a href="https://x.com/AkitaOnRails/status/1695526606503022923?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1695526606503022923?s=20</a></li>
<li>Tweet: O ranking oficial não é importante, discutir as técnicas, sim <a href="https://twitter.com/AkitaOnRails/status/1695913459202838839"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1695913459202838839</a></li>
<li>Tweet: 1o resultado com Crystal/Lucky <a href="https://x.com/AkitaOnRails/status/1696207752043872433?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1696207752043872433?s=20</a></li>
<li>Tweet: Minhas impressões aprendendo framework Lucky, em Crystal <a href="https://twitter.com/AkitaOnRails/status/1696220345152176478"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1696220345152176478</a></li>
<li>Tweet Leandro: diminuir workers do nginx melhora uso no lado do postgres <a href="https://x.com/leandronsp/status/1696292520106299417?s=20"target="_blank" rel="noopener">https://x.com/leandronsp/status/1696292520106299417?s=20</a></li>
<li>Tweet: Mais discussão das técnicas de otimização como batch insert <a href="https://x.com/AkitaOnRails/status/1696255958328938745?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1696255958328938745?s=20</a></li>
<li>Tweet Leandro: experimentando a teoria de diminuir workers e pool na versão de Ruby, conseguindo quase 30k <a href="https://x.com/leandronsp/status/1696219396316836331?s=20"target="_blank" rel="noopener">https://x.com/leandronsp/status/1696219396316836331?s=20</a></li>
<li>Tweet: contribuindo com o projeto Kiwi e comentando sobre LevelDB <a href="https://x.com/AkitaOnRails/status/1697283886156226818?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1697283886156226818?s=20</a></li>
<li>Tweet: meu mantra da programação <a href="https://x.com/AkitaOnRails/status/1697317688006066432?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1697317688006066432?s=20</a></li>
<li>Tweet: 1o resultado positivo com Crystal <a href="https://x.com/AkitaOnRails/status/1697651647273427429?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1697651647273427429?s=20</a></li>
<li>Tweet: 1o resultado positivo com Rails <a href="https://x.com/AkitaOnRails/status/1698475713920119117?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698475713920119117?s=20</a></li>
<li>Tweet: mexendo na versão Node do Lucas Weis <a href="https://x.com/AkitaOnRails/status/1698565631560282590?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698565631560282590?s=20</a></li>
<li>Tweet: desafio: todas conseguem bater os 40k <a href="https://x.com/AkitaOnRails/status/1698491304366219744?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698491304366219744?s=20</a></li>
<li>Tweet: apanhando da versão Elixir do Gabriel Oliveira <a href="https://x.com/AkitaOnRails/status/1698760144924905474?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698760144924905474?s=20</a></li>
<li>Tweet: revisando a versão Elixir <a href="https://x.com/AkitaOnRails/status/1698809091055874491?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698809091055874491?s=20</a></li>
<li>Tweet Gabriel: consertando o crash de Elixir no meu PC <a href="https://x.com/oliveigah/status/1698866344853151857?s=20"target="_blank" rel="noopener">https://x.com/oliveigah/status/1698866344853151857?s=20</a></li>
<li>Tweet: resolvido problema do Elixir <a href="https://twitter.com/AkitaOnRails/status/1698888695376437677"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698888695376437677</a></li>
<li>Tweet: mexendo no Go do Luan, 29k <a href="https://x.com/AkitaOnRails/status/1698818634397647092?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1698818634397647092?s=20</a></li>
<li>Tweet: fuçando a versão finalista de Dotnet <a href="https://twitter.com/AkitaOnRails/status/1698851765658013900"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698851765658013900</a></li>
<li>Tweet: 1o resultado da versão PHP do Lauro <a href="https://twitter.com/AkitaOnRails/status/1698923556631990453"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698923556631990453</a></li>
<li>Tweet: 1o resultado da versão Python do Ian Cambrea: <a href="https://twitter.com/AkitaOnRails/status/1698966472083517931"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698966472083517931</a></li>
<li>Tweet: Primeiro contato com Lean4 <a href="https://twitter.com/AkitaOnRails/status/1699064101111218212"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1699064101111218212</a></li>
<li>Tweet: Bibliotecas em Lean da Sofia <a href="https://twitter.com/AkitaOnRails/status/1699069812079767739"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1699069812079767739</a></li>
<li>Tweet: Resumo até agora <a href="https://twitter.com/AkitaOnRails/status/1698900197068153027"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698900197068153027</a></li>
<li>Tweet: Resumo das técnicas até agora <a href="https://twitter.com/AkitaOnRails/status/1698903125338226739"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698903125338226739</a></li>
<li>Tweet: Tentando versão em Nim (fracasso) <a href="https://twitter.com/AkitaOnRails/status/1699533869739856257"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1699533869739856257</a></li>
<li>Tweet: versão nova em Java do Junior Leão <a href="https://twitter.com/juniorleaoo/status/1699427149831114850"target="_blank" rel="noopener">https://twitter.com/juniorleaoo/status/1699427149831114850</a></li>
<li>Tweet: versão desclassificada em V Lang do Carlos Silva <a href="https://twitter.com/_insalubre/status/1699498542245544269"target="_blank" rel="noopener">https://twitter.com/_insalubre/status/1699498542245544269</a></li>
<li>Tweet: Leandro explica a lógica de diminuir vazão do nginx <a href="https://twitter.com/leandronsp/status/1699568664859603184"target="_blank" rel="noopener">https://twitter.com/leandronsp/status/1699568664859603184</a></li>
<li>Tweet: resumo das técnicas até agora: <a href="https://twitter.com/AkitaOnRails/status/1698903125338226739"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1698903125338226739</a></li>
<li>Tweet: Vinicius Ferraz descobre e publica network_mode: host <a href="https://twitter.com/AkitaOnRails/status/1700488994323128673"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1700488994323128673</a></li>
<li>Tweet: Reinaldo/Zsantana, atinge Zero knockouts: <a href="https://twitter.com/reijsantana/status/1701000310280573287"target="_blank" rel="noopener">https://twitter.com/reijsantana/status/1701000310280573287</a></li>
<li>Tweet: Chegando de viagem e ajustando todo mundo de novo: <a href="https://x.com/AkitaOnRails/status/1701230737922339143?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701230737922339143?s=20</a></li>
<li>Tweet: Rails bate 45k <a href="https://x.com/AkitaOnRails/status/1701248237586219416?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701248237586219416?s=20</a></li>
<li>Tweet: Ruby do Leandro bate 47k <a href="https://x.com/AkitaOnRails/status/1701423430870954312?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701423430870954312?s=20</a></li>
<li>Tweet: Rails do Lazaro bate 46k <a href="https://x.com/AkitaOnRails/status/1701446727910199459?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701446727910199459?s=20</a></li>
<li>Tweet: Node.js bate 46k <a href="https://x.com/AkitaOnRails/status/1701295880844775425?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701295880844775425?s=20</a></li>
<li>Tweet: Go do Luan bate 46k <a href="https://x.com/AkitaOnRails/status/1701286259459523033?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701286259459523033?s=20</a></li>
<li>Tweet: Bun bate 43k <a href="https://x.com/AkitaOnRails/status/1701630742642471420?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701630742642471420?s=20</a></li>
<li>Tweet: Python bate 46k <a href="https://x.com/AkitaOnRails/status/1701267343521767606?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701267343521767606?s=20</a></li>
<li>Tweet: V Lang bate 46k <a href="https://x.com/AkitaOnRails/status/1701256544904438210?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701256544904438210?s=20</a></li>
<li>Tweet: Consertando Java do Reinaldo, bate 46k <a href="https://x.com/AkitaOnRails/status/1701402351913910693?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1701402351913910693?s=20</a></li>
<li>Tweet: C++ do Lucas William bate 46k <a href="https://x.com/AkitaOnRails/status/1702008261736534294?s=20"target="_blank" rel="noopener">https://x.com/AkitaOnRails/status/1702008261736534294?s=20</a></li>
<li>Tweet: Refatorando o Rust do Vinicius <a href="https://twitter.com/AkitaOnRails/status/1701816747274260698"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1701816747274260698</a></li>
</ul>
<h2>Pull Requests<span class="hx:absolute hx:-mt-20" id="pull-requests"></span>
    <a href="#pull-requests" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>PR versão Node.js <a href="https://github.com/lukas8219/rinha-be-2023-q3/pull/1"target="_blank" rel="noopener">https://github.com/lukas8219/rinha-be-2023-q3/pull/1</a></li>
<li>PR versão Node.js, network mode <a href="https://github.com/lukas8219/rinha-be-2023-q3/pull/2"target="_blank" rel="noopener">https://github.com/lukas8219/rinha-be-2023-q3/pull/2</a></li>
<li>PR versão Go Lang <a href="https://github.com/luanpontes100/rinha-de-backend-2023-q3-golang/pull/1"target="_blank" rel="noopener">https://github.com/luanpontes100/rinha-de-backend-2023-q3-golang/pull/1</a></li>
<li>PR versão Go Lang, network mode <a href="https://github.com/luanpontes100/rinha-de-backend-2023-q3-golang/pull/1"target="_blank" rel="noopener">https://github.com/luanpontes100/rinha-de-backend-2023-q3-golang/pull/1</a></li>
<li>PR versão PHP/Swoole <a href="https://github.com/lauroappelt/rinha-de-backend-2023/pull/1"target="_blank" rel="noopener">https://github.com/lauroappelt/rinha-de-backend-2023/pull/1</a></li>
<li>PR versão Python/Sanic <a href="https://github.com/iancambrea/rinha-python-sanic/pull/1"target="_blank" rel="noopener">https://github.com/iancambrea/rinha-python-sanic/pull/1</a></li>
<li>PR versão de Lean4 fix de Location: <a href="https://github.com/meoowers/rinha/pull/1"target="_blank" rel="noopener">https://github.com/meoowers/rinha/pull/1</a></li>
<li>PR versão de Lean4 fix de data: <a href="https://github.com/meoowers/rinha/pull/2"target="_blank" rel="noopener">https://github.com/meoowers/rinha/pull/2</a></li>
<li>Versão em Nim/Jesper: <a href="https://github.com/akitaonrails/rinhabackend-nim-jester-api"target="_blank" rel="noopener">https://github.com/akitaonrails/rinhabackend-nim-jester-api</a></li>
<li>Versão em Java/Quarkus: <a href="https://github.com/zsantana/rinha-backend-by-bruno-borges/tree/main"target="_blank" rel="noopener">https://github.com/zsantana/rinha-backend-by-bruno-borges/tree/main</a></li>
<li>PR versão de C++: <a href="https://github.com/lucaswilliameufrasio/backend-cockfighting-q3-2023/pull/1"target="_blank" rel="noopener">https://github.com/lucaswilliameufrasio/backend-cockfighting-q3-2023/pull/1</a></li>
<li>PR versão de C# do Zan: <a href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3-csharp/pull/3"target="_blank" rel="noopener">https://github.com/zanfranceschi/rinha-de-backend-2023-q3-csharp/pull/3</a></li>
<li>PR versão de Ruby do Leandro: <a href="https://github.com/leandronsp/rinha-backend-ruby/pull/4"target="_blank" rel="noopener">https://github.com/leandronsp/rinha-backend-ruby/pull/4</a></li>
<li>PR versão de Bun: <a href="https://github.com/Met4tron/rinha-bun/pull/1"target="_blank" rel="noopener">https://github.com/Met4tron/rinha-bun/pull/1</a></li>
<li>PR versão de Java do Reinaldo, fix de data: <a href="https://github.com/zsantana/rinha-backend-sem-cache/pull/1"target="_blank" rel="noopener">https://github.com/zsantana/rinha-backend-sem-cache/pull/1</a></li>
<li>PR versão de V Lang, network mode <a href="https://github.com/carlosqsilva/rinha-2023-q3/pull/1"target="_blank" rel="noopener">https://github.com/carlosqsilva/rinha-2023-q3/pull/1</a></li>
<li>PR versão de Rust do Vinicius, refactoring e fix: <a href="https://github.com/viniciusfonseca/rinha-backend-rust/pull/2"target="_blank" rel="noopener">https://github.com/viniciusfonseca/rinha-backend-rust/pull/2</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Se costuma acompanhar a comunidade de desenvolvimento no Twitter, talvez tenha ouvido falar que em agosto de 2023 aconteceu um mini evento online chamado de Rinha de Backend, organizado pelo Zanfranceschi, desenvolvedor experiente, atualmente trabalhando no Nubank.</p>
<p>Eu não conhecia ele e só fiquei sabendo que esse evento aconteceu quase uma semana depois que já tinha acabado. Mesmo se soubesse antes não teria participado porque nunca fui de competições, hackathons ou algo assim. Mas eu gostei muito do que os participantes fizeram e, sem querer, isso tomou boa parte do meu tempo, incluindo várias noites mau dormidas.</p>
<p>Sem sombra de dúvidas este vai ser o video mais trabalhoso que já fiz na história deste canal. Não só este como o próximo. Hoje vou resumir minha saga de 16 dias do pós-rinha avaliando diversos projetos dos participantes. Neste percurso, aprendi muita coisa nova que não sabia ou que achava que fosse ser diferente. Foi uma longa saga, muitas noites mau dormidas, frustração, ansiedade, crises existenciais, suor e lágrimas. É bastante coisa e só o resumo vai dar um video de mais de 1 hora.</p>
<p>Por isso vou pedir muita paciência de vocês. Vai ter várias técnicas importantes que vou mencionar o nome e os efeitos que causam, mas vai ser bem por cima. Se ficarem confusos com termos que parecem complicados, não se preocupem, vou explicar os detalhes no próximo. Também vai ser no próximo que vou falar detalhes das linguagens que vou mencionar. Então segurem as críticas. Hoje quero só compartilhar como foi minha experiência.</p>
<p>Primeiro vou resumir como foi o evento em si, um pouco sobre os projetos que ganharam e o ranking que gerou alguma controvérsia, pelo que ouvi falar. Daí vou contar o que pensei a respeito e como resolvi começar a abrir os projetos dos participantes com uma missão: fazer todo mundo chegar no primeiro lugar!</p>
<p>(&hellip;)</p>
<p>Como disse, eu não participei do evento, nem acompanhei o dia a dia. Acho que o desafio foi lançado primeiro mas ninguém sabia exatamente como seriam os critérios pra definir o vencedor até dias depois. E parte da graça de uma &ldquo;rinha&rdquo; como essa não é ser uma competição tecnicamente, perfeitamente justa, tem muito fator sorte e incerteza envolvido, como em qualquer esporte.</p>
<p>Nem sempre é o melhor atleta técnico que ganha todos os jogos, mas quem aprendeu mais nos jogos anteriores, quem consegue se adaptar rápido e quem teve menos azar. Assim foi esta rinha também, por isso em nenhum momento encarem qualquer coisa que parece uma crítica como uma crítica ao evento e sim como dicas pros próximos. Minha discussão vai ser puramente técnica. A crítica real vai pra quem tentou levar isso a sério demais. Ser torcedor apaixonado de um time é normal, sair xingando e brigando na rua quando vê o cara do outro time é ser um animal irracional imbecil. Vou assumir que estou falando com homo sapiens e não neandertais.</p>
<p>O desafio foi divulgado num repositório de GitHub do Zan que vou deixar o link nas descrições abaixo. Aliás, sempre olhem a descrição de todo video. Eu coloco links pra várias coisas extras pra vocês. Além de ter a descrição de todos os capítulos pra ficar mais fácil achar seções de videos longos como este. Enfim, leiam as instruções do desafio mas em resumo, o objetivo era escolher qualquer linguagem de programação, com ou sem frameworks, implementar 4 endpoints de APIs e configurar pra rodar com Docker Compose numa configuração específica de infraestrutura.</p>
<p>Fazer a API em si, em qualquer linguagem, é super simples. É um endpoint POST &ldquo;/pessoas&rdquo; que recebe um JSON simples com dados como apelido, nome, data de nascimento e stack de programação, um array com elementos como &ldquo;php&rdquo; ou &ldquo;java&rdquo;. É como se fosse o cadastro de um sisteminha de recrutamento ou algo assim. Tem que validar que apelido tem no máximo 32 letras, nome no máximo 100 letras, data de nascimento tem que ser minimamente válido. Inclusive ele não fala que 31 de fevereiro tem que ser inválido, só fala que o formato tem que ser ano, tracinho, me, tracinho, dia.</p>
<p>A lista de stack tem que ser um array sem elementos nulos e cada elemento não podia ter mais que 32 letras. E finalmente, o ID tinha que ser um UUID, que é um tipo de número aleatório. Isso foi um bom requerimento como vou mostrar mais pra frente. Mas em termos de validação era só isso. Se passar dados inválidos temos que devolver o cabeçalho HTTP 400 de bad request.</p>
<p>Ou se voltar erro no insert do SQL por causa de constraints inválidas como nome não poder ser nulo, apelido já existir ou algo assim, tem que devolver cabeçalho HTTP 422 de erro unprocessable entity. A resposta pra requisição de criação precisa ter o cabeçalho HTTP 201 created e um cabeçalho &ldquo;Location&rdquo; com o caminho pra essa pessoa recém criada no formato GET &ldquo;/pessoa/uuid&rdquo;. Isso é comum numa API restful básica.</p>
<p>Esse endpoint devolvido no Location deve conseguir encontrar a pessoa recém criada e devolver o JSON com os dados. Se passar um UUID que não existe, tem que devolver o cabeçalho HTTP de 404 not found. Depois precisava ter outro endpoint GET &ldquo;/pessoas&rdquo; que receba um parâmetro chamado &ldquo;t&rdquo; pra ser um &ldquo;termo de procura&rdquo;, pra pesquisarmos o termo no apelido ou nome ou na stack. Seria tipo fazer um SQL com apelido like &ldquo;%termo%&rdquo; OR nome like termo OR stack like termo. Já vou explicar porque esse seria o pior jeito.</p>
<p>Devolvemos o resultado em JSON com código HTTP 200 ou devolve código 400 de erro se não passar termo nenhum. Não encontrando nada bastava devolver um JSON com array vazio em vez de 404 not found. E finalmente o 4o endpoint era um simples GET &ldquo;contagem-erro&rdquo; que deveria fazer um count na tabela e devolver quantas inserções de fato aconteceram. Esse é o único endpoint que não faz parte do stress test e isso pode se tornar um ponto importante pra alguns truques como vou explicar no próximo video.</p>
<p>Como podem ver, são endpoints super triviais. Se você é um programador experiente tenho certeza que já tem de cabeça como faria na sua linguagem mais fluente. Dá pra fazer rapidinho uma versão que funciona em 1 ou 2 horas se estiver um pouco enferrujado. Mesmo se for bem iniciante, seguindo qualquer tutorial de APIs restful no framework que estiver aprendendo, é pra conseguir fazer em 1 dia. Essa é a ordem de grandeza e talvez sirva pra vocês saberem em que pé estão. Vai demorar mais do que isso se quiser experimentar técnicas diferentes. Teve versão que eu levei uns dois dias pra terminar porque resolvei aprender um framework novo do zero, por exemplo.</p>
<p>No próximo video vou demonstrar como dá pra fazer uma versão simples em poucos minutos, por agora só acreditem que dá. Aliás, vamos começar a listar coisas pro próximo episódio. &ldquo;Mostrar como fazer uma versão simples do zero&rdquo;. Mas agora vem a parte que separa os adultos das crianças: a infraestrutura. Não basta só fazer o código e dizer &ldquo;roda na minha máquina&rdquo;. Tem que rodar dentro das restrições da rinha. Então não é só subir um banco de dados, subir sua aplicação sozinha e acabou.</p>
<p>Segundo as regras, tem que subir duas instâncias da sua aplicação, pra rodar em paralelo. E ambos precisam ficar embaixo de um balanceador de carga, no caso pediram o Nginx. No mundo real aplicações rodam com várias instâncias em paralelo embaixo de um balanceador de um Kubernetes, Seasaw, HAProxy ou várias outras alternativas. Azure tem o Application Gateway, AWS tem o ELB, o Elastic Load Balancing, tem o Traefik, F5 Big-IP e mais. Mas NGINX é fácil e simples o suficiente pra este desafio. Não é o mais rápido, mas também não é o mais difícil de usar.</p>
<p>E aqui começa a parte realmente difícil: uma instância de NGINX, uma instância de banco de dados, que pode ser postgres, mysql ou mongodb, mais duas instâncias da sua aplicação sendo balanceadas, precisam caber em 1 CPU e meio e no máximo usar 3 gigabytes de RAM e só. Essa é a regra da rinha. Fazer a API não é o desafio. Essa limitação que é. Mas tudo bem, fazer caber é fácil, o problema mesmo é que no final esse sistema tem que aguentar um stress test, um teste de carga.</p>
<p>Stress test é um teste automatizado que simula como pessoas ou sistemas consumiriam o seu sistema, mandando requisições http pros endpoints da API. No caso eles escolheram a ferramenta Gatling, que tem versão aberta e uma paga com mais recursos. A versão gratuita é razoavelmente simples de usar e o script pro cenário de carga está no GitHub da rinha. Vamos anotar aqui pra eu explicar &ldquo;O Básico de Stress Test&rdquo; no próximo video.</p>
<p>Lembrando que quando a rinha começou acho que os participantes ainda não tinham acesso a esse script de testes, justamente pra não tentar burlar os critérios. Eu não sei se eles sequer tiveram acesso ao script durante a rinha. Imagino que não, só no final. Vamos assumir que ninguém sabia. Isso que diferencia os participantes da rinha de programadores como eu agora, que tem a oportunidade de avaliar sabendo tudo que eles não sabiam. Não comparem o meu resultado com o deles, isso seria injusto.</p>
<p>Em resumo, você podia escolher linguagem, framework e implementar uma aplicação web que serve 4 endpoints de APIs. Tinha que ser o mais leve possível pra aguentar carregar duas instâncias, mais nginx, mais banco, em 1.5 CPUs e 3GB de RAM, orquestrado por docker compose.</p>
<p>Eu ainda não fiz um video explicando o que é um framework web, então vamos anotar aqui pra explicar no próximo video. Eu já tinha feito um video sobre Docker, recomendo que assistam depois, mas aí também no próximo vou explicar como usar o Docker Composer pra subir todos os componentes de um sistema web na sua máquina também.</p>
<p>A idéia é que os participantes deviam empacotar a aplicação numa imagem de Docker, subir no Dockerhub, postar o arquivo de docker compose como pull request no GitHub da rinha e aí os organizadores iam rodar esse docker compose e aplicar algum teste de carga. Quem passasse, ia pro ranking final.</p>
<p>O ranking foi interessante. Teve 90 participantes. Desses, 39 foram desclassificados por diversas razões. Ou o docker compose tinha algum bug e nem carregava, ou algum dos endpoints da API não respondia como deveria, ou deixaram de implementar alguma coisa que tinha nas regras. Sobraram 51 projetos que funcionaram, mais da metade. Um número respeitável. No final, além de implementar os endpoints corretamente, o resultado pro ranking foi baseado na quantidade de inserts que foram feitos no banco de dados.</p>
<p>Ordenando a lista por essa quantidade de inserts, tivemos os Top 10. Quem ganhou a rinha foi o Vinicius Fonseca, com sua implementação usando Rust, com o framework Actix com Tokio. Em segundo lugar ficou o Leo Vargas com sua versão em Go e Fiber, o framework que usa FastHTTP. Em terceiro ficou o André Marra e Albert Kliemke, que fizeram em dupla uma versão em Dotnet 8 com C#. Em quarto ficou a Isadora Souza, que também usou Go com Gin. Em quinto ficou a dupla algebraic, a Sofia e a Gabi, que fizeram em Lean4, a linguagem mais desconhecida da rinha toda. Depois vou falar delas.</p>
<p>Em sexto ficou o Vinicius Santos que também fez em Rust. Em sétimo ficou o Jean Rodrigues que também fez em Go. Em oitavo ficou o Luiz Picanço, com outra versão em Rust. Em nono lugar &ldquo;e&rdquo; também em décimo ficou o Rodrigo Navarro, que participou com duas versões de Rust, uma feita com o framework Axum e outra com o mais leve Touche que, por ironia, ficou depois da versão em Axum. Aliás, parece que ele fez pelo menos uma dessas versões numa live no canal dele, pra quem se interessar corre atrás pra assistir.</p>
<p>Portanto, nos Top 10 ficou assim: 5 versões em Rust, 3 versões em Go, 1 em Lean e 1 em dotnet. Aí as redes sociais foram à loucura. Primeiro, ninguém achou estranho já que isso confirma a hegemonia do Rust e do Go como as linguagens mais rápidas da atualidade. Pior ainda: o fato de dar um dotnet nos Top 10 mas não um Java, fez um monte de gente ficar ridicularizando o Java. Sabe o que falei do torcedor de time neandertal? Sim, afinal na cabeça deles, Java é uma linguagem velha e ultrapassada, certo? Vamos ver como os neandertais leram o resto do ranking.</p>
<p>Seguindo pros próximos 10. Em décimo primeiro ficou o Gabriel Oliveira com uma versão em Elixir. Alguns ficaram tipo, &ldquo;puts, não era o Elixir que se vendia como rápido? Perdeu até pro dotnet?&rdquo; Em décimo segundo ficou o Lucas William com uma versão em C++. &ldquo;Ué, C++ não era pra ser o mais rápido? Rust e Go são foda mesmo, muito melhor que C++&rdquo;. Em décimo terceiro ficou o Lucas Weis com uma versão em Node.js com Express. &ldquo;Pois é, bem que me falaram que Javascript não é tudo isso que falam, né?&rdquo;</p>
<p>Em décimo quarto ficou o Yuri Gomes que fez uma versão em Bun, antes do lançamento da versão 1.0 alguns dias atrás. E pois é, Bun é só hype mesmo, nem lançou ainda e já é lento. Em décimo quinto ficou o Thales Maciel que fez em Rust também. Coitado, deve ser um júnior que né, não é culpa do Rust. Em décimo sexto ficou um cara de apelido saiintbrisson, que não deixou arquivo de README pra trás com o nome nem links, então nem fui tentar procurar.</p>
<p>Em décimo sétimo ficou o Lauro Appelt com sua versão em PHP. Finalmente uma linguagem que se associa mais com Web clássica. Quem diria, PHP ainda não morreu, mas tá lá embaixo com um pé na cova né? E só em décimo oitavo ficou o Bruno Borges com a primeira versão em Java da rinha. E só pra piorar a situação do Java, ele ficou encostado no décimo novo que foi o Lázaro Nixon com uma versão em Ruby on Rails e o vigésimo do Leandro Proença que também fez uma versão em Ruby. Peraí, Java tá tão ruim agora que fica encostado em Ruby?? Que fase heim?</p>
<p>Eu queria parar no vigésimo, mas só no vigésimo primeiro lugar tivemos o Ian Cambrea com a primeira versão em Python, usando Sanic, na competição. Pelo visto Python também é só hype né? Não ganhou nem de PHP, nem de Ruby e nem de Java. Espero que vocês que acompanharam não tenham pensado desse jeito, porque seria vergonhoso.</p>
<p>Dentre os 51 projetos enviados, 8 foram feitos em Go, 7 em javascript, 6 em Rust, 6 em Java. 5 em PHP. 4 em dotnet. 3 em Python, 2 em Ruby, 2 em Elixir, 1 em C++, 1 em Lean, e o último lugar foi 1 feito em Bash pelo Leandro. Teve uns 3 no ranking que o README não dizia em que linguagem foi feito e nem o link pro Github então também não fui atrás pra saber.</p>
<p>O Lean da Sofia e Gabi foi o mais inesperado, ainda mais por ter entrado nos Top 10 e o último de Bash do Leandro foi o mais inusitado porque acho nem ele esperaria ganhar com isso, mas quis representar e mandou super bem. Sim, Bash de terminal que vocês usam no Linux como shell. Tem gente que roda Doom numa calculadora, porque não uma aplicação web em Bash? Mas nesse caso, como esperado, Bash não tinha como competir mesmo.</p>
<p>Alguns participaram com mais de uma implementação, como o Leandro que fez em Ruby e Bash, o Lauro Appelt com duas versões em PHP, o Rodrigo Navarro com duas em Rust. Não sei como foi a divulgação do Zanfranchesci, mas ele mandou muito bem. Foi uma participação bem variada e ao meu ver cumpriu exatamente o que eu imaginaria de uma rinha: galos de diferentes tamanhos e cores. Rust e Go na frente, Javascript e Java pra trás. É o que a platéia gostaria de ver. Foi show de bola.</p>
<p>Tendo visto os resultados, se no topo tivesse sido uma hegemonia absoluta de Rust e Go provavelmente eu teria aceitado como todo mundo: sim, talvez nessas restrições de infra, só eles mesmo pra aguentar a carga. Mas como tinha um dotnet ali na terceira posição, e meus parabéns pro André e Albert por isso, mas isso chamou minha atenção: não vejo porque dotnet consegue estar nos Top 10 e outra linguagem não, seja Java ou seja Javascript.</p>
<p>Com essa pulga atrás da orelha, minha saga começa na sexta feira, dia 25 de agosto, eu abri o editor e comecei a fazer minha versão. Tava um pouco enferrujado, então levei acho que umas 2 horas pra implementar em Ruby on Rails mesmo. Levou mais tempo do que deveria porque pensei: &ldquo;hum, se é um teste de carga em inserção, talvez o postgres seja o gargalo. Depois do HTTP POST, o teste vai checar se inseriu pelo ID. Não tem como deixar só em memória porque são duas instâncias isoladas. Como tem balanceador de carga, o normal vai ser a inserção cair numa instância e a pesquisa cair na outra instância&rdquo;.</p>
<p>A conclusão imediata que qualquer um chegaria é: vou colocar um cache compartilhado, talvez memcached. Mas como queria já lidar com o que achei que fosse o gargalo, pensei, &ldquo;vou jogar jobs em fila de redis pra inserir em background&rdquo;. Então esquece memcache, vou usar redis tanto como cache quanto fila de jobs. Deve caber nas restrições de RAM porque aí o postgres deve trabalhar um pouco menos e eu divido os recursos.</p>
<p>Pelo que avaliei depois, não fui só eu que pensei assim. Várias das versões fizeram a mesma coisa: usaram redis ou nats como cache e fila de jobs assíncronos. É uma coisa que todos nós, que já temos cicatrizes de guerra, fazemos meio sem pensar e isso é um sério problema. Não existem soluções automáticas. Cada caso é um caso e precisa ser medido antes de implementar qualquer truque. Cache ou filas não deixam nada rápido automaticamente em todo caso. Eles também custam recursos. De novo, vamos deixar anotado aqui pra eu explicar no próximo video, como é essa estratégia de usar jobs e filas pra inserir e porque cache não fazia diferença aqui.</p>
<p>Pelo mesmo motivo achei que fosse ver mais gente usando MongoDB, assumindo que muitos imaginam que é mais rápido, mas do total de 90 projetos só 7 tentaram. E mesmo MySQL só 2 tentaram. A maioria esmagadora acreditou no Postgres por padrão. A conclusão: coisas relativamente fáceis, como adicionar caches e filas, todo mundo faz na frente. Coisas mais complicadas, como mudar o paradigma de armazenamento, aí deixamos pra depois. Eu também escolhei ir de Postgres.</p>
<p>Sendo eu criador de conteúdo que, por 20 anos venho evangelizando coisas como uso de Postgres, cache e jobs assíncronos com Redis, me deu uma ponta de dor na consciência. Mesmo aqui no canal já fiz um video chamado &ldquo;Tornando sua App Web Mais Rápida&rdquo;, onde faço justamente essas recomendações. Eu tento explicar em que casos usar, mas acho que dá pra explicar isso em mais detalhes, por isso deixei anotado pra voltar nesse assunto no próximo video.</p>
<p>Quando terminei minha versão de Rails, com cache, jobs e tudo mais, rodei o stress test. Já imaginava que poderia não ser rápido, mas sempre tem aquela esperança de &ldquo;vai que&rdquo; né? E pra minha decepção não deu mais que uns 15 mil inserts, abaixo ainda das versões de Rails do Lazaro e do Leandro. Foi quando comecei a postar tudo que ia descobrindo no Twitter. Esse foi meu primeiro tweet.</p>
<p>Olha que situação ridícula. Quem gera esses relatórios é a própria ferramenta Gatling, que roda o teste de carga. No próximo video vou mostrar como ele funciona. Nesse gráfico interessa a barrinha verde, olha como tá lá embaixo e a vermelha tá lá em cima. É a quantidade de knockouts ou KOs, de requisições que não conseguiram ser processadas e foram perdidas. Em resumo, só entenda que queremos que verde seja grande e vermelho seja pequeno ou idealmente, zero. Nesse momento eu pensei: &ldquo;é, talvez essa carga seja muito pesada pra uma linguagem interpretada como Ruby numa infra de docker tão apertada&rdquo;.</p>
<p>Resolvi baixar a versão do Leandro que usa Ruby mas sem framework pesado como o Rails. E os resultados dele foram bem melhores que os meus, mas ainda assim tem uma barra vermelha considerável de knockouts. Eu conseguia 15 mil inserts e a versão dele batia acima de 30 mil inserts, na minha máquina. Na minha cabeça essa discrepância fazia sentido. Imagino que façam na de vocês também, afinal quanto menos coisa tiver, mais rápido deveria ficar.</p>
<p>Só pra testar os limites, tirei a restrição de 1.5 CPU e 3GB de RAM e configurei o docker compose pra usar 24 CPUs e 20GB de RAM, afinal meu PC aguenta muito mais que isso. Assim a barrinha verde de fato sobe, mas olha que ainda tem uma barrinha vermelha. Eu ainda estou perdendo requisições, e pára pra pensar, isso não faz sentido, em nenhuma linguagem.</p>
<p>Nesse ponto as coisas não estavam fazendo muito sentido. Bacana, eu sei que Rust e Go são rápidos, afinal são compilados e tals, mas se o problema fosse só falta de recurso, na minha máquina, que é absurdamente potente, não deveria estar dando nenhum knockout. Isso foi aumentando a pulga atrás da minha orelha. A conclusão nunca pode ser &ldquo;a linguagem é uma bosta&rdquo;, a primeira conclusão sempre tem que ser &ldquo;eu estou fazendo alguma coisa errada&rdquo; e ir atrás de descobrir o que.</p>
<p>Resolvi tentar uma outra versão, aproveitar o domingo, dia 27 de agosto. Fazia algum tempo que queria desenferrujar meu Crystal e aprender o framework web chamado Lucky. Ótima desculpa. Deixei a documentação do site aberta de um lado, editor do outro e fui fazendo tentativas. Minha premissa é que Crystal, sendo uma linguagem que compila binário nativo, como Go, deveria ser bem mais rápido que minha versão em Rails. No final gastei o domingo inteiro mais a segunda feira seguinte pra fazer, porque nunca tinha usado Lucky antes.</p>
<p>Vamos anotar isso também pro próximo video: falar um pouco mais das minhas experiências mexendo em linguagens diferentes, mas pra hoje só precisa entender que os criadores do Crystal, o Ary e o Brian, quiseram inventar uma linguagem com sintaxe o mais próximo possível de Ruby, adicionando coisas modernas como inferência de tipos, fibers, channels, mas com perfil de performance similar de Go, e em alguns casos até de Rust.</p>
<p>Foi nesse ponto que fui vendo outros desenvolvedores na comunidade começarem a esmiuçar mais os critérios do ranking, em particular o MrPowerGamerBR. Sendo javeiro ele foi um dos que ficaram sofrendo bullying do povo tirando sarro do Java ter se saído tão mau no ranking. Em vez de só sair dando reply xingando todo mundo, como todo twiteiro faz, ele fez o que um programador de verdade deveria fazer: abrir o código e provar com números.</p>
<p>Ele fez a mesma coisa que eu: começou uma nova versão na linguagem que mais gosta: Kotlin. Pra quem não sabe, Kotlin foi inventado pela JetBrains, a empresa que faz a IDE IntelliJ e o Android Studio. Eu também gosto dela e vou deixar anotado pra comentar a respeito no próximo video. Pra hoje, basta entender que é compatível com Java e tecnicamente oferece a mesma performance.</p>
<p>Dia 28 de agosto ele postou essa versão, que não só melhora a posição de Java, mas iguala na mesma posição Top 1 da versão de Rust do Vinicius Fonseca. Mais do que isso, ele estudou mais a fundo o script de stress test em Gatling e determinou que existe um número máximo de requisições e de inserts possível durante os 3 minutos fixos do teste: 46 mil quinhentos e setenta e seis inserts.</p>
<p>Na verdade vai ser 46 mil quinhentos ou seissentos e alguma coisa porque no script de stress test do Gatling tem essa chamada pra &ldquo;randomized&rdquo; que afeta a quantidade de requisições pra criação de registros. Se rodar o teste várias vezes, vai dar números diferentes. Imagino que o Zanfranchesci fez isso de propósito pra adicionar o fator de &ldquo;sorte&rdquo; no espírito de uma rinha. Portanto o resultado numérico nunca foi pensado pra ser totalmente justo. E antes que os haters apareçam de novo: eu estou super ok com isso.</p>
<p>Mais do que isso, o MrPowerGamerBR olhou com mais calma a versão de Rust do Vinicius. Sim, ele estava inconformado, e nada é mais motivador do que a força do ódio. Ele notou que surgiam inconsistências esquisitas. Às vezes o total dava 300 a mais do que o máximo. Às vezes dava 500 a mais. Como isso poderia ser possível? Não tem como aparecer requisições do nada. Se existe um máximo e passa do máximo, obviamente existe um bug.</p>
<p>E de fato tem. Não foi de propósito, a gente esquece bugs. Por isso sempre falamos: não é porque uma linguagem compila, não é porque uma linguagem tem checagem de tipos, que não precisa de testes. No código do Vinicius tem uma rotina de aquecimento que insere mais de 500 registros falsos, não sabemos porque, mas depois tem um código que deleta isso. Só que esse código tem uma race condition, uma condição de corrida. Um erro clássico em programação concorrente. Ou seja, às vezes a rotina de limpeza roda antes de terminar de inserir tudo, aí sobra registros e isso polui o total no final.</p>
<p>Não chega a ser um problema, porque mesmo se corrigir esse bug, ainda assim a versão dele atinge o máximo, então sempre seria o primeiro. Mas estudando o stress test, estudando o código do Vinicius e também o do Lucas Weis que fez em Node.js, o MrPowerGamer aprendeu que existem truques que poderiam ser usados que a maioria não pensou. Não foram só eles, mas alguns pensaram além do óbvio e conseguiram resultados melhores.</p>
<p>Em 10 de setembro, o MrPowerGamerBR soltou um video detalhando essas descobertas e recomendo que assistam depois. Eu vou explicar tudo que ele falou e muito mais no próximo video. Uma dessas técnicas foi bulk inserts, a outra técnica foi gerar um índice especial pra pesquisas. Vamos deixar anotado aqui pro próximo video: explicar o que são bulk inserts e explicar como fazer pesquisas fuzzy no postgres e os segredos de ajustes de performance.</p>
<p>Se consertar o bug de race condition do código do Vinicius e se tirar o fator de aleatoriedade do stress test, o ranking fica bem diferente. Como o MrPowerGamer mostra nos seus tweets, o primeiro lugar seria compartilhado entre a versão de Rust do Vinicius, que continua sendo o primeiro, mas a versão de Go do Leonardo Vargas também sobe pra primeiro assim como o 6o lugar da Isadora e o 9o lugar do Navarro também virariam primeiro lugar.</p>
<p>Por causa disso o terceiro lugar de dotnet do André e Albert pula pro segundo lugar. Eles raspam no máximo mas não ultrapassam por meros sessenta e cinco inserts. O quinto lugar do Luiz Picanço em Rust sobe pro terceiro lugar. O Navarro, que tinha feito duas implementações em Rust, também sobe a outra versão, do 10o pra quarto. O oitavo lugar do Jean Rodrigues, em Go, sobe pra quinto lugar. Todo mundo vai subindo no ranking.</p>
<p>Mais ainda: sabendo o número máximo de inserts, se alguma versão ultrapassar, sabemos que tem bug nas validações. E isso aconteceu com o quarto lugar em Lean4 da Sofia e da Gabi, e a que ficou em sétimo lugar do Vinicius Santos, em Rust. Ambos ultrapassam os 48 mil e quinhentos e tanto, mas como tinha aquele fator de aleatoriedade no script, na rodada oficial, passaram batido, mas deveriam ter sido desclassificados. E que fique claro que não foi de propósito, que atire a primeira pedra quem nunca deixou um bug pra trás. Como já expliquei, numa rinha tem fator sorte, e está ok.</p>
<p>O problema é que o script de stress test, e o critério de ranking, não tinham sido divulgados desde o começo e nem testam tudo que está nas instruções. Por exemplo, eles deveriam checar as constraints da DDL do SQL que cria a tabela e índices pra garantir que coisas como apelido tem índice de unicidade, que o campo de data de nascimento era realmente de tipo data e coisas assim. Se fizer um campo de nascimento sendo um varchar, um string, e simplesmente inserir o que vier sem checar, no final vai ter mais registros do que deveria.</p>
<p>Se fosse pra ser mais rígido, deveriam ter implementado uma rotina de testes automatizados em Cypress, por exemplo, uma suite de testes de aceitação que testam todas as regras que estão nas instruções. Assim seria possível ver se a aplicação está devolvendo os códigos de erro quando enviamos dados inválidos. Sem isso, vários bugs passam despercebidos. Fica a dica pra uma próxima rinha. E também pra projetos de verdade: sem testes, tudo compila, tudo roda, mas bugs de validação vão passar despercebidos, em todas as linguagens, compiladas, tipadas, ou não.</p>
<p>Vou repetir: não encarem como uma crítica à rinha, o próprio nome &ldquo;rinha&rdquo; é pra ser uma brincadeira. Inclusive, eu acho que se tivesse um teste de aceitação mais rígido assim, teria muito menos participantes e muito mais projetos desclassificados, e meio que perderia a graça.</p>
<p>Mas o que me motivou a gastar tanto tempo nisso é que só olhando o ranking oficial, sem todo o contexto, eu também estava aceitando que Rails ou Java iam ser lentos mesmo e bola pra frente. Mas à medida que fui explorando o que o Vinicius, o Lucas, o Leandro, o MrPowerGamerBR, fizeram nas versões deles, resolvi copiar na minha versão de Crystal, pra tirar a prova dos nove. Não era pra ser difícil. Na minha cabeça, coisa de poucas horas.</p>
<p>Demorei um tanto pra entender o framework Lucky, o Avram que é o ORM que fala com o banco, tive até que abrir o código fonte deles pra entender. Na dúvida, na falta de documentação ou stackoverflow, nunca penso duas vezes e vou ler direto na fonte. Vantagem de usar projetos de código aberto é que o código é aberto, sacaram? É pra ler. E eu fiquei empolgado em tentar implementar o sistema de cache com job assíncrono pra fazer bulk insert como o Vinicius fez em Rust. Sendo Crystal compilado, era pra ter performance similar, né?</p>
<p>Ledo engano. Assim que terminei e fui testar com o Gatling, a decepção. Lembra a barrinha vermelha? Olha ela aqui firme e forte. Minha implementação nem entraria nos Top 20. Meros 20 mil inserts. Só que diferente do que tava pensando sobre Ruby, que é lento por ser interpretado e não tinha o que fazer, ou Java que come muita RAM e não tinha o que fazer, aqui eu fiquei meio puto. A culpa não podia ser do Crystal, nem do Lucky, tinha que ser minha. Eu estava fazendo alguma coisa muito errada. Será que eu sou uma farsa? Será que eu sou tão ruim assim?</p>
<p>Quando gastamos um tempo tentando descobrir um problema num código e depois de &ldquo;ver tudo&rdquo;, ou achar que viu tudo, ainda assim não encontrar, eu sempre tenho uma saída que funciona. Olhem neste código de Crystal. Pra passar o stress test, basta devolver HTTP 201 created e um Location válido pra próxima requisição. Até tinha deixado comentado aqui esse código de teste aqui. Posso descomentar essas duas linhas e comentar todas as de baixo. É isso mesmo, na dúvida, tire tudo e faça a resposta mais simples e idiota, devolver uma constante.</p>
<p>Mas esta Location não vai existir, porque se não inserir nada, não existe ninguém com ID número 1. Então, no show.cr, também tinha deixado esta linha comentada, que sequer tenta fazer a pesquisa no banco, só devolve outra constante. Descomento e comento o resto. Pro teste de consulta passar certo, basta devolver HTTP 200 de OK. Finalmente, na pesquisa por termos, mesma coisa, descomento esta linha pra devolver código 200 e comento o resto.</p>
<p>Só fazendo isso o teste de carga já passa. Esta vai ser a velocidade máxima  desse app, sem processar nada no banco, manjam? Estou removendo uma das variáveis de incerteza. Rodando o Gatling, só esperar 3 minutos e esse é o resultado. Tudo verde, nenhum vermelho de knockout, e olhem aqui do lado, todos os tempos de resposta da ordem de 1 milissegundo. Na verdade é menos de 1 milissegundo, mas ele arredonda. Esse é o tempo perfeito, porque a aplicação não tá fazendo praticamente nada. Ótimo, isso mostra como o Crystal sozinho é rápido.</p>
<p>Agora posso ir voltando meu código, uma linha de cada vez. Salvo, subo o docker-compose, que vai recompilar a imagem, e rodo o Gatling de novo. Checo e vejo se a diferença no resultado condiz com o código extra que coloquei. Volto, descomento mais uma linha, salvo, reinicio docker-compose, testo de novo, meço. E vou fazendo isso, uma linha de cada vez. E como esperado, nenhuma dessas coisas extras deu nada de estranho.</p>
<p>Até eu chegar nesta linha, a que monta a URL de Location. A única coisa que isso vai fazer é gerar uma linha como esta aqui em cima, um http://localhost etc. Claro, este método faz mais do que só concatenar strings, serve pra montar todo tipo de URL complexa que quiser incluindo encoding de parâmetros e tudo mais. Imagina aquelas URLs gigantes da Amazon. Mas eu nunca esperaria isso causar qualquer diferença significativa.</p>
<p>Na realidade, isso tá até errado. Pensei melhor e Location na realidade só precisa devolver o path, só a parte de &ldquo;/pessoas/id&rdquo;. Não precisa do protocolo nem domínio a menos que eu queira redirecionar o cliente consumindo a API pra outro domínio diferente, que não é o caso. Substituindo o método .url pra .path e testando de novo, batata, agora o resultado melhorou.</p>
<p>Eu twitei sobre isso, daí uma conta da comunidade Crystal retwitou e um dos desenvolvedores do framework Lucky, o Jeremy, ficou interessado. Ele foi tão legal que abriu uma issue no projeto. Fui lá explicar esses detalhes. Ele já tinha uma suspeita em mente e me pediu pra testar um ajuste. Este foi meu post no ticket aberto. Quando testei sem o patch dele, tava tomando knockouts, mas colocando o patch, caiu de mais de 600 knockouts pra menos de 10, então tinha alguma regressão de performance mesmo.</p>
<p>Não sei explicar exatamente o motivo ainda. A versão anterior parece que só concatenava strings, e o patch usa tipo um string builder. Concatenar gera duplicatas da string. Um Builder pré-aloca espaço e vai só adicionando as novas partes sem duplicar. É um trade off de CPU por RAM. Eu sei que isso faz alguma diferença, mas não sei ainda explicar porque faria diferença aqui. Mesmo tendo, não deveria ser significativo. Mas de qualquer forma, no número de stress test do Gatling fez muita diferença.</p>
<p>Sem querer, acabei achando um gargalo de performance no framework que levou a um conserto. É assim que a gente acaba contribuindo em projetos open source: sem querer. Mas essa não era a única otimização. Olhando outras versões notei que ninguém tava devolvendo mensagens de erro completas, como &ldquo;apelido já existe&rdquo; ou &ldquo;formato de data inválida&rdquo;. As instruções só falam que precisa devolver código de status 400 ou 422.</p>
<p>Na minha versão, antes, além do código eu devolvia um JSON com o erro. E processar JSON também custa tempo. Tirei todos os métodos de render ou json e usei só o método &ldquo;head&rdquo; que, como o nome diz, devolve só o cabeçalho, o código de erro. É uma coisa besta, que em testes individuais não faria diferença, mas o  Gatling manda centena de milhares de requisições. Aí parece que faz diferença.</p>
<p>Mesma coisa logs. O teste não exige logs. No mundo real precisamos ter mensagens de erro gravadas e retornadas pra ficar mais fácil achar bugs e defeitos, mas pra este desafio de performance que só dura 3 minutos, podemos desligar todos os logs e ganhar alguns milissegundos importantes. E fazendo pequenos ajustes assim, o resultado de inserts de Crystal foi subindo. Dos pífios 15 mil inserts, foi pra 30 mil, daí pra quase 40 mil inserts.</p>
<p>Não bati no máximo mas chegando perto dos 40 mil inserts, já foi um salto muito bom. Quando postei esse resultado já era dia 1o de setembro. Ainda me distraí no processo porque queria uma biblioteca pra auxiliar o caching em Redis mas não tinha nenhuma bem feito. Esbarrei num bem antigo chamado Kiwi que ninguém atualizava fazia tempo, então resolvi arrumar e melhorar o código dela. E essa foi minha segunda pequena contribuição open source.</p>
<p>Enquanto isso, em paralelo, o Leandro e o Lazaro, das versões de Ruby mínimo e de Ruby on Rails, continuaram mexendo nas versões deles. O lázaro que, no ranking oficial da rinha, tinha ficado lá na 19a posição com uma faixa de 24 mil inserts, também saiu ajustando as configurações dele e com muito pouco ajuste já tinha conseguido bater os 40 mil inserts também. O Leandro também. Com esses resultados eles já teriam entrado nos Top 10.</p>
<p>O Leandro experimentou mais com as configurações de postgres e de nginx. Muitos de nós achávamos que esses dois componentes não eram tão importantes. Pior, achávamos que eles não ajudavam em nada, então fizemos outra coisa feia que muitos fazem sem pensar: colocamos nginx pra aceitar o máximo de conexões quanto possível aumentando o número de worker connections lá pra cima de 10 mil. E no caso do Postgres a gente configurava os pools de conexão pra dezenas ou centenas de conexões. E isso é um enorme erro.</p>
<p>Vamos anotar pra explicar no próximo video, mas deixa eu tentar ilustrar aqui. Pra ter uma visão na cabeça vamos imaginar uma lanchonete, com vários caixas tirando pedido e mandando pros cozinheiros atrás. Os caixas são nossa aplicação, feitos em qualquer das nossas linguagens. Os cozinheiros são as conexões do banco de dados que é a cozinha.</p>
<p>O nginx é como o tamanho da porta da lanchonete. Se fizer uma porta que aguenta passar 100 mil pessoas de uma só vez, o que acontece? O stress test começa com poucas pessoas, umas 3, mas ao longo de 3 minutos, dispara 600 simultaneamente. Só que essas 600 não são atendidas ao mesmo tempo. Não tem caixa suficiente, daí vai formar um amontoado de gente desordenado.</p>
<p>Do lado da cozinha, o banco de dados, cada cozinheiro extra exige pelo menos uns 3 MB de RAM de espaço e vai consumir CPU. Mas a cozinha, é limitada. Lembra da regra da rinha? No máximo 1.5 CPU. Não adianta aumentar cozinheiro infinitamente, que a cozinha não aguenta. Se falarmos pros caixas tirarem quantos pedidos conseguirem, rapidamente esses pedidos vão ficar empilhados porque não tem cozinheiro suficiente pra atender.</p>
<p>A vontade é aumentar os cozinheiros, o pool de conexões. Digamos, temos duas instâncias da aplicação, os caixas, e cada um vai poder tirar até 100 pedidos de uma só vez. Então 200 pedidos. Mas não tem como ter 400 cozinheiros. Primeiro porque eles ocupam espaço, 3 mega cada já vai dar no mínimo 1.2 gigabytes de RAM. Mas beleza, o estabelecimento inteiro aguenta 3 gigabytes que é o limite imposto pela rinha lembra? Mas não tem fogão suficiente pra cozinhar, a cozinha tá apertada, temos menos de 1 CPU, cada cozinheiro vai demorar mais e mais tempo pra atender cada pedido. Entenderam?</p>
<p>Então o cenário é assim: a porta do estabelecimento permite entrar todo mundo de uma só vez, mil pessoas, 10 mil pessoas, entra tudo. Só tem 2 caixas, duas instâncias. Mesmo tirando pedidos rápido, fica tudo acumulado esperando. Não adianta contratar mais cozinheiro, porque a cozinha é limitada, e quanto mais cozinheiros tentamos colocar, mais cada cozinheiro demora. E enquanto o pedido não é atendido, a pessoa fica lá pendurada na frente do caixa esperando. E assim, vai acumulando centenas de pessoas na lanchonete esperando, até começarem a perder a paciência e ir embora. São as requisições perdidas ou knockouts.</p>
<p>A solução que o Leandro publicou não é intuitiva. Em vez de fazer o nginx aceitar quantas pessoas puder, fazemos diferente: limitamos o tamanho da porta. Digamos, no máximo 1000 pessoas. O NGINX coloca um sistema de senhas do lado de fora e vai deixando entrar só 1000 de cada vez, de forma mais ordenada. É igual num Poupa Tempo ou no seu banco. Pega a senha, senta e espera ser chamado. Agora os caixas atendem menos pessoas de uma vez, mais ordenado, e tiram menos pedidos de uma só vez, digamos, no máximo 50 em vez de 100 ou 200. Esse é o pool de conexões.</p>
<p>E na cozinha, em vez de precisarmos de 300 ou 400 cozinheiros, agora precisamos só de 100. Mas aí não vai andar mais devagar? Não, porque sobra mais espaço na cozinha e cada cozinheiro consegue cozinhar mais rápido. É melhor 100 cozinheiros terminando os pratos em 1 minuto ou 400 cozinheiros levando 10 minutos cada porque tão disputando fogão? Entenderam?</p>
<p>Eu estava cometendo esse erro também. Tava deixando nginx lá em cima, com portão gigante pra suportar 10 mil conexões e compensando com 200 conexões no pool de cada instância do Rails. E pra piorar ainda estava cometendo um erro no postgres. Eu tinha esta linha no docker-compose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>volumes: - ./postgresql.conf:/docker-entrypoint-initdb.d/postgresql.conf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso diz pro Docker Compose pegar esse arquivo postgresql.conf do meu diretório local e copiar pra dentro do container de postgres. Mas tava faltando modificar o comando de inicialização pra ter &ldquo;-c&rdquo; com o nome desse arquivo. É nesse arquivo que digo pro postgres que ele deve suportar, digamos, um máximo de 400 conexões.</p>
<p>Como não tava carregando o arquivo, subia com o limite padrão de 100 conexões. E isso confunde, porque eu pensava que ia conseguir abrir mais de 100, mas não ia. E não sabia porque. Esse foi o primeiro motivo de porque meus resultados tavam lá na casa dos 15 mil inserts: nesse caso faltava cozinheiro e eu achava que tinha suficiente. Falta de atenção que chama isso. Em vez de consertar no lugar certo, eu ficava tentando compensar em outros lugares, como no NGINX.</p>
<p>No próximo episódio vou explicar como eu poderia ter evitado essa confusão. Vamos deixar anotado aqui. Depois que descobri isso, e li o tweet do Leandro, reconfigurei o nginx pra faixa de 1000 workers de conexão e depois diminuí o número de cozinheiros no pool de conexões pra uns 40. O problema nem era o máximo de conexões, era a compensação excessiva que tava fazendo no NGINX. Um erro levando a outro erro.</p>
<p>À medida que ia diminuindo esses dois números, passava a aumentar a quantidade de inserts no final, a quantidade de pessoas atendidas de fato. Eu tava achando que minha versão de Rails, pelo fato de ter exagerado na solução e enfiado Sidekiq pra jobs, Redis pra fila e cache, ia faltar espaço na cozinha, mas não foi isso, foi questão de gerenciar o fluxo de pessoas.</p>
<p>Limitar a porta e os caixas em vez de escancarar. Lembrem-se disso: atendimento não é tentar atender todo mundo de uma só vez, é atender grupos organizados de cada vez. É uma questão de vazão e fluxo. Isso é essencial em operações. Seja atendimento numa lanchonete, seja numa linha de fábrica, seja num servidor web.</p>
<p>E foi assim que em 3 de setembro, postei o resultado da minha versão de Rails. Faixa de 39 mil inserts, quase batendo os 40 mil que queria. O cenário que descrevi de gerenciar o fluxo de pessoas na lanchonete é um caso clássico, que demonstra que o problema não é a capacidade de atendimento do caixa, ou de cozinhar dos cozinheiros: é de gerenciar o fluxo de requisições, das pessoas ou sessões. É quando sabemos que o problema principal é gerenciar I/O, entrada e saída.</p>
<p>Nós que somos mais experientes já deveríamos ter parado pra pensar nisso primeiro, mas assumimos errado no começo e essa hipótese errada nos fez perder muito tempo. Assumimos que o problema seria a cozinha, o banco de dados. Aí tentamos compensar aumentando número de cozinheiros ou cozinheiros mais rápidos que seriam os caches. Seria o caso se o problema fosse CPU bound, limitação de CPU. Mas na realidade era problema de fluxo, de entrada e saída, no fluxo dos caixas, o problema era I/O bound, como a maioria dos problemas de Web comum.</p>
<p>Uma vez controlado o fluxo, podemos otimizar o caixa pra em vez de só tirar o pedido e ficar esperando de braços cruzados esperando; enquanto o cozinheiro tá ocupado, o caixa pode fazer outras coisas na frente, como receber o pagamento, pegar a bandeja, separar o refrigerante, limpar o balcão. Não precisa esperar o prato chegar pra depois fazer tudo isso, dá pra fazer em paralelo. É pra isso que servem threads ou os async/await de várias linguagens, como Javascript no Node.js.</p>
<p>Nesse ponto eu já tinha visto o MrPowerGamer e outros colaboradores como o Bruno Borges conseguindo subir Kotlin e Java pro mesmo nível do Rust. Já vi o Leandro e o Lazaro subindo Ruby e Rails pra perto do mesmo nível, faixa dos 40 mil inserts. Eu mesmo já tinha conseguido subir Crystal e Rails. Foi quando minha intuição dizia que a maioria dos participantes, se tivessem conhecimento de tudo que a gente descobriu até agora, seriam capazes de chegar perto desses 40 mil inserts ou até bater o máximo dos 46 mil e quinhentos do Rust do Vinicius.</p>
<p>Alguns não gostaram de ouvir isso, porque agora aquele bullying contra o Java ou a fanboyisse de Rust perderiam valor. E como eu detesto dizer uma coisa sem ter provado antes, resolvi fazer a única coisa racional: demonstrar na prática. Ainda era a mesma segunda-feira, 4 de setembro. Escolhi começar aleatoriamente mexendo na versão em Node.js e Express do Lukas Weis que já tava perto do topo, na faixa de 34 mil inserts, em 13o lugar no ranking oficial. Na minha máquina tava dando abaixo disso, uns 27 mil.</p>
<p>A versão dele foi uma das que mexi mais. Como foi um código feito pra competição, ele e muitos outros não se preocuparam em seguir boas práticas, clean code, nem nada disso, só cuspiram código até funcionar e publicaram. Não tem absolutamente nada de errado nisso e não considerem esses códigos como representação do que eles conseguem fazer com mais tempo, em projeto de verdade. Isso tem que ser repetido o tempo todo: código pra competição é diferente de código de verdade.</p>
<p>Mas como eu estava com tempo e não era pra participar da competição, aproveitei pra desenferrujar meu Javascript e fui refatorar um pouco o código pra ficar mais organizado e mais legível. Meu objetivo era totalmente diferente da rinha: dar uma limpada e otimizar o suficiente pra bater pelo menos a faixa dos 40 mil inserts, o que elevaria o projeto pros Top 5 oficiais.</p>
<p>Recomendo que leiam meu pull request com calma, deixei o link na descrição do video abaixo, mas em resumo, depois de organizar o código comecei tirando fora a otimização prematura de usar Redis como cache. Como falei antes, o problema não era a velocidade dos cozinheiros, o postgres. Portanto a solução não era tentar achar cozinheiro mais rápido, um Redis. Com a configuração adequada do fluxo de nginx, do pool por postgres, dividindo CPU e RAM melhor no Docker Compose, já resolvia. Arranquei o Redis fora, daí teria mais recursos pro postgres.</p>
<p>Esse cache, além de tudo, não estava sendo efetivo porque o Lucas tentava cachear os resultados do endpoint de pesquisa por termos, só que o stress test manda pesquisas com termos aleatórios, raramente repetidos, então o cache nunca ia ajudar, porque toda pesquisa ia ser nova e não ia ter o resultado no cache. Portanto cache na pesquisa de termos era inútil, só um peso a mais de gravar no cache.</p>
<p>Além disso, no endpoint de criar novo registro, ele deixava o banco de dados criar a chave primária, daí recebia de volta no final e só aí gravava no cache. Isso tem que acontecer um atrás do outro, não dá pra ser assíncrono. Adicionar o valor no cache só aumentava o tempo da requisição. Mais uma vez, era um trabalho extra sem benefício.</p>
<p>No endpoint seguinte, de pesquisar pessoa direto por ID, ele buscava no cache. Mas a pesquisa por chave primária no postgres é igualmente rápido. De novo, o cache não fazia diferença. Só faria sentido se primeiro gerar a chave primária, com uma biblioteca de UUID na aplicação, e mandar o insert pro banco já com essa ID e em paralelo já mandar gravar no cache também com a mesma ID. Como não precisa esperar o banco devolver uma nova ID, não precisamos esperar pra gravar no cache, entenderam? É uma das vantagens de usar um gerador de chaves primárias externo ao banco. Muito iniciante não sabe que não é obrigatório deixar o banco responsável por IDs.</p>
<p>O Lucas também tava devolvendo códigos de erro HTTP junto com mensagens em JSON descrevendo o erro, mas a rinha não se incomodava com isso, então só arranquei fora. O stress test não liga pra mensagem, só quer respostas rápidas. No mundo real, o certo é dar respostas completas, porque sem logs bem descritos depois fica difícil achar bugs e problemas. Novamente, é uma otimização que só serve pra uma competição.</p>
<p>O Node.js tem recurso de cluster, subir forks do processo pra escalar melhor. Mesma coisa que Rails também faz. O Lucas parece que experimentou, mas desistiu. Eu ia tirar fora, mas voltei e passei a usar pra tentar distribuir um pouco mais a carga. Ter um reactor com corrotinas assíncronas e conseguir ter concorrência é diferente de ter paralelismo e o ideal é ter as duas coisas. Eu explico isso no video de concorrência e paralelismo e no próximo video talvez eu tente explicar melhor neste contexto. Vamos deixar anotado.</p>
<p>A parte que possivelmente pesou mais na versão do Lucas foi o endpoint de pesquisa que faz select LIKE na tabela. Muitos erraram nisso: precisa fazer select LIKE nas colunas apelido, nome e stack, então é apelido LIKE termo OR nome LIKE termo OR stack like termo, e esse é o pior jeito de fazer select. O melhor jeito é pré-criar um único campo com as três colunas concatenados durante o insert. Daí fazer um índice usando GIST com a extensão de trigrams. Alguns tentaram GIN, mas pra este desafio o melhor era GIST. Já tínhamos anotado, mas vou explicar isso no próximo video.</p>
<p>A segunda coisa foram as validações. Validar os dados antes de inserir no banco costuma ser mais rápido do que tentar inserir e deixar o banco devolver erro. Por exemplo, que a data de nascimento é inválido, tipo mês 13 ou dia 31 em fevereiro. Podemos só checar se a string tem o formato de ano, tracinho, mês, tracinho dia, no mínimo. Ou tentar converter essa string num objeto válido de data pra garantir. Eu não medi exatamente esse ponto, mas o Lucas usou a biblioteca moment.js, que antigamente já foi bem popular mas acho que é considerada meio obsoleta.</p>
<p>O jeito mais popular hoje é usar outra biblioteca, a date-fns ou simplesmente usar o construtor da classe Date do Javascript nativo. Eu comecei tentando usar o date-fns mas achei mais fácil só checar se o parse da string pra um new Date is not a number (isNaN). Com todas essas mexidas fui mexer no Docker Compose. Ele tava subindo 3 instâncias de Node no Docker, mas como reativei o código de Node Cluster, desci pra 2, cada uma com 1 fork extra, totalizando 4 processos.</p>
<p>Diminuindo a carga do código de cache, que não tava servindo pra nada. Melhorando as pesquisas por termo. Diminuindo a carga do código de validação. Ou seja, fazendo nossos caixas trabalhar menos, em seguida era o problema de diminuir a montanha de gente que o nginx tava deixando entrar tudo de uma só vez. Diminuí a quantidade de workers do nginx dos absurdos 20 mil, pra 10 mil até 1024. Notei que uns 1000 é ideal pra maioria das aplicações. Muitos até menos.</p>
<p>Fazendo isso os caixas trabalham de forma mais organizada e eficiente, precisando de menos cozinheiros, menos conexões de banco no pool. O Lucas tinha configurado com 200, mas agora dava pra descer pra uns 45, talvez até menos. E fazendo tudo isso, essa versão de Node.js saiu da faixa de 27 mil inserts, na minha máquina, ou 34 mil que conseguiu na máquina dos organizadores da rinha, e pulou pra faixa dos 40 mil também. Do meu ponto de vista, foi uma mudança significativa. E cimentou na minha cabeça que dava pra fazer a mesma coisa pra outras versões.</p>
<p>Desculpas ao Lucas por ter pego ele pra Judas, mas não entendam errado. Vários outros, incluindo eu mesmo, cometemos os mesmos erros. Só aproveitei pra explicar mais na versão dele pra não me repetir em todos os próximos. Enfim, ainda no dia 4 de setembro, depois de atingir os 40 mil inserts com o Node.js, resolvi mexer em outro velho conhecido, Elixir. Na minha cabeça não fazia sentido Elixir ter se saído abaixo dos Top 10. Mas eu fiquei surpreso com um problema: essa versão do Gabriel Oliveira se recusava a carregar na minha máquina!</p>
<p>Como chegou a figurar no ranking oficial, significa que o Zan conseguiu rodar pra avaliar. Portanto, se a versão é a mesma (e tem que ser, é a mesma imagem de Docker), por exclusão, tem que ser alguma coisa na minha máquina. Mas toda vez que eu tentava subir o Docker Compose, via as instâncias da app crasheando sem nenhuma mensagem de erro, simplesmente morriam. Puf, zero, nada.</p>
<p>Sem entender o motivo, fiz um teste: mudei o comando do docker compose pra só subir um  &ldquo;sleep infinity&rdquo; do Linux. Todo mundo conhece o conceito de sleep, certo? Pausa o processamento pelo tempo que mandar. Linux suporta infinity, daí fica pausado pra sempre, como um loop infinito, while true da vida. Fazendo isso o container fica de pé pra eu poder fuçar. Daí é possível usar o comando docker-compose exec pra abrir um bash dentro do container.</p>
<p>De dentro, tentei executar o binário do app na mão, e de fato, crasheava. Achei isso muito estranho, aí me ocorreu, será que o app é muito pesado? Vou tentar carregar o iex que é só a linha de comando do REPL do Elixir, sem carregar nada da aplicação. E crasheava também. Estranhíssimo. Vamos pular o Elixir e carregar direto só o Erlang, o comando &ldquo;erl&rdquo;. E crasheava! Bizarro! A sensação que eu fiquei é que os programas pediam mais recurso do que a máquina conseguia entregar e crasheavam, como falta de RAM.</p>
<p>Resolvi editar o arquivo de docker compose pra tirar a limitação da rinha de CPU e RAM. Tentando rodar de novo, aí subia! Por alguma razão tava faltando RAM. Abri o HTOP pra ver quanto de RAM tava usando e era um absurdo, coisa de quase 1 gigabyte e meio! Fiquei bem confuso. Como fazia tempo que não mexia com Elixir fiquei pensando, &ldquo;ué, será que nos últimos anos eles enfiaram tanta coisa no Elixir que agora precisa de mais memória que uma JVM do Java?&rdquo;. É confuso porque quem já mexeu com web de Java, com Spring e tudo, sabe que 1GB de RAM não é fora do comum. Mas não esperava isso do Elixir.</p>
<p>Eu tava quase jogando a toalha, então resolvi reportar no meu Twitter. Todo mundo achou estranho, mas o próprio Gabriel conseguiu achar a resposta, era o limite máximo de porta que o Erlang detectava no meu sistema. Em Erlang e portanto também em Elixir, portas são usadas pra se conectar com o mundo externo. Por exemplo, pra ler do standard input, STDIN, ou escrever no standard output, STDOUT, são portas. Pra abrir arquivos, pra receber conexões de rede, são portas.</p>
<p>Erlang pré-aloca no mínimo 1024 portas. Pra determinar o máximo, podemos abrir o shell &ldquo;erl&rdquo; e rodar o comando <code>erlang:system_info(port_limit).</code> E olha só: meu sistema devolve mais de 1 milhão de portas. Daí tem o detalhe que ainda não entendi: na aplicação em Elixir parece que tenta pré-alocar esse máximo sei lá porque, em vez de ir só alocando se precisar. Daí dá mais de 1 gigabyte de RAM. E se limito dentro das restrições da rinha, iria faltar memória no container, crasheando na inicialização.</p>
<p>Felizmente, com uma simples variável de ambiente <code>ERL_MAX_PORTS</code>, podemos colocar um número mais sano, tipo 2048 ou algo assim. Limitando de 1 milhão pra 2 mil, fez uma puta diferença, agora o app sobe, consumindo na faixa de 160 megabytes por instância, que é bem mais razoável e próximo até de versões como Rails ou Node.</p>
<p>Isso é um problema que só aconteceu no meu sistema porque a configuração de Arch Linux é mais agressiva nos limites do que um Debian ou Ubuntu, que são mais conservadores. Sendo mais específico, isso é limite de file descriptors, ulimit do sistema, depois pesquisem sobre isso. De qualquer forma, a versão Elixir do Gabriel eu gostei bastante porque usou características exclusivas de Elixir. Ele fez as duas instâncias rodando em containers diferentes se conectarem num cluster.</p>
<p>Clusters saem de graça em Erlang, porque a máquina virtual já trás toda a infraestrutura pronta. É fácil de usar. Não serve pra tudo, mas quando precisa é bem útil. Lembram como falei que não valia a pena usar um cache como Redis, primeiro porque o postgres já era rápido o suficiente, mas também porque subir um container de Redis significa dar menos recursos pro postgres? Com o recurso de cluster do Erlang, dá pra ter um cache compartilhado entre as duas instâncias, sem precisar de um Redis entre os dois.</p>
<p>O app em Elixir consegue consultar na memória da primeira instância, e a segunda instância no outro container, consegue pedir pra primeira instância porque estão no mesmo cluster. Assim não precisam de uma memória externa na forma de um Redis da vida. À primeira vista isso parece mais esperto, mas como já disse antes, o problema não é o postgres, e adicionar um sistema de cache só adiciona peso desnecessário.</p>
<p>Mesmo assim esta versão, ajustando só nginx e o pool de conexões, consegue passar dos 41 mil inserts e entra nos Top 5. Mas tem um porém, além do cache, as instâncias faziam chamadas RPC entre eles, ou seja a 1a instância podia pedir pra 2a instância gravar ou pesquisar no banco e vice versa. Isso eu achei desnecessário e adiciona uma comunicação externa via rede pra toda chamada.</p>
<p>Eu não tive tempo de testar, acho que se arrancar fora essas chamadas remotas, o código de cache e dar mais recursos pro postgres, talvez seja possível conseguir um resultado melhor. Mesmo assim eu esperava que Elixir conseguisse um número maior. De todas as versões que eu mexi, essa ainda foi a que performou um pouco pior. Então tem espaço pra simplificar mais o código. Elixir certamente consegue mais. Estão notando o padrão: em vez de adicionar coisa, estou tirando coisas e com isso deixando mais rápido. Por isso sempre repetimos tanto que otimização prematura é a raíz de todo o mal.</p>
<p>Com a versão Elixir do Gabriel batendo acima dos 40 mil inserts, resolvi procurar uma versão em Go Lang. E escolhi um dos que ficou mais embaixo no ranking. A do Luan ficou em 23o lugar com só 21 mil inserts. Assim como Elixir, não tem porque Go ficar lá embaixo e isso já se provou porque pelo menos 3 versões tinham alcançado os Top 10.</p>
<p>Fiz pequenos ajustes no docker compose, pra dar um pouco menos de CPU pras instâncias de app, assumindo que Go sem nenhum framework iria precisar de menos recursos que Elixir. Daí podemos aumentar CPU do postgres. Teve duas modificações de código que quis mexer. Primeiro, fazer erros devolverem só os códigos de erro 400 ou 422 de HTTP sem mensagens de erro. É pouca coisa mas já faz um pouco de diferença.</p>
<p>A segunda coisa é que ele tava usando Regular Expressions pra validar o formato da data de nascimento. Na real, acho que isso é meio besteira, mas como Regex costuma ser mais lento do que dar split na string e checar cada componente, troquei também. Acho que nenhuma dessas duas coisas faz muita diferença no final. O que deu diferença sim foi o que já falei antes. Primeiro, diminuir o tamanho da porta de entrada: diminuir os 10 mil workers de nginx pra 1024. E aumentar o pool de conexões, embora isso também nem tenha sido necessário.</p>
<p>Só de limitar e controlar o fluxo de entrada de requisições, literalmente uma linha, já disparou o resultado da faixa de 29 mil inserts pra 34 mil. Não chegou nos 40 mil que eu queria, mas por hora resolvi subir meu pull request, que também tem link na descrição abaixo, e pular pra outra coisa. Só pra terminar esse dia 4 de setembro, fui dar uma fuçada na tal versão em dotnet do André e Albert que alcançaram terceiro lugar.</p>
<p>Como muitos outros, eles também optaram por fazer uma versão codificada de forma rápida, sem muitas boas práticas, então ficou bem verbose, tudo super longo. A curiosidade é que como muitos,  também optaram por implementar um cache, mas diferente da maioria, em vez de Redis escolheram usar Nats.</p>
<p>Nats deveria ser mais usado, e parece que povo de Dotnet usa mais. É uma alternativa até mais confiável do que Redis pras funções de cache ou como uma fila de jobs simples. Em cima do nats, usaram os recursos de async do C# com channels pra fazer a implementação de bulk insert. E com isso conseguiram atingir quase o máximo, 44 mil inserts. Esta versão não mexi em nada, só fucei e tweetei a respeito.</p>
<p>No dia seguinte, dia 5 de setembro, me sugeriram fuçar alguma versão de PHP então puxei pra rodar a versão do Lauro Appelt que ficou em 17o lugar no ranking, com meros 25 mil inserts. Fazia muitos anos que não mexia com PHP, então precisei da ajuda dos universitários.</p>
<p>Pacotes de PHP hoje em dia se instalam com Composer, que é parecido com NPM de Javascript ou Cargo de Rust. Foram-se os dias de baixar zip de sites como Freshmeat ou SourceForge. Mas PHP ainda tem o conceito de extensions, que como o nome diz, carrega junto com o PHP em si, e modificam as estruturas internas da linguagem.</p>
<p>Antigamente lembro de usar PEAR que eram as extensions em C, mas hoje parece que são PECL. Nesse estágio precisava carregar essa nova extension chamada Swoole, que adiciona funcionalidades de corrotinas e modelo de I/O não-bloqueante, o que transforma o PHP em algo parecido com Node.js. Basta baixar, compilar e instalar a extension e editar o php.ini pra carregar ela na inicialização.</p>
<p>Depois de bater cabeça pra entender isso e instalar os pacotes, pude rodar essa versão do Lauro, que ele implementou com o framework Hyperf. PHP 7 tem uma sintaxe bem mais moderna do que o antigo PHP 3 que eu usava, mais de 20 anos atrás. Mudou bastante coisa, tá bem mais legível, bem mais organizado. E o Lauro foi um dos poucos que deu uma caprichada e deixou tudo fácil de entender e legível. Recomendo darem uma olhada.</p>
<p>Nessa versão também não mexi em nada no código, só parei pra entender, desenferrujar um pouco do meu PHP. A única coisa que ajustei foi o pool de conexões. Aqui, ao contrário da maioria, o Lauro foi super conservador e limitou o pool de conexões com 10 conexões, muito pouco. Resolvi soltar tudo e jogar lá pra cima, em 500, que é exagerado. Mas fazendo isso e rodando o stress test, essa versão também subiu pra quase 40 mil inserts, o que também o colocaria lá pelos Top 5.</p>
<p>Por causa disso já me dei por satisfeito por enquanto, já que queria pular pra ver outras versões. Nesse dia não mexi em muita coisa. Dei uma pausa e a noite resolvi que ia mexer mais no dia seguinte, mas acabei não conseguindo dormir. Levantei e voltei pro PC. Queria mexer em pelo menos mais uma versão, a de Python. Escolhi o 21o lugar do Ian Cambrea, com menos de 24 mil inserts.</p>
<p>Ele não fez em Django, que seria o mais comum pra web em geral, nem em FastAPI que parece ser o mais comum pra APIs. Ele escolheu Sanic, que é um framework que eu não conhecia. A única modificação significativa que eu fiz foi notar que ele também resolveu usar Redis pra cache, mas não implementou um pool de conexões pro Redis. Tava usando uma única conexão pra tudo.</p>
<p>Não consigo afirmar que era só isso, mas testando na minha máquina como os outros, o resultado bateu nos 40 mil inserts também. De curiosidade, essa foi a única versão que eu vi que usou NGINX diferente. Normalmente a gente configura NGINX pra fazer balanceamento de carga usando protocolo HTTP, portanto usando rede TCP por baixo, fazendo proxy reverso pra outro servidor web HTTP atrás, no caso o Sanic.</p>
<p>Mas NGINX, e outros serviços que rodam em Linux, como o próprio Postgres, que também costumamos conectar via TCP na porta 5432, suportam conexões usando UNIX Sockets. Se tudo for rodar na mesma máquina, que não é comum no mundo real, não tem necessidade de usar rede TCP/IP. Tá tudo na mesma máquina. Podemos fazer um processo se comunicar diretamente com outro processo usando IPC ou inter-process communication, e um dos jeitos de fazer isso é via UNIX Sockets.</p>
<p>De forma resumida, sabe quando fazemos tipo um cat num arquivo, adicionamos um Pipe, e rodamos um grep do outro lado pra filtrar o conteúdo do arquivo? Isso é um jeito do processo grep se comunicar com o processo cat via esse pipe que liga o stdout do cat ao stdin do grep. É mais ou menos isso que acontece com o NGINX usando unix sockets pra falar com as instâncias de Sanic. Basta todos enxergarem os mesmos arquivos .sock. Depois dêem uma olhada no repositório do Ian pra ver como ele fez.</p>
<p>Como a versão de Python também foi fácil e não exigiu nenhum trabalho meu além de ajustes triviais, fui dormir e no dia seguinte resolvi olhar pro topo da lista de novo. Fiquei curioso com isso de Lean4 que a Sofia e a Gabi usaram e alcançaram o Top 4 do ranking. Nunca tinha ouvido falar nessa linguagem, mas o que vi foi impressionante.</p>
<p>Lean4 é uma linguagem experimental ainda, longe de ser 1.0, criada na Microsoft Research pelo brasileiro Leonardo de Moura em 2013. O objetivo dele foi criar uma linguagem pra ser fácil criar provas de teoremas. É uma linguagem mais focada em formalidades matemáticas, inspirada em linguagens funcionais como Haskell ou OCaML. Ele tem um cheiro de dialeto de linguagem da família ML. Se curte linguagens funcionais e formalidade matemática, essa pode ser uma boa alternativa.</p>
<p>Mais impressionante foi a dupla Algebraic, as autoras do projeto da rinha, a Sofia que tem só 21 anos e a Gabi que tem só 17 ou 18 anos. Como falei desde o começo, implementar esses endpoints de API, em si, não é nenhum desafio. Mas neste caso foi. Lean4 é experimental, comunidade pequena, e quase nada pra Web, porque nunca foi o foco. Ela é quase toda focada em teoremas matemáticos e não criar APIs pra Web.</p>
<p>Pra fazer APIs, elas precisavam de um parser de JSON, já que na regra da rinha, o stress test ia mandar milhares de requisições com JSON. Mas não tinha uma biblioteca de JSON, então a Sofia fez um do zero. Quando se recebe as requisições HTTP, algumas contendo esses JSON, precisa conseguir parsear o pacote. Existe já parser de HTTP em C, que todo mundo usa em várias linguagens, mas pra Lean4 não tinha. Então a sofia fez um wrapper em FFI pra integrar.</p>
<p>Pra implementar a aplicação em si, ela queria usar algum framework web, como em Node você tem Express ou em Java tem Spring. Mas em Lean4 não tinha, então a Sofia fez um pequeno do zero.  Além disso, pra rodar uma aplicação web, duh, precisa de um servidor Web. Sabe? Tipo Node.js, tipo um web2py, ou um Tomcat. E claro, também não tinha. E claro, a Sofia fez um. Ou seja, esse foi aquele caso de &ldquo;não sabendo que era impossível, foi lá e fez&rdquo;.</p>
<p>Só esse esforço já merece uma menção honrosa, porque eu acho que ninguém teve metade do trabalho que elas tiveram. Mais foda ainda porque essa versão foi uma das mais performáticas. Também é igualmente impressionante que uma linguagem desconhecida, experimental e que provavelmente ninguém nunca usou pra web, seja tão performática.</p>
<p>O criador, Leonardo, focou em fazer uma linguagem com objetivo de provar teoremas e verificação formal em vez de performance bruta. Mesmo assim foi bem sucedido em performance. Não deixem de olhar o código da rinha no repositório da Gabi, além das bibliotecas no repositório da Sofia. Links abaixo.</p>
<p>No video de análise do MrPowerGamerBR, onde ele descobriu os limites do stress test e das regras da rinha, também conseguiu identificar que algumas das versões nos Top 10 tinham bugs, como de validação, que acabaram deixando passar inserts que não deveriam, em particular por falta de validação de data.</p>
<p>Essa versão em Lean4 tinha esse tipo de bug. Elas esqueceram de colocar validação da data de nascimento. Tudo bem, mesmo com a validação, a versão delas ainda ia ficar entre os Top 10, por pouco. Então aproveitei pra tentar molhar meus dedos em Lean4 pela primeira vez e implementei uma validação simples. Foi o código menos intuitivo que eu fiz em muito tempo. Acredito que quem for mais experiente em linguagens como Haskell ou F# devem conseguir entender mais fácil.</p>
<p>Tinha outro pequeno bugzinho na hora de gerar a URL pro cabeçalho de Location depois de inserir o registro no banco, então corrigi isso também. De qualquer forma, vejam o link dos meus pull requests na seção de links abaixo. No fim desse dia postei um tweet de resumo até este momento com tudo que descobri. Neste estágio da minha saga, já tinha conseguido fazer minha própria versão de Rails e de Crystal bater os 40 mil inserts, ajustei a versão em Node do Lucas, Elixir do Gabriel, Go do Luan, PHP do Lauro, Python do Ian, além dos ajustes no Lean da Sofia e Gabi.</p>
<p>Chegamos no dia 6 de setembro, véspera de feriado prolongado de Independência e que eu tinha planejado viajar&hellip; Depois de ter batido cabeça com Lean4, resolvi que queria bater cabeça com outra linguagem que nunca mexi antes. Tentei fuçar Zig, mas achei que tava experimental demais, o ferramental super instável, na verdade, tudo instável. Coisas da versão 0.10.0 quebram na 0.12.0. Pra uma experiência de um dia só, não ia compensar. Eu acho que é uma das linguagens mais promissoras, tanto que foi usada pra fazer o novo Bun, mas vou deixar pra outra ocasião.</p>
<p>Então lembrei que sempre quis tentar a linguagem Nim, outra que é compilada como Go, tem uma sintaxe inspirada em Python, existe já faz alguns anos então assumi que seria mais estável e melhor documentada do que Zig. Provavelmente fácil de chegar numa boa performance, assim como consegui com Crystal. Ledo engano. Apanhei bastante, a começar pela falta de documentação.</p>
<p>Não ajudou nada que a biblioteca assíncrona pra Postgres se chama asyncpg, que é o mesmo nome da de Python. Quando pesquisa no Google, adivinha quem aparece primeiro? Pra piorar, a linguagem e suas bibliotecas usam e abusam de macros, que modificam a sintaxe da linguagem. Sem ter uma boa experiência, foi confuso diferenciar o que era da linguagem e o que eram macros. As mensagens de erro do compilador confundiam mais do que ajudavam. Reclamações de amador, claro, não estou dizendo que é um defeito, mas é uma curva de aprendizado maior pra iniciantes.</p>
<p>Recursos de corrotinas, tipos como Option, pareciam ainda meio experimentais, com hacks pra funcionar certas sintaxes. Escolhi o framework web Jesper ,que parece um Sinatra de Ruby. Apanhei um bocado mas consegui implementar os 4 endpoints numa manhã. Só que contrário às minhas expectativas, o resultado foi pífio, uma verdadeira droga mesmo.</p>
<p>Já tentei compilar com opção speed, nada. Já tentei compilar com modo multithread, nada. Suspeito que a biblioteca de async pool que estou usando é imatura e vaza conexões. Depois preciso investigar isso melhor. Mesmo aumentando pool pra 100 ou mais, nem chega até o fim do stress test, abre o bico muito antes. Male male insere 2000 inserts. Isso deixaria esta versão lá no fim do ranking oficial, só ganhando da versão de brincadeira feita em Bash do Leandro.</p>
<p>A probabilidade maior é que eu, sendo amador, posso ter feito erros de iniciantes e por isso foi ruim. Mas na maioria das linguagens mais maduras, um iniciante teria dificuldade de deixar tão lento assim, mesmo se fosse num Javascript. Se tiver alguém assistindo que tem experiência com Nim, não deixe de dar uma olhada no meu pull request linkado abaixo pra ver se descobre o problema, mas no estado que tá agora, eu não recomendaria usar Nim pra uma aplicação web, mesmo se fosse bem simples. Se for pra usar uma linguagem imatura com bibliotecas instáveis e mau testadas, é melhor ir direto pra Zig ou V Lang que são mais modernas.</p>
<p>Apesar dessa tentativa frustrada, outros desenvolvedores acompanhando minha saga começaram a se mexer pra fazer versões melhores. Por exemplo, nesse mesmo dia vi o Junior Leão mostrando uma outra versão em Java com Spring Boot, usando Redis pra cache com JDBC direto e também conseguiu alcançar os 40 mil inserts.</p>
<p>Outra tentativa bem sucedida foi do Carlos Silva, conhecido como Insalubre, que tinha participado com uma versão em V Lang, mas tinha algum problema e ele foi desclassificado, então nem entrou no ranking oficial. V Lang é uma nova linguagem com cara de Go. Ele se inspirou nas discussões de técnicas que ficamos fazendo no Twitter, consertou a versão dele, e atingiu os 46 mil inserts.</p>
<p>O Leandro continuou mexendo na sua versão de Ruby e neste dia   reportou que conseguiu bater os 46 mil inserts. Ele foi quem mais testou a hipótese de diminuir a porta de entrada do NGINX e controlar a vazão de requisições. Com só 256 workers do NGINX e 30 conexões totais de Postgres no pool, conseguiu alcançar os 46 mil inserts, demonstrando que nem o nginx e nem o postgres nunca foram os gargalos desta rinha. Veja o fio do tweet dele nos links abaixo pra mais detalhes.</p>
<p>No fim do dia resumi num tweet todas as técnicas que descobrimos até agora. Por exemplo, validação pode consumir  tempo precioso, quanto mais otimizar, melhor. Não precisa devolver mensagens de erro elaboradas, muito menos em JSON, só o cabeçalho HTTP com o código de erro tá otimo e vai ser mais rápido. O postgres não é o gargalo, não com essa quantidade mixuruca de dados. 50 mil inserts não é nem um peido. Por isso não precisa tentar otimizar prematuramente com Redis pra cache.</p>
<p>Linguagens que escalam com threads ou forks precisam de mais conexões com o banco. Um exclusivo pra cada thread. Então tem que dar um pouco mais de RAM pro banco subir essas conexões extras. Cada conexão de Postgres custa caro, uns 2 a 3 megabytes, porque são forks. Então 100 conexões exigem no mínimo 300 megabytes. E ainda tem que sobrar RAM pra processar as queries. Ou seja, menos de 500 megabytes é inviável. Linguagens com suporte a fibers exigem um pouco menos conexões, porque compartilham a mesma conexão na mesma thread. De novo, tá anotado já pra eu explicar isso no próximo episódio.</p>
<p>Uma coisa que eu e o MrPowerGamerBR vimos na implementação vencedora em Rust do Vinicius Fonseca, é que ele entendeu mas nem todo mundo pensou na forma correta de lidar com searches fuzzy do Postgres. Também já anotamos pra eu explicar no próximo episódio. O Vinicius fez certinho. Ao tornar o search mais eficiente, sobra mais CPU pro postgres conseguir lidar com os inserts.</p>
<p>E por fim, o mais importante, é lembrar do conceito de controlar a vazão na entrada, diminuindo quantidade de workers de NGINX. Esse nunca foi o gargalo, com meio CPU e quase nada de RAM,  aguenta todas as requisições do stress test sem reclamar. Alguns não gostam de NGINX porque algo usando FastHTTP de Go é muito mais eficiente, mas nesta rinha o NGINX nem chega perto de ser gargalo.</p>
<p>Colocando 0.15 CPU e uns 200 megabytes pro NGINX, perto de 0.75 CPU e pelo menos 1.2 GB de RAM pro Postgres, daria pra colocar um pool exagerado de mais de 200 conexões, embora os 100 padrão no total seja suficiente pra maioria.
O que sobrar, dá pra dividir com sua aplicação, ou seja, 0.3 CPU e até 0.8 GB de RAM pra cada instância.
Depende se sua aplicação for mais pesada que isso, como as versões em Java ou Rails ou mesmo Node, aí precisa ir tirando e testando de 0.05 em 0.05 do postgres, dando pra aplicação e medindo pra ver os limites.</p>
<p>E chegamos no feriadão de 7 de setembro. Apesar de eu estar coçando pra investigar mais códigos da rinha, tive que parar por alguns dias. Eu corri pra tentar desvendar tudo antes do feriado, e apesar de ter conseguido ajustar várias versões pra chegar nos 40 mil inserts, ainda tava coçando a cabeça com o elusivo máximo de 46 mil. Tinha algumas suspeitas pra testar quando voltasse, mas fui viajar com essa pulga atrás da orelha ainda.</p>
<p>Já tinha planejado de levar minha namorada pra conhecer Gramado, no Rio Grande do Sul. Nos divertimos bastante apesar de estar super cheio e com tempo meio duvidoso. Visitamos diversas atrações, como a Super Carros, onde pude fazer test driver de Ferrari e Nissam GT-R! Mas, este video não é de turismo. Quem me acompanhou no Instagram viu as stories em tempo real. Se não acompanhou e ficou curioso, deixei um destaque lá. Procurem o link na descrição abaixo.</p>
<p>Mesmo durante o feriado, vários desenvolvedores continuaram empenhados em desvendar os mistérios da Rinha e na sexta feira, dia 8 de setembro, outro dos participantes, o Vinicius Ferraz, fez umaa descoberta muito importante. Ele twitou assim: &ldquo;você que participou da rinha, tomou um j.i.IOException: Premature Close? Tunou o banco, sua app, o nginx e não conseguiu resolver 100%? Sabe onde poderia estar o problema? Na rede do docker. Como resolver? network_mode: host em todos os serviços.</p>
<p>Caraca!!! Nas primeiras versões que mexi, na minha em Rails, em Crystal, a de Node, onde gastei mais horas, chegava perto dos 40 mil, 39 mil, o comportamento ficava estranho. Olhando no gráfico não tinha muita explicação. Ficava esse tremido esquisito no fim do gráfico, como se estivesse tentando ultrapassar um teto invisível.</p>
<p>Mesmo nas versões vencedoras, como o Rust do Vinicius, dava pra ver um tremido menor no fim. Mas ele conseguia ir aguentar até o fim. Independente da versão, mais cedo ou mais tarde, aparecia isso. Eram requisições que começavam a se perder! Não perdia tudo nos inserts, mas principalmente nas pesquisas por termo, até em pesquisas inválidas, o que era mais absurdo, já que  só checa se o parâmetro de termo não existe e devolve cabeçalho de erro. Esse endpoint nunca tinha que dar knockout, porque não processa nada.</p>
<p>Como teve aquele caso do Elixir do Gabriel, que descobrimos que meu sistema Manjaro baseado em ArchLinux tem ulimit maior e por isso afetava o uso de memória do Erlang, imaginei que tinha alguma coisa na minha infra que também pudesse estar afetando isso. Cheguei a suspeitar do Podman, que eu estava usando no lugar do Docker. Mas não deu tempo pra investigar isso porque chegou o feriadão e fui obrigado a parar.</p>
<p>E era mesmo no Docker, e o mesmo problema acontece no Podman. Explicando. Quando subimos container de Docker, não é uma máquina virtual. Eu expliquei isso em detalhes no meu video de Docker, então assistam lá depois. O processo roda nativo no sistema operacional host. Só que a infra do RunC de Linux, que é a base do Docker e do Podman, nos dá opção de enganar o processo rodando. Uma dessas formas é quando mapeamos volumes. Dizemos que um diretório dentro do container na verdade mapeia pra outro diretório do lado de fora, no host.</p>
<p>O mesmo vale pra rede. Digamos que eu queira subir vários postgres na mesma máquina. Não ia conseguir, porque o primeiro ia subir e dar bind na porta 5432. O segundo ia tentar dar bind na mesma porta, mas como já tá ocupado, ia falhar. Com containers, cada processo consegue dar bind na porta 5432 de uma rede falsa, virtual. Ele acha que tá sozinho na máquina. Daí podemos mapear a porta interna do container pra uma porta na rede de verdade do lado de fora.</p>
<p>Pra isso precisamos de uma ponte de rede, um &ldquo;bridge&rdquo;, que é o modo de rede ou network mode padrão. Mas podemos escolher fazer bind direto na porta da rede de verdade, do host. É isso que o Vinicius sugeriu: network_mode: host. Ao não usar o modo ponte, estamos retirando o gargalo da tradução de tráfego da rede virtual pra rede de verdade. Isso custa recursos, é mais pesado. Não existe almoço grátis. Tudo custa alguma coisa. O que a gente não sabia é que era pesado a ponto de afetar esse stress test.</p>
<p>Sem querer, esse era o elo perdido da rinha, a última peça que faltava. E só pra me deixar ainda mais ansioso pra voltar do feriadão, no domingo dia 10 o desenvolvedor Reinaldo resolveu fazer uma versão nova em Java usando Quarkus, Reactive e VertX seguindo as dicas do Bruno Borges e Vinicius Ferraz. Depois vejam o repositório dele, o usuário é zsantana. Ele twitou como conseguiu uma versão que atingia mais de 51 mil inserts.</p>
<p>Mas já falamos que o máximo é 46 mil e quinhentos, portanto certamente esta versão tem bug de validações, como eu demonstrei na versão em Lean4 da Sofia. Mais importante, esta versão em Java atingia um critério que a rinha não media mas que pra mim era importante e eu passei os últimos dias tentando entender porque ninguém atingia: Zero Knockouts. Não perder nenhuma requisição. Esse é o ponto onde vencemos o stress test.</p>
<p>Entenderam? Até este momento a noção era que o stress test era tão pesado que nenhuma aplicação conseguia segurar tudo e eventualmente ia perder algumas requisições. Alguns mais, alguns menos. Isso é comum em testes de carga. Mas assim como nginx e postgres nunca foram o gargalo, a carga também nunca foi tão pesada assim. Quando o Zan foi testar os participantes pra encontrar os finalistas, o teste era ainda mais leve.</p>
<p>Ele dobrou o teste, senão todo mundo ia passar. Mas mesmo dobrando, ainda não é pesado o suficiente. Todos os teste que fiz até agora foi com essa versão já dobrada. É importante ter essas ordens de grandeza em mente. Vamos deixar anotado porque preciso explicar isso pra vocês, o que significa sessões simultâneas, tempo de resposta, throughput.</p>
<p>Faixa de 50 mil linhas numa tabela é super leve pra um banco de dados como Postgres. Só vamos sentir que ele tá pedindo água quando atingirmos faixa de milhões de linhas e isso se estiver sem índices adequados. 600 sessões fazendo requisições, não é pesado. Mesmo com a restrição de menos de 1 CPU no Docker Compose. 1000 sessões simultâneas continua não sendo pesado.</p>
<p>A viagem de volta de Gramado foi cansativa. Varamos noite no domingo pra segunda. Estava de carro alugado e saímos quase 2h da manhã de Gramado pra devolver o carro às 4h em Porto Alegre e pegar o vôo de volta pra Congonhas, em São Paulo às 6h. Pegamos táxi perto das 9h da manhã. Antes das 10h chegamos em casa. Eu só pude cochilar rapidinho no avião e quando cheguei já liguei meu PC.</p>
<p>A primeira versão que queria testar foi a de Crystal com Lucky. Troquei o network mode no docker compose pra host e &hellip; foi! Era isso. Bateu 47 mil inserts. Provavelmente algum bug de validação porque ultrapassou o máximo. Zero knockouts. Sabe a tal tremida nos gráficos que falei? Liso, até o fim, uma linha reta em todos os gráficos. Era isso que eu sabia que Crystal tinha capacidade, e confirmamos, a rede do Docker que não estava deixando!</p>
<p>Próxima versão, mexi no meu Rails overengineered com Sidekiq e tudo mais. Network mode host e bateu acima de 45 mil inserts, faltou pouco pra atingir o máximo dos 46 mil e tantos. Não gastei muito tempo ajustando valores no docker composer, mas mesmo assim deu só 4% de knockouts. Antes dava 20%, 30% ou mais. Mas isso prova meu ponto: o gargalo nunca foi nem falta de CPU e nem falta de RAM, nem postgres e nem nginx, era um problema de I/O e isso é gerenciável em quase qualquer linguagem. Se Crystal e Ruby batem o máximo, qualquer outra deveria conseguir.</p>
<p>Peguei a versão de Lean4 e ainda tem algum bug de validação, mas bate acima dos 47 mil inserts. Mais uma versão que vence o stress test sem suar. Peguei a versão em Node.js do Lucas e também bateu no máximo de 46 mil e tantos. E isso foi se repetindo. A versão em Python e Sanic do Ian Cambrea, a versão em V Lang do Carlos, a versão em Go do Luan, a de PHP com Swoole do Lauro, a outra versão em Rails do Lazaro, todos batem o máximo.</p>
<p>De bônus, resolvi testar a versão em C++ do Lucas William que tinha ficado em 12o lugar com menos de 35 mil inserts, também bate perto do máximo agora, 43 mil inserts. Não bateu por algum bug na validação. Enquanto algumas versões faltava validação e deixava inserir mais que o máximo, nesta versão tem alguma coisa filtrando demais e impedindo de gravar mais. Olhando os gráficos claramente podemos ver que tá sobrando recursos. Zero knockouts, não tá perdendo requisições, então tem recursos sobrando.</p>
<p>Nesta segunda-feira, tava todo mundo só falando do lançamento da versão 1.0 do Bun, a nova alternativa a Node.js e Deno que promete ser mais performático. Ainda é cedo pra dizer e ainda nem é totalmente compatível. Mas o Yuri Gomes tinha participado da rinha com a versão pré-lançamento do Bun, 0.8 e tinha ficado em 14o lugar com menos de 28 mil inserts. Mas com network mode em host, ele também bateu o máximo e com tempos similares da versão de Rust. Neste exemplo com escopo pequeno, seu desempenho foi à altura do hype, o que é bem promissor.</p>
<p>Alguns ficaram me cutucando pra mexer em C# também. Eu não tinha mexido em nenhum até agora porque a versão do André e do Albert já provaram que alcançava o máximo. Mas, nada mais justo do que finalizar essa segunda-feira super corrida pós-feriado, do que avaliar a versão do nosso querido organizador da rinha, Zanfranceschi. Por coincidência ele participou com uma versão de Dotnet.</p>
<p>Não vi ele no ranking oficial. Acho que, como organizador ele se absteve de se colocar na disputa, participou pela diversão mesmo. Mas cometeu o mesmo erro que eu e outros: assumimos que postgres ia ser o gargalo e colocamos cache de Redis antes de realmente medir a necessidade. Mas espero que a esta altura tenha ficado claro que postgres nunca foi o gargalo. Não estou dizendo que nunca é, em outros projetos pode ser, mas neste desafio não era. Por isso é caso a caso, precisa medir.</p>
<p>Por causa disso ele implementou um cache em memória usando um ConcurrentDictionary. Lembra aquelas pesquisas com select LIKE que mencionei antes? No Postgres, se indexarmos com GIST e trigrams, fica bem rápido, mas com uma estrutura rudimentar como um dicionário, um hash, ele é obrigado a percorrer 100% do dicionário o tempo todo, extremamente lento.</p>
<p>Isso limitou o resultado a 40 mil inserts. Eu tentei fazer os mesmos ajustes de todo mundo: limitar vazão do nginx, mexer no pool de conexões, dividir melhor recursos do docker composer, colocar em network mode host, mesmo assim não dava. Minha suspeita é que esse ConcurrentDictionary matou esta versão, se alguém quiser tentar tirar e retestar, vá no repositório dele nos links abaixo.</p>
<p>De qualquer forma, com a ajuda de todo mundo, do Leandro, do MrPowerGamer, do Gabriel Oliveira, do Vinicius Ferraz e outros, conseguimos atingir o objetivo não oficial que eu estabeleci pra rinha: vencer o stress test e colocar todas as linguagens no mesmo 1o lugar ou dentro dos Top 3. Fizemos isso com 3 versões de Ruby, Node.js, PHP, Python, Java e Kotlin, Bun, V Lang, Go Lang, Crystal, C++, todo mundo bateu os máximos.</p>
<p>As duas exceções que não bateram foi a de C# do Zan e a de Elixir do Gabriel. Em ambos os casos, acho que a lógica de cache engargalou. Que é lição aprendida: não faça otimização prematura. Em vez de ficar mais rápido, pode ficar mais lento. Se tirar esse cache, provavelmente bateriam os Top 3. E falando em Top 1, agora que consegui cumprir minha missão nesta rinha, só faltava uma última coisa que queria fazer: desenferrujar meu Rust mexendo na versão do ganhador oficial, o Vinicius Fonseca. Finalmente chegamos no 16o dia da minha saga, dia 13 de setembro.</p>
<p>Assim como a versão em C++ do Lucas, ou em Node do outro Lucas, que colocaram tudo num arquivo só. O Vinicius fez a mesma coisa em Rust. Repetindo: é uma rinha, o critério não era o código durar pra sempre, era só aguentar o stress test. Beleza do código, mantenabilidade, legibilidade, segurança, nada disso eram critérios. Eu que fui narigudo e fiquei fuçando e mexendo no código dos outros sem ninguém pedir. Meu objetivo era só desenferrujar meus skills. Uma boa forma de fazer isso era pegar o código dos outros e sair reorganizando.</p>
<p>Essa foi a primeira coisa que fiz no código do Vinicius: refatorar. Rust já não é uma linguagem muito fácil de bater o olho. Cheio de anotações, um monte de .clone() e .unwraps() e tipos e generics pra lá e pra cá. E tudo amontoado. Dividindo em módulos fica mais fácil de bater o olho. Mas eu queria corrigir o bug que o MrPowerGamerBR identificou na análise dele: a condição de corrida que acabava deixando sobrar mais linhas na tabela do que o máximo possível.</p>
<p>Isso acontece porque o Vinicius manda inserir mais de 500 linhas na inicialização pra algum tipo de aquecimento. Que eu saiba o Rust não tem e nem precisa de um just in time compiler, um JIT, já que é pré-compilado em binário. Aquecimento faz mais sentido pra Java, que tem JIT. Enfim, ele deixa essa rotina rodando numa corrotina da biblioteca Tokio, a mais famosa que dá suporte à semântica de Futures, o equivalente a Promises de Node.js.</p>
<p>Na sequência, inicia um segundo Future assíncrono. Essa rotina tem um timer, um sleep de 2 segundos. Quando passa 2 segundos  manda um comando de SQL pra deletar as linhas de aquecimento. É aqui que acontece a condição de corrida. Nada garante que 2 segundos é suficiente pra inserir essas 500 linhas. Muitas vezes o delete acontece antes. Daí sobra linhas na tabela.</p>
<p>Não havia necessidade do delete estar num future separado. Bastava disparar o delete depois do último insert. Ou uma gambiarra seria aumentar esse timer de 2 pra mais segundos. Eu fiz isso. E pra garantir, alterei o último endpoint, o de contagem final da tabela, pra filtrar os registros de aquecimento. Com isso batemos os mesmos 46 mil e tantos inserts, como todo mundo. Depois dêem uma olhada no meu pull request pra conseguir entender melhor essa versão de Rust.</p>
<p>Eu não curto competições de código. Não porque sou contra. Cada um com seu entretenimento. Não tem nada de errado em competir com código. Só não pode achar que código de competição é a mesma coisa que código pra projeto de verdade. As técnicas são diferentes. Os requerimentos são diferentes. Muitos truques que mencionei, não devem ser usadas sem critério, vai dar errado. E nenhuma comparação generalizada deve ser feita entre as diferentes tecnologias usadas.</p>
<p>Uma competição no estilo dessa rinha serve pra mostrar quem foi mais esperto, quem teve mais sorte, quem foi mais cuidadoso. O Zan teve bastante trabalho pra organizar e pra avaliar um a um. Cada rodada do stress test do Gatling custa 3 minutos. 50 projetos que passaram significa que ele gastou quase 3 horas só esperando os testes rodarem, sem contar os outros projetos participantes que tinham bugs. Agora que passou é fácil dizer &ldquo;ah, poderia ter feito X melhor, poderia ter feito Y melhor&rdquo;, mas a posteriori tudo é fácil.</p>
<p>Eu gastei mais de 2 semanas avaliando 18 repositórios em 16 linguagens diferentes. Eu tive a sorte de contar com a contribuição de diversos outros desenvolvedores que se animaram pra investigar junto. Acho que conseguimos desvendar todos os truques possíveis desta rinha, incluindo vencer o stress test do Gatling. Mas isso não é competição, é análise técnica. Não é divertido pra todo mundo, só pra nós mais nerds.</p>
<p>Mesmo se você for torcedor de um Neymar, Messi, Cristiano Ronaldo, seja lá quem acha que é o melhor jogador do mundo, se no dia do jogo eles torcerem o tornozelo e não participarem, o resultado vai ser o que der no dia. Quando perdem, não significa que são ruins no geral. Só significa que perderam aquele jogo, ponto final. É o conjunto da obra que determina os melhores e não um jogo individual. Sorte faz parte do jogo. Mesma coisa nesta rinha. Só porque um Java ou um PHP não apareceram no topo, desta vez, não tem nenhuma correlação com a qualidade deles em outros jogos.</p>
<p>Amador desbocado é quem faz mais barulho nas redes sociais, então meu objetivo foi educacional: demonstrar porque estão errados e fazer todos entenderem que sim: com mais tempo, experiência, skill, qualquer uma das linguagens tinha capacidade de alcançar resultados semelhantes, alguns com mais trabalho, alguns com menos trabalho, alguns gastando mais recursos, alguns menos, não importa. Sua linguagem não te define.</p>
<p>Assistiram meus videos de Sua Linguagem Não é Especial? O que eu falei lá? Um bom desenvolvedor é promíscuo com linguagens. Eu mexo em qualquer uma, a qualquer hora, quando quiser e como quiser. O que eu falei em videos como Aprendendo a Aprender ou Aprendizado na Beira do Caos? Várias das linguagens que mexi nesta saga, ou nunca tinha mexido, como Nim, Lean4, V Lang, ou tava enferrujado fazia anos, como C++, C#, Rust, Go, PHP, Crystal. Mesmo linguagens que tenho um pouco mais de intimidade, como Ruby, Javascript, Python, tive que quebrar a cabeça.</p>
<p>O que eu fiz? Instalei uma a uma e fui fuçar código dos outros. Quebrei elas várias vezes. Consertei, refatorei, reorganizei, fiquei em muita tentativa e erro, reimplementei técnicas diferentes, apliquei técnicas de uma linguagem em outra, li muito código fonte. Mesmo sem nunca ter mexido num framework como Lucky, mesmo Bun que acabou de lançar, não levou mais que algumas horas pra me acostumar. Essa é a diferença: as primeiras linguagens que aprender vai levar meses, anos pra dominar.</p>
<p>As próximas vão ficando exponencialmente mais rápido de pegar, o suficiente pra conseguir fazer o básico. Novamente, demonstrei este ponto na prática. Em um dia já dá pra fazer alguma coisa, se me der poucos dias, consigo me virar em qualquer uma. Em poucas semanas já vou saber truques avançados de cada uma. Eu consigo me tornar produtivo numa linguagem nova em 1 mês. Onde vou levar mais tempo? Em linguagens que tem paradigmas muito diferentes, como Haskell, Ocaml, F#, Clojure ou o Lean4 que vimos hoje.</p>
<p>Com todos esses disclaimers e avisos feitos, ouçam até o final antes de sair comentando. Sim, Rust e Go aparecem no topo do ranking oficial porque, se não cometer erros graves, o tempo de resposta individual de cada requisição vai ser na faixa abaixo de 1 milissegundo, de microssegundos. Frameworks como Rails ou um Laravel da vida vão responder na faixa de milissegundos. Quanto menor o tempo de resposta, mais requisições dá pra responder no mesmo período de tempo. Rust ou Go realmente tem mais velocidade bruta e conseguiram bater a barreira acima dos 40 mil inserts, mesmo com docker em modo ponte, e chegar no máximo durante o evento.</p>
<p>Porque todo mundo não passa a adotar Rust, Go, V Lang, Zig, de uma vez? Tudo compilado em binário nativo super rápido? Vamos colocar assim: não existe um único motivo. Não pensem que as pessoas ou empresas são burras e não sabem que são rápidos. C e C++ existem muito antes da maioria de vocês nascerem e sempre foram rápido. Todo mundo sabe disso.</p>
<p>No caso de um V Lang ou Zig é fácil explicar: é porque são novos demais e instáveis demais, tem suporte baixo, tem ferramental imaturo, mudando o tempo todo e quebrando compatibilidade, falta documentação na forma de livros, cursos justamente porque ainda estão mudando muito. Esses não tem como ser adotados em larga escala. Só early adopters muito dedicados. Só empresas menores e com maior margem de assumir riscos, com um co-fundador ou diretor técnico muito bom pra liderar.</p>
<p>Mas tudo é uma questão econômica. Não me xinguem, eu sou só mensageiro, mas a realidade é que a enorme maioria dos programadores não tem capacidade de aprender a usar linguagens de baixo nível, no nível que seria necessário pra serem produtivos e produzir código bem feito. O fato da maioria das empresas não estar desesperadamente procurando esse perfil é a evidência. Sempre existem exceções, não comece com &ldquo;ah, mas eu conheço fulano ou ciclano&rdquo;. Exceção não define a regra.</p>
<p>Fica a dica: se tornar um programador avançado nas tecnologias mais difíceis, te torna uma mosca branca, que vale muito mais
que os outros. O que eu sempre falo? Tudo que é fácil pra você aprender, também é fácil pra qualquer outro. Quanto mais gente tiver fazendo a mesma coisa, mais barato todos vão ser. O valor está em ser bom naquilo que poucos tem capacidade de conseguir. Onde a barreira é mais alta. E isso vai ser Rust, Go, C++, pra resolver problemas muito mais complicados do que fazer Cruds ou APIs.</p>
<p>Reflitam sobre vocês mesmos, porque estão estudando Java ou C# ou Javascript? Porque são as posições que tem mais vagas. Rust não se vê vagas todos os dias. Estão vendo? É o problema do ovo e da galinha. Você não quer investir tempo pra aprender Rust porque não vê vagas todo dia. E as empresas não oferecem vagas porque não vêem trocentos cursos e estudantes aprendendo. Por isso que esse tipo de movimento, ou começa em startups arrojadas ou dentro de gigantes como uma Microsoft. As vagas existem, bem pouco, e pagam bem, só que são bem mais exigentes. Mas ninguém gosta de vagas difíceis, só querem as que acham fácil.</p>
<p>Não é prático nem realista manter aplicações web complexas e gigantes em linguagens assim. Imagine um Shopify, GitHub, MercadoLivre, Amazon ou iFood da vida, 100% em linguagens de baixo nível. Partes do sistema, que são mais sensíveis a performance, já adotam coisas como Go, mas é a menor parte. Pro uso do dia a dia, dashboard, relatórios, cruds e coisas assim, não compensam. Seria um enorme desperdício dar tarefas consideras commodity como fazer relatórios, pra um tipo de programador raro e caro, com de Go ou Rust.</p>
<p>Adotar uma tecnologia só porque é mais rápida, não é um bom argumento. Hoje em dia todas são rápidas o suficiente. Mesmo linguagens que um programador de Rust chamaria de lentas, como Javascript ou Python, conseguimos atingir patamares similares com um custo um pouco maior de máquina. Como demonstrei aqui, na maioria dos casos, é possível dobrar a performance, triplicar, tendo o conhecimento adequado, sem quase modificar código, nem precisar jogar tudo fora e reescrever tudo em Rust. É isso que programadores como eu fazemos.</p>
<p>No fim dia, dado a escassez de programadores de Rust ou Go e a dificuldade de achar talentos que consigam ser treinados nisso, em pouco tempo, sai bem mais barato compensar com servidores mais parrudos. Alguns pensam que pra dobrar a performance da sua aplicação em PHP, em Java, Dotnet, Python, Node, precisa jogar tudo fora e reescrever em Go. Tá errado.</p>
<p>Só vai garantir que os erros por ignorância que foram cometidos com um PHP, vão se repetir em Go. Programador ruim, vai fazer código ruim em PHP ou em Go. Não existe nenhuma garantia que vai ficar mais rápido se você continua não sabendo porque o anterior tava lento. Por outro lado, por experiência, já melhorei muito sistema de verdade mexendo nos 20% de problemas que dão 80% de retorno. É sempre assim, às vezes são besteiras de pouquíssimas linhas, como as que mostrei durante este video.</p>
<p>Como falei antes, são vários motivos, custo-benefício sendo o principal. No geral o maior problema é que é muito difícil formar bons programadores que consigam tirar vantagem dessas tecnologias. O custo-benefício não compensa. Não significa que essas linguagens não tem chance, significa que esse mercado sempre vai ser de nicho. Mas pra quem tem o talento e a capacidade, é um nicho que paga muito melhor. É só ser bom.</p>
<p>Enfim, hoje eu só quis resumir minha saga de como explorei 16 linguagens, claro que superficialmente, em 16 dias. Não deu pra detalhar cada truque, mas deixamos tudo anotado, e no próximo video vou explicar o truque por trás da mágica: como tudo isso funciona, por que funciona e, mais importante, quando usar e quando não usar. Se ficaram com dúvidas mande nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>performance</category><category>linguagens-de-programacao</category><category>backend</category><category>akitando</category></item><item><title>[Akitando] #137 - Games em Máquina Virtual com GPU Passthrough | Entendendo QEMU, KVM, Libvirt</title><link>https://akitaonrails.github.io/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt/</guid><pubDate>Wed, 01 Feb 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/IDnabc3DjYY"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Este episódio levou literalmente MESES pra eu conseguir fazer. Entre atualizar o hardware da minha máquina, testar todo tipo de combinação de configuração, até conseguir rodar jogos da Steam, num Windows virtualizado, a quase mesma velocidade que nativo.&lt;/p&gt;
&lt;p&gt;Hoje vamos entender o que é QEMU, KVM, Libvirt, como passar uma GPU da NVIDIA diretamente pra dentro de uma VM, e como garantir a máxima performance na virtualização, incluindo entender como diversos componentes de hardware e do Linux funcionam.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/IDnabc3DjYY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Este episódio levou literalmente MESES pra eu conseguir fazer. Entre atualizar o hardware da minha máquina, testar todo tipo de combinação de configuração, até conseguir rodar jogos da Steam, num Windows virtualizado, a quase mesma velocidade que nativo.</p>
<p>Hoje vamos entender o que é QEMU, KVM, Libvirt, como passar uma GPU da NVIDIA diretamente pra dentro de uma VM, e como garantir a máxima performance na virtualização, incluindo entender como diversos componentes de hardware e do Linux funcionam.</p>
<p>Este conteúdo vai ser DENSO, então não deixem de usar os capítulos abaixo pra se orientar e consultar todos os links que deixei listado aqui.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:00:44 - Cap 1: Meu Setup (até 2024, senão podem pular)</li>
<li>00:05:51 - Cap 2: Introdução à Virtualização | Por que não Dual Boot?</li>
<li>00:08:26 - Cap 3: O que é IOMMU | Setup das GPUs</li>
<li>00:13:46 - Cap 4: Redes Virtuais | Minha placa 10 Gigabits</li>
<li>00:17:57 - Cap 5: Criando a Máquina Virtual | Virt-Manager</li>
<li>00:19:51 - Cap 6: Recapitulando UEFI | O que é OVMF?</li>
<li>00:23:06 - Cap 7: Entendendo CPUs | Pinagem de CPUs</li>
<li>00:33:01 - Cap 8: O que é QEMU? E KVM? | Libvirt</li>
<li>00:38:58 - Cap 9: Limitando CPUs do Host | Cgroups</li>
<li>00:43:06 - Cap 10: Configurações de Performance | Features Importantes</li>
<li>00:45:32 - Cap 11: Monitor Virtual e Streaming | SPICE e QXL</li>
<li>00:47:22 - Cap 12: Configurando Áudio | Pulseaudio e Pipewire</li>
<li>00:50:01 - Cap 13: PCI Passthrough | NVIDIA GPU</li>
<li>00:50:27 - Cap 14: Keyboard, Video, and Mouse | KVM via EVDEV</li>
<li>00:52:47 - Cap 15: Instalando Windows em Disco VirtIO | Fedora VirtIO ISO</li>
<li>00:57:15 - Cap 16: Demonstrando Performance com Games | GPU Passthrough</li>
<li>00:57:53 - Cap 17: Anti-Cheat em Elden Ring | Dual Boot</li>
<li>01:00:24 - Cap 18: Lidando com Monitor com 2 Inputs de Video | Escolhendo Inputs</li>
<li>01:03:08 - Cap 19: Lidando com Suspend do Host | Desligando Suspend</li>
<li>01:05:04 - Cap 20: Conclusão | Mais Coisas</li>
<li>01:07:49 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF"target="_blank" rel="noopener">https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF</a></li>
<li><a href="https://wiki.gentoo.org/wiki/GPU_passthrough_with_libvirt_qemu_kvm"target="_blank" rel="noopener">https://wiki.gentoo.org/wiki/GPU_passthrough_with_libvirt_qemu_kvm</a></li>
<li><a href="https://github.com/virtio-win/kvm-guest-drivers-windows"target="_blank" rel="noopener">https://github.com/virtio-win/kvm-guest-drivers-windows</a></li>
<li><a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.229-1/"target="_blank" rel="noopener">https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.229-1/</a></li>
<li><a href="https://github.com/rockowitz/ddcui"target="_blank" rel="noopener">https://github.com/rockowitz/ddcui</a></li>
<li><a href="https://github.com/rockowitz/ddcutil/issues/35"target="_blank" rel="noopener">https://github.com/rockowitz/ddcutil/issues/35</a></li>
<li><a href="http://www.ddcutil.com/commands/"target="_blank" rel="noopener">http://www.ddcutil.com/commands/</a></li>
<li><a href="https://github.com/olavmrk/usb-libvirt-hotplug"target="_blank" rel="noopener">https://github.com/olavmrk/usb-libvirt-hotplug</a></li>
<li><a href="https://getlabsdone.com/how-to-install-windows-11-on-kvm/#Add-the-windows-11-virtio-driver"target="_blank" rel="noopener">https://getlabsdone.com/how-to-install-windows-11-on-kvm/#Add-the-windows-11-virtio-driver</a></li>
<li><a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-adding_storage_devices_to_guests-adding_hard_drives_and_other_block_devices_to_a_guest"target="_blank" rel="noopener">https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-adding_storage_devices_to_guests-adding_hard_drives_and_other_block_devices_to_a_guest</a></li>
<li><a href="https://linuxhint.com/install_virtio_drivers_kvm_qemu_windows_vm/"target="_blank" rel="noopener">https://linuxhint.com/install_virtio_drivers_kvm_qemu_windows_vm/</a></li>
<li><a href="https://www.tecmint.com/manage-kvm-storage-volumes-and-pools/"target="_blank" rel="noopener">https://www.tecmint.com/manage-kvm-storage-volumes-and-pools/</a></li>
<li><a href="https://passthroughpo.st/simple-per-vm-libvirt-hooks-with-the-vfio-tools-hook-helper/"target="_blank" rel="noopener">https://passthroughpo.st/simple-per-vm-libvirt-hooks-with-the-vfio-tools-hook-helper/</a></li>
<li><a href="https://www.libvirt.org/hooks.html"target="_blank" rel="noopener">https://www.libvirt.org/hooks.html</a></li>
<li><a href="https://archlinux.org/packages/extra/x86_64/qemu-emulators-full/"target="_blank" rel="noopener">https://archlinux.org/packages/extra/x86_64/qemu-emulators-full/</a></li>
<li><a href="https://unix.stackexchange.com/questions/30106/move-qcow2-image-to-physical-hard-drive"target="_blank" rel="noopener">https://unix.stackexchange.com/questions/30106/move-qcow2-image-to-physical-hard-drive</a></li>
<li><a href="https://libguestfs.org/virt-resize.1.html"target="_blank" rel="noopener">https://libguestfs.org/virt-resize.1.html</a></li>
<li><a href="https://www.cyberithub.com/resize-qcow3-image-with-virt-resize-kvm-tools/"target="_blank" rel="noopener">https://www.cyberithub.com/resize-qcow3-image-with-virt-resize-kvm-tools/</a></li>
<li><a href="https://loc-clan.com/files/win10"target="_blank" rel="noopener">https://loc-clan.com/files/win10</a></li>
<li><a href="https://www.reddit.com/r/VFIO/comments/l8g6iy/pc_suspends_itself_while_in_vm_due_to_suspend/"target="_blank" rel="noopener">https://www.reddit.com/r/VFIO/comments/l8g6iy/pc_suspends_itself_while_in_vm_due_to_suspend/</a></li>
<li><a href="https://www.linaro.org/blog/the-evolution-of-the-qemu-translator/"target="_blank" rel="noopener">https://www.linaro.org/blog/the-evolution-of-the-qemu-translator/</a></li>
<li><a href="https://github.com/foxlet/macOS-Simple-KVM"target="_blank" rel="noopener">https://github.com/foxlet/macOS-Simple-KVM</a></li>
<li><a href="https://github.com/foxlet/macOS-Simple-KVM"target="_blank" rel="noopener">https://github.com/foxlet/macOS-Simple-KVM</a></li>
<li><a href="https://passthroughpo.st/explaining-csm-efifboff-setting-boot-gpu-manually/"target="_blank" rel="noopener">https://passthroughpo.st/explaining-csm-efifboff-setting-boot-gpu-manually/</a></li>
<li><a href="https://passthroughpo.st/explaining-csm-efifboff-setting-boot-gpu-manually/"target="_blank" rel="noopener">https://passthroughpo.st/explaining-csm-efifboff-setting-boot-gpu-manually/</a></li>
<li><a href="https://github.com/vanities/GPU-Passthrough-Arch-Linux-to-Windows10"target="_blank" rel="noopener">https://github.com/vanities/GPU-Passthrough-Arch-Linux-to-Windows10</a></li>
<li><a href="https://github.com/rockowitz/ddcutil/issues/35#issuecomment-337919874"target="_blank" rel="noopener">https://github.com/rockowitz/ddcutil/issues/35#issuecomment-337919874</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Video de hoje vai ser mais pra mim do que pra vocês. Quem me acompanha no Insta sabe o que eu ando mexendo e alguns meses atrás viram que eu estava brincando com upgrade no meu PC, NAS e, principalmente, PCI passthrough da GPU pra máquinas virtuais. Quero mostrar como ficou, como estou usando, mais ou menos os principais conceitos envolvidos. Embora não seja um passo a passo, vou explicar coisas que normalmente não se vê numa instalação normal de Linux e detalhes sobre como um PC funciona. Mais importante, vou explicar todos os componentes pra rodar máquinas virtuais em Linux e como elas funcionam. Se queria aprender mais sobre virtualização, hoje é o dia.</p>
<p>(&hellip;)</p>
<p>Eu já fiz dois videos falando do meu setup anterior então assistam depois. Mas agora estou com um PC bem potente, com CPU AMD Ryzen 9 5950X de 16 núcleos ou cores e 32 threads, 2 pentes de memória DDR4 de 4 Ghz funcionando com perfil XMP, ou seja, com overclock, da G.Skill, placa mãe Aorus X570S. Não é a melhor placa mas dá conta do recado. Ela tem 2 slots M.2 pra NVME que é SSD muito mais rápido, um Samsung 970 Evo Plus de 2TB onde tá instalado Windows 11 nativamente como dual boot e outro NVME primário Samsung também de 1 TB onde está meu Manjaro GNOME.</p>
<p>Além deles preenchi 3 slots SATA com SSDs normais Samsung modelos 860 e 870 de 1 TB que uso só pra cache do DaVinci Resolve, outro de 4TB que era meu antigo drive pra jogos da Steam e que recentemente encheu então comprei um de 8TB pra mover tudo pra lá. Vou fazer esse de 4TB ser meu novo cache pro DaVinci por enquanto. Óbvio que não precisa de tudo isso só pra cache, mas não tenho outro uso pra ele por enquanto, então beleza.</p>
<p>O Linux boota usando a GPU primária da AMD, um modelo fraquinho que é o RX 6400. Essa placa não é muito melhor que uma antiga NVIDIA GTX 1050 de 2016. Mas é mais do que suficiente pra coisas como tocar video 4K sem perder frames, renderizar aplicativos de produtividade como terminal ou editor de texto. Só pra programar e navegar na Web é muito mais do que suficiente. Mas obviamente é  imprestável pra games mais pesados que um CS:Go. </p>
<p>Pra renderização pesada de video quando edito os episódio do canal com DaVinci Resolve, ou pra brincar de modelagem 3D com Blender ou pra jogos pesados como God of War da vida, aí tenho uma GPU secundária que fica desligada no Linux. A NVIDIA RTX 3090 com 24 Gigabytes só de memória de video. Esse sim é um monstro. </p>
<p>Recentemente saiu a RTX 4090 que tem quase o dobro da performance da minha, mas também custa o dobro do preço, usa muito mais energia e ocupa quase o dobro do espaço dentro do gabinete, e pra funcionar decentemente o certo seria trocar minha placa mãe pra uma que suporte memória DDR5 e trocar minha CPU da série 5000 pra nova série 7000 que também acabou de sair pra não ter gargalo. Na prática o custo-benefício ainda não compensa. Vou fazer esse upgrade só daqui mais uns 2 anos.</p>
<p>Falando em resolução, eu uso setup de dois monitores, a principal é a Asus ROG TUF de 31&quot;, resolução de 2160 por 1440 pixels, ou seja, 2K a 144 hz. O topo de linha pra gamer seria um monitor de micro LEDs 4K de 300 hz HDR+, mas pro meu uso tá mais que bom. Eu não participo de esports então mais que 1440p e 144hz eu nem ia perceber tanta diferença e só ia pesar mais na GPU. Nessa configuração consigo jogar qualquer jogo pesado da Steam em Ultra High com Ray Tracing e ainda ter mais de 100 frames por segundo.</p>
<p>Meu monitor secundário é um Samsung de 29&quot; 4K e 60hz. Esse eu exagerei um pouco. É o monitor que uso na vertical pra deixar coisas como meu Discord, Gmail, gráficos da bolsa e coisas assim pra ficar dando uma olhada. Poderia ser um monitor de 24&quot; e não precisava ser 4K porque uso em 1440p pra ficar igual ao principal. Enfim, o resto não é importante pro video de hoje mas só pra ficar completo, tenho um microfone BM 800 e uma webcam Logitech Brio de 1080p de resolução. Não sou, nem quero ser podcaster nem streamer. Pra calls de Zoom tá mais que bom.</p>
<p>Pra áudio, em vez de usar o que vem na placa mãe liguei um DAC externo da Topping via USB. DAC é um conversor digital pra analógico. Ele recebe o áudio digital via USB e converte em onda pra mandar pra um pre-amp, que pega esse áudio analógico convertido com sinal baixo e amplifica pra nível de linha pra mandar pras caixas de som ou fone de ouvido. As caixas de som não nem nada de especial, são 2 Edifier. Pra usar na mesa perto de mim é o suficiente. Já o fone que uso é um Sennhaiser HD 650 que é um modelo de entrada pra quem quer um som de referência, não modificado.</p>
<p>Meu mouse é um Logitech MX Master 3 sem fio, o melhor mouse de produtividade que existe. Super confortável e excelente pra trabalhar em edição de video por exemplo. Ele não é feito pra jogar, embora nada impeça. Um mouse gamer tem metade do peso desse Logitech Mas como falei, não participo de esports e também não jogo FPS. Não curto Valorant, Apex Legends, Overwatch nem nada disso. Só jogo single player e com gamepad. E gamepad os meus preferidos são o de Xbox One X, depois o modelo SN30 pro+ da 8BitDo que compartilho com meu notebook, e o de PS4 pra emuladores como Dolphin ou CEMU pra jogos da Nintendo porque gamepad de PS4 tem acelerômetro e giroscópio, coisa que do Xbox não tem.</p>
<p>Todo mundo que acompanha o canal sabe que sou um sommelier de teclados e meu favorito até hoje é o Moonlander da ZSA. Dá bastante trabalho pra treinar e se acostumar, mas depois que aprendi realmente a usar um teclado split, separado no meio, com layout ortolinear, não consigo mais voltar pra um teclado normal. Eu sofro um pouco quando preciso usar do notebook. Quem não sabe o que é isso, assista meu terceiro video sobre teclados, que explico em detalhes.</p>
<p>O objetivo hoje é eu conseguir instalar uma máquina virtual Windows em cima do meu Manjaro e passar a GPU da NVIDIA inteiramente pra ele. Meu Manjaro continua funcionando normalmente via a GPU da AMD, e na máquina virtual a RTX 3090 vai ficar exclusiva só pro Windows virtualizado. Muitos podem se perguntar &ldquo;ué, mas não é mais fácil só fazer um dual boot?&rdquo; E sim, é mais fácil mesmo. Mas vejam esta tela do programa Virt Manager que eu instalei. </p>
<p>Tudo isso são máquinas virtuais. Eu tenho uma instalação de Garuda que é outra versão de Arch Linux mais voltado pra gamers. Eu tenho um Ubuntu que uso pra testes e eu tenho 4 instalações de Windows 11. Uma que está como dual boot, instalado numa NVME e outras 3 que rodam num disco virtual em rede. Eis porque eu não queria só depender de dual boot: queria poder instalar outros Windows pra tarefas ou testes diferentes em separado, sem ter que reparticionar meu SSD toda hora e ficar reconfigurando dual boot. Do jeito que fiz, posso instalar um novo OS virtualizado a qualquer momento, ou destruir também, sem afetar a estabilidade do meu sistema.</p>
<p>Veja que tenho um MacOS também. Infelizmente é lento porque Apple parou de dar suporte pra GPU da NVIDIA faz alguns anos. Só GPUs antigas da NVIDIA funcionariam, como a GTX série 1000 de mais de 5 anos atrás, e só até o MacOS Mojave de 2021. Simplesmente não existem drivers pra usar a RTX 3090 num Mac. Pra conseguir acelerar esse MacOS virtualizado precisaria passar a GPU da AMD, mas como é minha principal, que o host Manjaro usa, ia ficar complicado então por enquanto não vai rolar Mac.</p>
<p>Falando em suporte de hardware e abrindo a página de PCI passthrough do ArchWiki, a primeira coisa que ele vai explicar é que precisa checar se o meu hardware suporta o que precisa. A primeira coisa é mais óbvia, minha CPU precisa suportar instruções de virtualização o VT-x ou VT-d. Tem que entrar na BIOS e checar se tá ativado. Muitas vezes vem desligado. Hoje em dia toda CPU suporta sem problemas, mas CPUs de uns 5 anos ou mais pra trás, é bom checar. No Wiki também fala sobre modelos de placa mãe. Mas de novo, só se preocupa com isso se seu hardware for muito velho.</p>
<p>Os passos 1, 2 e 3 do Wiki podem ser automatizados por essa ferramenta <code>gpu-passthrough-manager</code>, instala com <code>yay</code> ou <code>pacaur</code> ou seja lá qual gerenciador de AUR você usa. Eu gosto do <code>yay</code>. Ele vai fazer duas coisas importantes: configurar a linha de comando que carrega a kernel no boot pra habilitar IOMMU e depois que rebootar, vai separar a GPU da NVIDIA pra não carregar mais os drivers no boot, já que precisa estar desligado pra passar pra máquina virtual. Vamos entender.</p>
<p>IOMMU, ou Input Output Memory Management Unit é um recurso que ligamos na kernel e que a CPU por baixo precisa suportar. A grosso modo é assim, a CPU e dispositivos como teclado ou placa de video, normalmente se comunicam via endereços de memória direta entre um e outro. O problema é que se a CPU de verdade já está falando com o dispositivo, não teria como outra CPU virtual compartilhar os mesmos dispositivos. </p>
<p>Em vez disso o controlador de IOMMU cria endereços virtuais que mapeiam pros reais e esses endereços virtuais podem ser passados pra máquinas virtuais, dando acesso direto aos dispositivos sem ter que passar pela CPU host pra intermediar essa comunicação. Isso é crucial pra máquina virtual conseguir falar diretamente com a GPU ou com outros dispositivos, por exemplo se colocar uma segunda placa de rede de verdade no PC, poderia passar inteira pra máquina virtual.</p>
<p>O Wiki explica como ligar isso, mas depois pede pra criar um script com esse código aqui e executar. O resultado vai ser algo parecido com isso aqui. Esses são os diversos grupos de IOMMU que meu sistema organiza. O que me importa é este Grupo 30 que tem só dois dispositivos, a RTX 3090 e um controlador de áudio também da NVIDIA. E o importante é que não tem mais nenhum outro dispositivo compartilhando o mesmo grupo. Se tivesse, na hora de passar a NVIDIA pra máquina virtual precisaria passar o outro dispositivo nada a ver junto, e dependendo do que fosse, poderia ter problemas. O ideal é estar assim: o que eu quero passar, isolado num grupo separado. Significa que passamos pelos pré-requisitos, meu hardware suporta o que quero fazer.</p>
<p>Até esse ponto, se fizer uma instalação normal de Manjaro, o identificador de hardware acha tanto minha AMD RX 6400 quanto a RTX 3090 e carrega os drivers dos dois. Mas pra poder passar pra máquina virtual, preciso que no boot, o driver da NVIDIA não seja carregado. Pra isso vamos usar um truque: carregar um driver dummy, de mentira, que vai se pendurar e bloquear a GPU. Pra isso usamos um driver VFIO que é um módulo que vem na kernel do Linux. Aquela ferramenta que falei antes, o <code>gpu-passthrough-manager</code> vai fazer isso pra gente, só selecionar os dois dispositivos da NVIDIA e deixar ele fazer o trabalho. Na Wiki tem o jeito manual também, mas vou pular.</p>
<p>Agora chegou a hora de instalar e configurar o software de virtualização propriamente dito. Precisamos dos pacotes <code>qemu-desktop</code>, <code>libvirt</code>, <code>edk2-ovmf</code> e o <code>virt-manager</code>. Pra rede precisa do pacote <code>dnsmasq</code>. Quando instalar tudo, só habilitar o libvirt pro SystemD carregar no boot com <code>sudo systemctl enable libvirtd</code> e já dar start nele também. Pronto, do ponto de vista de preparo e instalação já tá tudo pronto. Agora é a parte divertida, configurar a máquina virtual propriamente dita.</p>
<p>Uma máquina virtual vai se conectar com os mesmos dispositivos que seu Linux Host, o que boota nativamente de verdade, no caso o meu Manjaro. Coisas como os NVME  posso deixar outra máquina usar, se e somente se, não estiverem montadas. Lembra a diferença? Uma coisa é um dispositivo como <code>/dev/nvme1n1</code> outra coisa é o ponto de montagem <code>/mnt/blabla</code> que aponta pra uma partição no device. Se ficou confuso é porque você pulou meus videos de Linux onde explico isso. Se não sabe como Linux boota e como funciona por baixo, não vai entender nada deste video também. </p>
<p>Enfim, o ponto é que é possível passar dispositivos de armazenamento entre meu host e o guest contanto que só um deles tenha pontos de montagem. Aliás, pra facilitar, vou dizer host sempre que me referir ao meu Manjaro principal, e guest ou convidado é a máquina virtual. Continuando, dispositivos USB como teclado, mouse, hubs e tudo mais suportam hot swap. Isso deve ser intuitivo, podemos desconectar e reconectar que o OS reinicializa a quente, sem precisar rebootar. Quando ligamos um dispositivo USB no guest, é como se estivéssemos tirando o cabo do host e ligando no guest.</p>
<p>Placa de video só dá pra passar via grupo de IOMMU e se estiver descarregado, ou seja, com o driver stub de VFIO bloqueando. O driver VFIO é como se fosse um plugue esperando a tomada da máquina virtual ligar nele, saca? Só aí que a placa mãe vai ser ativada. Os monitores, por sua vez, via cabo HDMI ou DisplayPort estão conectados fisicamente nas placas de video e não na placa mãe, então quem controla os monitores são as placas de video. </p>
<p>Cada monitor suporta ligar vários cabos de entrada neles. Normalmente só ligamos um, mas já devem ter visto este menu que permite selecionar input sources diferentes, as diferentes portas na parte de trás. É assim que dá pra compartilhar um monitor com mais de um computador e ficar alternando os sinais de entrada. Meu monitor secundário vertical, o Samsung, tá ligado na placa da AMD. Meu monitor principal, o Asus, também tá ligado no AMD, então no boot o Wayland vê os dois e configura setup de extensão do meu desktop. </p>
<p>Antes que alguém pergunte, pra ter papel de parede que enche os dois monitores desse jeito uso um programa chamado HydraPaper. Ele funciona via esse programa gráfico mas também dá pra chamar via linha de comando, daí não é difícil fazer um script pra colocar no cron e assim mudar o papel de parede de tanto em tanto tempo. Fica de exercício pra vocês. Voltando, esse monitor Asus também tem outro cabo HDMI ligado nele, que se conecta com a placa de video da NVIDIA, que neste momento permanece desligado. Já já vamos acordar ele.</p>
<p>Mas não é só com placa de video que preciso me preocupar. Esqueci de mencionar, mas minha placa mãe X570S vem com uma placa de rede embutida de 2.5 Gigabits por segundo, que é excelente já que a maioria dos PCs e notebooks no máximo vem com placa de 1 Gigabit. E lembrando que 1 Gigabit é mais que suficiente pra maioria das pessoas porque a conexão de internet de provedores ainda é faixa de 100 megabits, ou seja, 1 Gigabit é 10 vezes mais banda do que precisa pra uma pessoa assistindo YouTube. Mas pra editar video via rede é pouco. Eu explico sobre isso nos videos da mini-série de redes vejam lá depois.</p>
<p>Eu queria poder guardar os videos que gravo pro canal, que são em 4K formato DNxHR, que é pouco comprimido pra manter qualidade, no NAS e apontar o DaVinci Resolve direto pra uma pasta de rede aberto nesse NAS. Assim não preciso ficar copiando os arquivos pro meu PC e depois copiando de volta pro NAS. Pra isso a velocidade entre os dois precisa ser alta. Por isso resolvi comprar um switch e uma placa de video PCI Express de 10 Gigabits, que é 1000 vezes mais rápido que seu plano de internet de 100 megabits.</p>
<p>Eu não vou passar essa placa diretamente pra máquina virtual porque uso ela no host também. Como quero compartilhar a mesma placa entre duas máquinas, preciso tipo criar uma placa de rede virtual, como o dispositivo TAP que expliquei no episódio de VPN e uma rede entre a placa virtual ou a placa real, aí tenho algumas opções. Vamos abrir o aplicativo Virt-Manager, que é uma interface gráfica que facilita configurar as coisas. Essa interface é opcional porque tudo que vou mostrar graficamente pode ser feito via linha de comando usando o comando virsh. Por exemplo, no meu terminal posso digitar <code>sudo virth net-list --all</code> e mostra as duas redes virtuais que já tinha configurado na minha máquina: default e network.</p>
<p>Voltando pro Virt-Manager, posso abrir no menu &ldquo;Edit&rdquo; e &ldquo;Connection Detail&rdquo; e olha só as mesmas duas redes aparecendo. Clicando em &ldquo;+&rdquo;  posso criar uma nova rede virtual, mas não preciso porque já tenho essa daqui que chamei de &ldquo;network&rdquo;, podia ser um nome mais criativo. Mas o importante é o nome do dispositivo virtual, a placa de rede de mentira, que dei o nome de &ldquo;virbr1&rdquo; porque na rede &ldquo;default&rdquo; já tinha &ldquo;virbr0&rdquo;. Configurei pra ser uma rede usando NAT pra sair pela minha placa real cujo nome é &ldquo;enp4s0&rdquo;. E como sei esse nome?</p>
<p>Lembram dos videos de rede? No terminal posso usar o comando &ldquo;ip&rdquo; pra mexer na rede. Se fizer <code>ip link show</code> mostra todos os dispositivos de rede. Lembram que falei que minha placa mãe já vinha com rede embutida de 2.5 Gigabits, esse é o dispositivo &ldquo;enp8s0&rdquo; que aparece logo embaixo do loopback. Estou escondendo o MAC Address deles, claro. Em seguida temos o &ldquo;enp4s0&rdquo; que falei que é a placa externa de 10 Gigabits. Em seguida temos dispositivos virtuais, &ldquo;virbr0&rdquo; e &ldquo;virbr1&rdquo;. No fim tem um &ldquo;wl&rdquo; alguma coisa que é o Wi-Fi que não uso, mas também veio embutido.</p>
<p>Do ponto de vista do sistema operacional, tanto &ldquo;enp4s0&rdquo; quanto esse &ldquo;virbr0&rdquo; são placas de rede. Posso criar quantos dispositivos virtuais quiser. Posso criar um separado pra cada máquina virtual ou conectar várias máquinas na mesma placa. E cada placa pode usar diversas estratégias pra se conectar na rede. Vamos entender essa opção &ldquo;mode&rdquo;. Olha só, isolated é fácil, impede a placa virtual de se conectar na rede e na internet. Só vai conseguir enxergar outras máquinas virtuais, mas como o nome diz, ficam isoladas do resto do mundo.</p>
<p>Todas as outras permitem acessar tanto a rede interna quanto a internet. Se escolher Bridge, vai se comportar como se fosse uma nova máquina de verdade conectada na rede. Vai procurar e encontrar o DHCP do modem da Vivo, igual meu PC de verdade faz no boot, pegar um IP e navegar normalmente. Se escolher NAT, como eu fiz, ele pega um IP privado atrás do meu PC de verdade e usa NAT pra falar na rede e acessar a internet. </p>
<p>Routed nunca precisei usar então não sei os detalhes e Open acho que é se quisesse que a máquina virtual ficasse exposta diretamente na internet pública, que não é nosso caso. Então na prática ou escolho Bridge ou NAT. Tecnicamente, Bridge deveria ser mais performático, já que não precisaria passar por um NAT intermediário. Mas não lembro se tive problemas ou se não senti nenhuma diferença, mas vale testar entre os dois.</p>
<p>Pronto. Rede tá ok. Agora podemos começar o processo de criar a máquina virtual. É com esse ícone de &ldquo;mais&rdquo; no topo. Escolho a primeira opção pra procurar a ISO de instalação do Windows. Ele detecta corretamente. Caso fosse uma distro de Linux que não detectasse,  precisaria escolher manualmente o mais próximo, mas pra agora vamos pro próximo passo. </p>
<p>Agora posso escolher quanto de RAM e quantas threads da minha CPU quero dar pra máquina virtual. Não vou mexer aqui porque isso vou mudar mais pra frente, então vamos pro próximo passo. E agora é a parte pra criarmos um HD virtual ou apontarmos pra um SSD de verdade na máquina. Vou só criar um HD virtual. </p>
<p>Nos meus videos, como de WSL, mostrei como faz pra criar um HD virtual formato de VMWare ou Hyper-V que é o formato VHD ou VHDX, literalmente Virtual HD. Mas o QEMU gosta de usar outro formato, o QCOW2 que significa QEMU Copy-On-Write versão 2.</p>
<p>COW permite que esse HD virtual seja usado simultaneamente por duas ou mais máquinas virtuais diferentes, cada uma vai continuar criando blocos novos tipo num snapshot diferente. Isso também significa que esse HD virtual suporta snapshots, suporta voltar pra versões anterioes, mesmo se dentro não estivermos usando BTRFS. O formato QCOW2 acho que é mais avançado que um VHD. Além de snapshots também suporta compressão e encriptação.</p>
<p>Posso escolher deixar gravar no diretório padrão, mas prefiro criar num diretório que separei só pra isso no meu NAS. Então escolho &ldquo;Manage&rdquo; e nessa janela escolho um pool, que é um diretório e mando criar um novo volume, dou um nome qualquer, formato qcow2 e uns 100 gigabytes. Tanto faz o tamanho porque esse arquivo é dinâmico, na realidade só vai realmente gastar o que precisar, então no começo não ocupa espaço nenhum. Ele não precisa já pré-reservar o espaço todo. E agora no último passo mando deixar customizar mais antes de fazer o primeiro boot.</p>
<p>Esta é a tela mais detalhada que mostra tudo que vai ter nesta nova máquina virtual. Vamos um a um nos itens que importam. Logo no Overview tem uma mudança importante. Precisamos garantir que estamos com o Chipset Q35 em vez do antigo legado i440FX e que o Firmware seja UEFI mas a versão OVMF com secboot, que é secure boot. Fodeu, um monte de letrinhas né? Vou explicar. Chipset, como o nome diz, é o conjunto de chips na placa mãe que controla memória, processador, I/O e tudo mais. Queremos os chips mais novos.</p>
<p>UEFI já expliquei nos videos de armazenamento. Muita gente ainda chama de BIOS, mas UEFI é a evolução da BIOS. Dentre outras coisas,  é quem dá suporte a HDs com partições GPT, que permite partições de tamanho superior a 2 terabytes e um número enorme de partições. Toda UEFI moderna nem precisa de um bootloader como Grub. Se a partição tiver uma pasta EFI, vai conseguir bootar direto. Na minha placa-mãe, por exemplo, é só apertar F12 no boot que me dá um menu pra escolher de qual partição quero bootar, o que facilita fazer dual boot.</p>
<p>OVMF significa Open Virtual Machine Firmware. É um firmware, o UEFI propriamente dito, ou seja o software que boota primeiro logo que liga o computador e é responsável por checar e inicializar todos os dispositivos do sistema, achar o bootloader da kernel do sistema operacional em alguma partição GPT e passar o controle pra continuar o boot. Numa placa mãe de verdade esse firmware costuma ser mais complexo porque tem uma interface gráfica. Meu Aorus por exemplo, se apertar a tecla Delete no boot, abre essa tela gráfica fancy aqui. O OVMF tem telas em modo texto bem mais simples e com bem menos opções, porque é pra máquinas virtuais.</p>
<p>Escolhi a versão com Secure Boot porque Windows 11 passa a exigir a existência de um chip de segurança TMP e o firmware precisa ter suporte pra conseguir falar com esse chip. Pronto, podemos confirmar e agora precisamos adicionar uma versão virtual desse chip. Só adicionar novo hardware, escolher TPM, que vai ser emulado, mas selecionar as opções avançadas pra ser modo TIS e versão 2.0 que é o que o Windows 11 não vai reclamar. </p>
<p>Um secure enclave como esse TPM, no mundo real, é um chip separado da CPU que vai conter coisas como chaves privadas que jamais vai dar pra ler diretamente, incluindo outras credenciais como certificados digitais. Também vai ter gravado a configuração da sua máquina que a firmware vai informar pra ele, assim nos próximos boots dá pra detectar se alguém modificou o hardware, que é o que normalmente se reclama com Secure Boot. </p>
<p>Muitos gamers odeiam Secure Boot e TPM não porque eles são ruins, mas porque software de Anti-cheat, de detecção de trapaças que muitos jogos instalam justamente fazem modificações muito semelhantes ao que um vírus, malware ou rootkit faria na kernel do sistema. O problema é a existência de anti-cheats mau feitos. </p>
<p>Enfim, isso é assunto pra outro dia. O importante é que temos como adicionar um TPM virtual pro Windows achar a aceitar. Mas já aproveito pra avisar que o anti-cheat de alguns jogos, como o Easy usado no Elden Ring, conseguem detectar que está rodando em máquina virtual e não permite entrar online, só dá pra jogar offline. Pra mim isso não é problema, mas pra alguns pode ser.</p>
<p>Agora chegou a hora de configurar a CPU. Problema que essa interface gráfica não permite modificar tudo que preciso. O que vou fazer é abrir a configuração de outra máquina que já tenho, copiar o XML do trecho de CPU e colar nessa nova máquina. Olha só, por padrão tenho uma linha pra quantas vcpus, depois o OS que é o firmware que acabamos de configurar, daí temos features da CPU e modo da CPU. Tudo isso precisamos mexer. Abrindo outra máquina que já tenho, começo copiando essas configurações de vcpu e cputune. </p>
<p>VCPU é simples: quantas threads vou dar pra máquina virtual. Como  tenho uma Ryzen 5950X, posso dar bastante. Decidi dar metade, ou seja, 16 threads pra máquina virtual. O que é essa configuração longa que colei aqui? Eu dei 16 threads, e se não colocar esse bloco de &ldquo;cputune&rdquo;, essas threads vão ser controladas pelo scheduler de threads do Linux Host por baixo. É ineficiente porque no Windows ou outro OS guest do lado de dentro já tem um scheduler e por baixo vai ter outro scheduler e no final as 16 threads vão ficar embaralhadas entre as 32 totais, podendo mudar dependendo se o Host tiver alguma carga pra processar. </p>
<p>Essa configuração depende do uso. No meu caso, se estou com um guest aberto, normalmente não estou fazendo nada pesado no host. 16 threads pra cada dá e sobra na real. Mas essa pinagem de vcpu pra thread real não é aleatório. De onde vem esses números? Pra isso preciso saber a topologia de núcleos e caches deste processador em particular.</p>
<p>Vamos lá, quando falamos CPU, estamos falando deste quadradinho aqui. Seja da Intel ou da AMD, é o processador central. Porém, quando o sistema operacional fala em &ldquo;CPU&rdquo; está se referindo a um único núcleo ou a uma única thread. Vocês já devem ter visto que a CPU do seu PC ou notebook pode ter 4 núcleos e 8 threads. Isso porque a maioria das CPUs modernas suporta um troço chamado HyperThreading, onde cada núcleo é compartilhado por até 2 threads. As duas ao mesmo tempo não conseguem funcionar em capacidade máxima, claro. </p>
<p>Só uma única thread consegue saturar um núcleo. Mas no dia a dia, raramente você está saturando tudo, então 2 threads conseguem compartilhar os recursos de um núcleo e simular 2 processamentos paralelos. Enfim, quando ver em Linux falar em CPU pense que é a thread. Meu Ryzen 9 tem 16 núcleos e 32 threads ou 32 CPUs. Isso porque bem antigamente, de fato uma CPU era só um núcleo. Só dos anos 2000 pra cá que começamos a explorar múltiplos núcleos no mesmo die, ou processador, que é o quadradinho na minha mão.</p>
<p>Aproveitando, num terminal todo mundo sabe que se digitar <code>ls</code> vai listar os arquivos no diretório, certo? Mas sabiam que existem vários programas <code>ls</code> -alguma coisa? Faz o teste, abre um terminal, digita <code>ls</code> e dá &ldquo;tab&rdquo;. Olha no meu, vários comandos diferentes. Nos videos de Linux já mostrei o <code>lsblk</code> lembram? Que lista os dispositivos de bloco do sistema, no caso seus HDs ou SSDs. Olha só os meus. E se você usa Snap pra instalar programas vai ter essa poluição de dispositivos de loop aqui, mas pode ignorar isso.</p>
<p>Pra agora nos interessa <code>lscpu</code> com a opção &ldquo;-e&rdquo;. Ele lista todas as threads disponíveis no meu processador, o clock máximo e mínimo que aguenta e o clock atual no final. Não sei se sabiam disso mas hoje em dia os clocks são controlados dinamicamente. Eles aumentam ou diminuem dependendo da carga que mandarmos. Isso é importante pra economizar energia. Se estamos fazendo quase nada, o chip elege deixar as threads em clock mais baixo.</p>
<p>Alguém poderia pensar: &ldquo;boa, quer dizer que quando eu mandar fazer coisas pesadas como renderizar video, vai subir todo mundo pra esse máximo aqui então?&rdquo; E na realidade não. Depende do governador do sistema operacional, depende dos algoritmos e sensores de Precision Boost da AMD ou Turbo Boost da Intel. Eles tentam ser mais inteligentes do que só subir tudo pro máximo ou desligar tudo pro mínimo. </p>
<p>Se eu forçasse todos pra ficar em 5Ghz, não ia aguentar muito tempo, porque todos os núcleos iam consumir mais energia, e esquentar muito mais. E todos atingindo temperaturas altas demais, tipo mais de 90 graus Celsius, o processador vai entrar em modo de proteção e fazer o que se chama de throttling, que é diminuir a energia e diminuir o clock pra conseguir resfriar.</p>
<p>Por isso que parece contra o bom senso, mas muitos fazem undervolting. Que é limitar a energia e evitar de chegar em clocks tão altos. Mas qual a vantagem disso, não fica mais lento? Pensa assim, é melhor todas as threads funcionarem por mais tempo abaixo de 4Ghz do que ir pra 5Ghz, esquentar demais, e daí a CPU forçar todo mundo pra baixo de 2Ghz até esfriar. Entenderam? Devagar e sempre é melhor que pico rápido e cansa rápido. Em notebook que não tem espaço, undervolting é a única solução. Em PC com espaço a solução é colocar um cooler melhor.</p>
<p>Enfim, clocks não importam pra hoje, mas se não conheciam é legal estudar as configurações de Precision Boost ou Turbo Boost que tem na UEFI da sua placa mãe. Placas baratas não permitem mexer muita coisa, mas as placas mais avançadas e mais caras costumam dar mais opções de tensão, corrente, e tudo mais que afetam os clocks. Mas vamos voltar pro <code>lscpu</code>. Pra fazer a tal pinagem de VCPU pra CPU real precisamos saber o número real das CPUs que é a primeira coluna, de 0 até 31. Mas os núcleos ou cores de verdade é a 4a coluna. Veja como cada núcleo tem 2 threads como expliquei agora pouco.</p>
<p>Mas e aí, é só pegar 16 quaisquer, colocar lá naquele XML e já era? Não, pra saber baixamos o pacote hwloc. Execute <code>lstopo</code> no terminal e vai aparecer uma janela gráfica parecida com essa. No lado direito mostra os dispositivos conectados em pistas ou lanes de PCI. São coisas como SSDs. Isso também não interessa, o importante é bloco maior na esquerda. Entenda, pra uma CPU, uma thread, funcionar de forma eficiente, ela precisa ter acesso aos dados que vai processar no momento em que for processar.</p>
<p>Se toda instrução tiver que pausar e esperar procurar na RAM e trazer pra CPU, tudo seria lento, a CPU ia desperdiçar clocks não fazendo nada além de esperar. Isso porque RAM é super lenta comparado com um registrador, que é quase instantâneo. Mesmo minha CPU rodando na média a 3.4Ghz e estar usando DDR4 de 4 Ghz, não é só o clock. O barramento é lento, tem distância física da CPU até os pentes de memória, tudo isso afeta o desempenho. Inclusive é uma das razões de notebooks modernos soldarem RAM na placa mãe: pra diminuir a distância física.</p>
<p>Pra evitar que uma CPU fique com fome de dados e pause, cada uma delas tem um pequeno cache L1, nesse caso de 32 kilobytes e um cache L2 de meio megabyte. Parece pouco, mas é bastante coisa pra uma única CPU e elas ficam literalmente grudados a milimetro de distância só. Se tudo correr bem, esses caches vão ter os dados que as CPUs precisam, na hora que precisam e nada pausa. L1 é a memória mais rápida e mais cara de todas, por isso tem tão pouco. L2 é um pouco menos rápido, e L3 é mais lento que L2. Preste atenção nesse diagrama.</p>
<p>A parte importante é que diferente de L1 e L2 que são exclusivos por CPU, o cache L3 é compartilhado com um grupo de CPUs, no caso temos dois blocos de L3 de 32 megabytes cada, compartilhado entre 8 núcleos cada. Digamos que tem uma thread de C ou Java rodando no núcleo 0. Por alguma razão o scheduler do Linux gostaria de usar o núcleo 0 pra outra coisa e resolve mover a thread que ainda não acabou do núcleo 0 pro núcleo 3. </p>
<p>Como ambas compartilham o mesmo cache L3, se o estado dessa thread, quer dizer, imagine as variáveis dentro da thread, estiverem no L3, basta parar a thread do núcleo 0 e reiniciar no núcleo 3 que vai continuar de onde parou sem problemas. Mas digamos que o scheduler do Linux fosse burro e mandasse pro núcleo 15, o último.</p>
<p>Antes de conseguir iniciar a thread no núcleo 15, ele precisa primeiro copiar o bloco de memória do 1o cache L3 pro segundo cache L3, senão a nova thread não sabe como continuar o trabalho. Sacaram? Um scheduler de threads tem que ser inteligente pra gerenciar esse tipo de coisa. E essa é a dica pra podermos pinar, ou seja, reservar CPUs pra nossa máquina virtual: queremos pegar todas as CPUs embaixo do mesmo cache L3. No meu caso, os núcleos de 0 a 7 ou de 8 a 15. Vamos voltar pro terminal e ver a saída do comando <code>lscpu</code> de novo.</p>
<p>Eu arbitrariamente escolho usar os núcleos de 8 a 15 e nessa listagem significa pegar as threads de 8 a 15 e de 24 a 31. Agora vamos voltar pra configuração XML. Estão vendo aqui os atributos &ldquo;cpuset&rdquo;? São exatamente as threads de 8 a 15 e de 24 a 31. Eu mapeei pras CPUs virtuais ou VCPUs de 0 a 15. Essa configuração vai variar se eu quiser passar menos CPUs, ou se o processador tiver menos núcleos e com outra topologia de caches L3, por isso precisa usar os comandos <code>lscpu</code> e <code>lstopo</code> pra decidir no seu sistema. Cada um vai ser diferente dependendo do modelo de processador.</p>
<p>Essa opção &ldquo;emulatorpin&rdquo; é pra dizer pro controlador da máquina virtual onde rodar as tarefas que permitem a máquina virtual funcionar. Mas até aqui só dissemos pra máquina virtual, quais CPUs usar. Meu host Manjaro ainda enxerga todas as 32 threads e pode querer usar as mesmas que mandei o guest usar. Como fazer pro host só usar os núcleos do primeiro cache L3? De 0 a 7? Vamos de volta pra Wiki.</p>
<p>Tem várias formas diferentes,  poderia permanentemente limitar meu Linux a só usar os núcleos de 0 a 7, mas isso seria um desperdício quando não estiver usando máquina virtual. Em vez disso quero forçar o Manjaro a se limitar só quando iniciar a máquina virtual e pra isso  posso usar o SystemD. Lembra que SystemD faz bem mais que só gerenciar daemons no boot? Aqui ele gerencia os cgroups do sistema. Chegou a hora de explicar mais alguns termos que vocês não conhecem.</p>
<p>Antes de criarmos a máquina virtual falei que precisava instalar os pacotes do virt-manager, que é a interface gráfica pra criar a máquina virtual, mas também instalei qemu e libvirt sem explicar. Tentando ser curto é assim, o emulador propriamente dito é o QEMU que literalmente significa &ldquo;Quick Emulator&rdquo;. Ele quem vai simular a máquina virtual, rodar os programas virtualizados e passar instrução a instrução pra máquina de verdade por baixo. O equivalente VirtualBox ou VMWare ou Hyper-V seria o QEMU.</p>
<p>Na realidade ele faz bem mais que isso. Uma das principais funções é fazer tradução binária dinâmica. Pra quem usa Mac é o equivalente ao Rosetta. E pra quem não usa Mac, o que é Rosetta? 2 anos atrás a Apple saiu fora de usar processadores da Intel, que é padrão x86-64 e passou a usar chips M1 que eles mesmos desenvolveram, derivado de ARM. É meio como pular de um chip que fala português pra outro que fala chinês.</p>
<p>Funciona assim, digamos que eu tenha um programa compilado pra x86. Um trecho desse programa, em assembly poderia ser como esse aqui na tela. Não sei assembly de x86 então pedi pro ChatGPT gerar esse trecho pra mim, de bater o olho parece fazer sentido. Daí pedi pra escrever como seria a tradução que o QEMU faria pra  ARM64 e ele me deu esse outro trecho.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
; x86-64

mov rax, [rdi] ; Load the value at the address in rdi into rax

mov rbx, [rsi] ; Load the value at the address in rsi into rbx

cmp rax, rbx   ; Compare the values in rax and rbx

jg greater     ; If rax is greater than rbx, jump to the &#34;greater&#34; label

jmp done       ; Jump to the &#34;done&#34; label

  

greater:

  mov [rdi], rbx ; Store the value in rbx at the address in rdi

  jmp done       ; Jump to the &#34;done&#34; label

  

done:

  ret            ; Return from the function</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
; arm64

ldr x0, [x1] ; Load the value at the address in x1 into x0

ldr x2, [x3] ; Load the value at the address in x3 into x2

cmp x0, x2   ; Compare the values in x0 and x2

b.gt greater ; If x0 is greater than x2, jump to the &#34;greater&#34; label

b done      ; Jump to the &#34;done&#34; label

  

greater:

  str x2, [x1] ; Store the value in x2 at the address in x1

  b done       ; Jump to the &#34;done&#34; label

  

done:

  ret          ; Return from the function</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estão vendo como os mnemônicos são diferentes? Em Intel existe a instrução <code>mov</code> pra carregar valores, mas em Arm a instrução se chama <code>ldr</code>. Se assistiram meus videos da playlist de Como Computadores Funcionam eu falo mais sobre como Assembly e instruções de baixo nível como esses funcionam, assistam lá depois.</p>
<p>Naquele pacote <code>qemu-desktop</code> que instalamos vem só dois emuladores: o <code>qemu-system-x86_64</code> e o <code>qemu-system-i386</code> que são os emuladores de plataforma Intel de 64 bits e 32 bits. Mas existem outros emuladores pra diversas plataformas como ARM. Pra isso tem que instalar o pacote <code>qemu-emulators-full</code> ou direto tudo com <code>qemu-full</code> e aí ganhamos emuladores como <code>qemu-system-aarch64</code> pra ARM, mas olha a lista: tem os antigos MIPS, Alpha ou até Motorola 68000 mas tem coisa nova como Risc V. Quem tem interesse em programar baixo nível pra Risc V, pode criar máquina virtual com esse emulador. Leiam também a página de Wiki específica de QEMU depois pra aprender tudo.</p>
<p>Traduzir de uma arquitetura pra outra é bem lento mas no caso, pra rodar Windows de Intel em cima de um Linux compilado pra Intel, não tem tradução nenhuma envolvida. Só precisa pegar a instrução da máquina virtual e passar direto pra máquina real embaixo, é bem menos trabalhoso e por isso a performance é quase inteira mantida. Uma CPU virtual funciona uns 15 a 20% mais devagar por conta dessa intermediação do QEMU no meio. Mas sem configurar nada, mesmo de Intel pra Intel, um Windows rodaria lento emulado, e o culpado não é o QEMU.</p>
<p>A culpa são dos dispositivos da máquina: memória, HD, rede, e tudo mais é virtualizado em software, porque a máquina virtual não pode acessar o hardware diretamente, só a kernel do Linux rodando de verdade no metal é que pode. Por isso a AMD e Intel criaram as instruções VT-d, pra permitir passar acesso aos dispositivos reais diretamente, sem precisar gambiarrar com software no meio. Por isso lá no começo o Wiki manda checar se seu sistema suporta VT-d e VT-x.</p>
<p>Tendo essas instruções agora é tarefa da kernel do sistema operacional usar. No Windows isso é responsabilidade do Hyper-V. No MacOS é o &ldquo;Hypervisor.framework&rdquo;. Num BSD Unix o nome é bizarro, se chama &ldquo;bhyve&rdquo;, que significa &ldquo;BSD Hypervisor&rdquo;. Um hypervisor dá acesso às instruções VT-d e VT-x da Intel ou AMD-V da AMD, permitindo criar e gerenciar múltiplas máquinas virtuais rodando em cima deles. No Linux, o módulo responsável por isso se chama KVM, que significa &ldquo;Kernel Virtual Machine&rdquo;.</p>
<p>Eu detesto esse nome porque KVM também significa Keyboard, Video and Mouse e é uma caixinha onde conectamos, teclado, video e mouse, e podemos ligar em vários PCs diferentes, tipo numa lan house ou data center. Daí é só virar uma chavinha pra escolher qual PC queremos controlar. O nome deve ter sido de propósito porque o Kernel Virtual Machine ou KVM de Linux serve pra justamente gerenciar múltiplas máquinas também, só que virtuais.</p>
<p>Só o QEMU sozinho seria lento, pela falta de acesso ao hardware. Mas ele aceita usar aceleradores. E o KVM pode ser usado como acelerador e por isso chamamos a solução de &ldquo;QEMU/KVM&rdquo;. Entendido isso falta só mais um componente, o tal do &ldquo;libvirt&rdquo; que é o daemon que habilitamos no SystemD. KVM não é o único hypervisor que existe no Linux. Um pouco antes dele, existia outro chamado Xen, e tem alternativas comerciais, como o VMWare. </p>
<p>Seria um saco ter que saber os comandos específicos de cada um, e é pra isso que o libvirt serve: ele é o intermediário que fala com hypervisors de Linux. Eu posso controlar qualquer um com o mesmo comando <code>virsh</code> que mencionei antes. A interface gráfica &ldquo;Virt Manager&rdquo; que usei pra criar a máquina virtual de exemplo, por baixo, provavelmente usa esse comando, e eu poderia ter criado a máquina direto do terminal com esse comando também, só fazer <code>virsh create ...</code></p>
<p>O comando virsh é complexo, posso controlar todos os aspectos de um ambiente virtual come ele. Criar redes virtuais, pools de storage, snapshots e mais. Vale a pena estudar depois, mas pra hoje eu preciso de bem pouca coisa dele. Enfim, agora vocês sabem o que é libvirt, KVM e QEMU.</p>
<p>Antes dessa longa tangente eu tinha parado no problema onde já configuramos a máquina virtual pra só falar com as threads de 16 a 31 e agora precisamos obrigar o scheduler do Linux host a só falar com as threads de 0 a 15. Dessa forma cada sistema tem seu grupo de núcleos isolados. Isso é importante porque senão do nada o guest pode começar a engasgar quando o host ficar roubando as threads do guest.</p>
<p>Como falei antes, o libvirt é responsável por falar com o KVM pra fazer coisas como iniciar ou parar uma máquina virtual. Depois que minha máquina estiver configurada direitinho, é só fazer <code>virsh start</code> e o nome da máquina e pra desligar forçado, equivalente a apertar o botão de força de um PC, é só executar <code>virsh destroy</code>.  Felizmente o libvirt tem eventos e hooks, ou ganchos, onde podemos pendurar scripts. </p>
<p>A Wiki nos manda criar o script <code>/etc/libvirt/hooks/qemu</code>. Toda vez que o QEMU iniciar ou parar, o libvirt vai chamar esse script. Se receber o comando <code>started</code> usamos o systemd pra ajustar a propriedade AllowedCPU de 3 cgroups pra permitir usar só as threads de 0 a 15. Dessa forma finalmente garantimos que de 16 a 31 vai mesmo ser só pra máquina virtual. E quando vier o comando &ldquo;release&rdquo;, ajustamos pra voltar a permitir usar todos os núcleos. Agora é só reiniciar o serviço <code>libvirtd</code> e pronto. Isso termina a longa configuração de pinar VCPUs.</p>
<p>Vamos testar pra ver se funciona. Primeiro, sem nenhuma máquina virtual rodando, vou executar o programa GeekBench, que todo canal no YouTube que faz reviews de processador usa, e pular direto pros testes de multi-core. Olha o htop rodando em outro terminal e sim, todas as minhas 31 threads estão sendo trabalhadas. Podemos cancelar e agora abrir uma máquina virtual qualquer que já tenho configurado. Fazendo isso o libvirt vai atirar o evento de started pro hook e executar aqueles comandos de systemd.</p>
<p>Pronto, o guest tá carregado, voltamos pro terminal e executamos o GeekBench de novo. Vou pular pros testes de multi-core como antes. Voltamos pro htop: só as threads de 0 a 15 estão saturados. As threads de 16 a 31 estão sem fazer nada, reservados pro guest poder usar sem ninguém atrapalhando. Sacaram? É assim que garantimos que se eu estiver jogando na máquina guest e o Host decidir processar alguma coisa, não vai sequestrar uma thread minha e causar engasgos no meu jogo. Conseguimos controlar o processador com sucesso!</p>
<p>Mas o que são aqueles system.slice, user.slice e init.scope? São control groups, ou cgroups. Cgroups é um recurso de Linux pra organizar processos em agrupamentos, daí podemos limitar acesso a recursos pro grupo todo. Nesse caso estamos limitando acesso à CPU pra esses 3 grupos e a kernel vai enforçar isso. Aliás, pra quem não sabia, é combinando cgroups com outro recurso da kernel, namespaces, que começa a formar as bases pra containers como Docker. Mas isso fica pra outro episódio.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#!/bin/sh</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">command</span><span class="o">=</span><span class="nv">$2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$command</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;started&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- system.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- user.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- init.scope <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$command</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;release&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- system.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- user.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- init.scope <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mais um detalhe, esse hook vai rodar toda vez que qualquer máquina for iniciada ou parada pelo QEMU. No meu caso sempre pretendo criar máquinas virtuais com exatamente os mesmos núcleos pinados, então pode rodar sempre. Mas se você quiser máquinas com mais ou menos núcleos, precisa de um hook diferente pra cada máquina. E isso é simples. Em vez do script anterior de systemd em <code>/etc/libvirt/hooks/qemu</code> eu colocaria este outro script mais complicadinho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
GUEST_NAME=&#34;$1&#34;

HOOK_NAME=&#34;$2&#34;

STATE_NAME=&#34;$3&#34;

MISC=&#34;${@:4}&#34;

  

BASEDIR=&#34;$(dirname $0)&#34;

  

HOOKPATH=&#34;$BASEDIR/qemu.d/$GUEST_NAME/$HOOK_NAME/$STATE_NAME&#34;

  

set -e # If a script exits with an error, we should as well.

  

# check if it&#39;s a non-empty executable file

if [ -f &#34;$HOOKPATH&#34; ] &amp;&amp; [ -s &#34;$HOOKPATH&#34; ] &amp;&amp; [ -x &#34;$HOOKPATH&#34; ]; then

    eval \&#34;$HOOKPATH\&#34; &#34;$@&#34;

elif [ -d &#34;$HOOKPATH&#34; ]; then

    while read file; do

        # check for null string

        if [ ! -z &#34;$file&#34; ]; then

          eval \&#34;$file\&#34; &#34;$@&#34;

        fi

    done &lt;&lt;&lt; &#34;$(find -L &#34;$HOOKPATH&#34; -maxdepth 1 -type f -executable -print;)&#34;

fi</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não vou explicar linha a linha, é um script de shell. Se você entende de Linux é fácil. Toda vez que o QEMU iniciar uma máquina virtual, esse script vai rodar. Vai pegar o nome da máquina, digamos que seja  <code>win11-video</code> e vai procurar o esse script <code>/etc/libvirt/hooks/qemu.d/win11-video/qemu</code> e lá podemos colocar aquele script anterior de systemd mas agora ajustando exatamente quais CPUs vou permitir pro host pra essa máquina virtual em particular, sacaram?</p>
<p>O perfil de boost da CPU é controlado por uma coisa chamada Governador da Kernel. Sabe nas configurações de energia de qualquer sistema operacional, especialmente em notebooks, que você escolhe Balanceado, Modo de Economia ou Alta Performance? É isso. Normalmente o Linux se configura em perfil Balanceado, mas eu gostaria de forçar pra perfil de performance antes de iniciar meu Windows de games. Pra isso existe o comando <code>cpupower</code>. Só rodar <code>cpupower frequency-set -g performance</code> e pronto. Estude sobre <code>cpupower</code> depois pra descobrir se sua CPU tá em modo balanceado ou economia e você nem sabia. </p>
<p>Voltando pra configuração XML, vou copiar e colar vários blocos. Isso tudo tá explicado na Wiki mas não compensa ir um a um. Em resumo, o bloco de features declara quais funcionalidades essa máquina vai ter, como ACPI que é um padrão pra controlar energia da máquina. Relevante aqui é esse trecho de KVM com estado escondido. Isso é uma tentativa do KVM de tentar esconder do sistema virtualizado que ele está numa máquina virtual. Antigamente GPUs da NVIDIA davam erro se descobriam que estavam sendo virtualizados, mas nos modelos mais novos isso não acontece. Talvez se tentar usar algo mais velho que uma GTX talvez precise, e também desse outro atributo de vendor_id em cima.</p>
<p>Nos blocos de baixo, importante é deixar CPU em modo &ldquo;host-passthrough&rdquo; e checar que a topologia está de acordo com todo o trampo que fizemos no bloco de pinagem antes. Aqui tá certinho, 1 soquete, com 1 processador, 8 núcleos e 2 threads cada, totalizando as 16 threads que pinamos. No meu caso que estou usando AMD Ryzen, precisa dessa feature &ldquo;topoext&rdquo;, senão não dá pra usar hyperthreading. Em Intel não precisa. E pronto, acho que isso resolve a configuração da CPU. O Virt-manager podia já fazer tudo isso automaticamente pra gente, mas até lá precisa saber esses detalhes todos pra tirar o máximo do processador. Por isso precisa ler a Wiki em detalhes.</p>
<p>Continuando, o Virt-manager já adicionou alguns dispositivos que podem ser estranhos como esse Display Spice e Video QXL. QXL significa &ldquo;Qemu eXtended Logical&rdquo; e é uma placa de video virtual. Podemos escolher outros modelos e eu preferi deixar em Virtio que tem essa opção de aceleração 3D. Não espere nenhuma mágica disso ser capaz de rodar games, mas já é alguma coisa. Esse dispositivo é necessário em máquinas onde não temos ou não queremos dedicar placa de video de verdade só pra ela como estou fazendo.</p>
<p>Na real, fiz um teste bem besta. Bootei o Ubuntu virtualizado com Video QXL e testei abrir o firefox e carregar um demo de WebGL. Daí eu desligo, mudo de QXL pra Virtio com aceleração 3D e não faz nenhuma diferença, tenho os mesmos quadros por segundo. Das duas uma, ou o QXL também faz aceleração 3D ou o Virtio não tá acelerando nada, mas seja lá qual for o caso, o resultado parece o mesmo então meio que tanto faz. Até porque o objetivo de usar esses dispositivos virtuais não é fazer nada pesado graficamente como animação 3D ou jogos. Pra renderizar a interface gráfica de janelas, a velocidade é mais que suficiente.</p>
<p>SPICE é um protocolo pra acessar a interface gráfica remotamente, mais ou menos como um VNC ou Remote Desktop de Windows ou mesmo passar X via SSH pra quem é de Linux, mas é mais performático. Nunca vai ser rápido o suficiente pra games, mas pro dia a dia é suficiente. Então, Display SPICE é o monitor virtual que vai mostrar o que sai da placa virtual com Virtio. E vai aparecer aqui nessa aba com ícone de monitor. Deixa eu mostrar como é com outra máquina virtual que já tinha configurado que é um Ubuntu. Nesse Ubuntu não passei minha NVIDIA, então só tem a placa virtual mesmo, já que uso pra testes e não pra jogar nem nada mais pesado. (liga máquina)</p>
<p>No meu caso, salvo pra casos de sistemas de testes como esse Ubuntu, pretendo passar a placa de video real da NVIDIA pra dentro do guest. Então depois que instalar o sistema operacional e os drivers, vou tirar o Video Virtio e o Display SPICE. Mas um terceiro dispositivo que ainda tenho um pouco de dúvidas é o Console PTY. Isso é um terminal virtual de texto que conecta num pseudo-terminal PTY do host. É uma das formas do host se comunicar com o guest e vice-versa. Antes podia arrancar isso fora, mas não sei se teve alguma atualização que sem o console eu não tava conseguindo bootar a máquina virtual direito, então só deixo lá agora.</p>
<p>Agora vem o dispositivo de som. Pra maioria dos casos pode deixar o que vem por padrão já. Ele emula um dispositivo ICH9 que permite entrada e saída em estéreo. Se precisar usar microfone, acho que essa é a melhor opção. Mas se tiver um home theater, no caso um receiver ligado no PC com configuração de surround ou 5.1, que significa duas caixas laterais frontais, duas caixas traseiras surround, um central pra voz e um sub-woofer que é esse .1, daí precisa mudar no XML pro modelo ser &ldquo;usb&rdquo;. Áudio via USB desse dispositivo suporta saída 5.1 mas não fornece entrada.</p>
<p>Áudio em Linux é outro ninho de ratos chato mas no geral, na maioria das distribuições Linux funciona assim: primeiro tem os drivers pro hardware de áudio, seja o que vem embutido na sua placa mãe ou dispositivos externos. Esses drivers são chamados de ALSA, que significa Advanced Linux Sound Architecture. Mas os aplicativos, como um tocador de música ou video, não fala diretamente com esses drivers.</p>
<p>Entre os aplicativos e os drivers existe um intermediário que roda em userland que normalmente é o Pulseaudio. Tem também o Jack mas nunca vi o Jack em distros populares. O Pulseaudio é quem permite múltiplos aplicativos acessarem saída de áudio, organiza e centraliza o controle de múltiplos streams. Sabe quando abre a configuração de som num GNOME  e ele deixa controlar o volume independente pra cada aplicativo? É o Pulseaudio. </p>
<p>Eu não acompanhei a evolução, mas parece que o Pulseaudio, sendo um sistema antigo e legado, envelheceu mal pra lidar com o aumento no uso de multimídia de aplicativos modernos, especialmente necessidades profissionais, como editores de música que precisam trabalhar com dezenas ou centenas de streams de áudio de uma só vez e exigem baixíssima latência. Latência em áudio é crucial. Imagina um notebook de DJ no meio de um show enorme, e do nada latência dá um pico, trava e quebra o som no meio do show por 1 segundo. É o tipo de coisa que jamais pode acontecer. É um dos pontos fortes do MacOS, por isso todo mundo usa Macbooks em shows.</p>
<p>Pra combater isso nasceu um novo projeto, o Pipewire. Algumas distros modernas, como meu Manjaro, já começaram a substituir o Pulseaudio pelo Pipewire. Parece que Pipewire também tem suporte melhor a áudio via Bluetooth, então quem tinha problemas com fones sem fio, deve ter uma experiência melhor agora. Outra vantagem do Pipewire é ter uma camada de compatibilidade de API, daí aplicativos que esperam trabalhar com Pulseaudio conseguem se comunicar com o Pipewire. De qualquer forma, na configuração do QEMU não precisamos mexer nada. No XML que ele nos deu já vem esse canal de áudio apontando pra pulseaudio. Eu estou usando Pipewire no Manjaro e tá tudo funcionando então acho que funciona plug and play.</p>
<p>Áudio é um troço que dependendo do seu caso de uso, pode dar dor de cabeça. No Wiki que estamos seguindo, ele detalha direitinho como configurar pra pulseaudio, pipewire, jack, scream e mais. Se precisar muito tem como configurar na mão coisas como latência, bitrate. Por exemplo, pode dar problemas se no guest o som estiver configurado pra sair em 48 mhz e no host estiver pra 44 mhz. Se tiver sorte, como eu, não vai precisar mexer em nada. Mas só lembrando que se tiver problemas, tem que fuçar essa parte da documentação.</p>
<p>Chegamos no evento principal: passar a placa de video real NVIDIA pra dentro da máquina virtual. E se fez tudo direitinho lá no começo com IOMMU e VFIO, agora é só clicar em &ldquo;adicionar hardware&rdquo; aqui embaixo, escolher categoria PCI, e selecionar a RTX 3090 que tá separadinha já pra gente. Lembram como no mesmo grupo de IOMMU tinha esse dispositivo de áudio da NVIDIA? Precisa adicionar também, senão a máquina não vai bootar. Mas é só isso. </p>
<p>Comparado com os outros dispositivos que tivemos que configurar, a GPU é a mais fácil. Mais fácil ainda que teclado e mouse, que é nosso próximo passo. O ideal seria ter dois teclados e dois mouses, um conjunto ligado no host, e o outro pra passarmos pra máquina virtual, mas seria bem pouco prático fazer isso. Lembra quando falei sobre o KVM que não é o módulo de virtualização do Linux? O KVM que permite usar um conjunto de teclado e mouse e monitor e ficar chaveando pra mudar de um PC físico pra outro PC físico? É o que vamos fazer.</p>
<p>O chaveador virtual de teclado e mouse se chama EVDEV que é acrônimo pra &ldquo;event device&rdquo;. Olha no Wiki o que ele manda fazer. Temos que listar o que tem no diretório <code>/dev/input/by-id</code>. No meu caso tem todos esses aqui, o mais importante são os dois primeiros. Basta no XML, dentro do bloco de &ldquo;devices&rdquo; adicionar o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;evdev&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;source</span> <span class="na">dev=</span><span class="s">&#34;/dev/input/by-id/uinput-persist-mouse0&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/input&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;evdev&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;source</span> <span class="na">dev=</span><span class="s">&#34;/dev/input/by-id/uinput-persist-keyboard0&#34;</span> <span class="na">grab=</span><span class="s">&#34;all&#34;</span> <span class="na">repeat=</span><span class="s">&#34;on&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/input&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;mouse&#34;</span> <span class="na">bus=</span><span class="s">&#34;virtio&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;keyboard&#34;</span> <span class="na">bus=</span><span class="s">&#34;virtio&#34;</span> <span class="nt">/&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso, posso mover meu teclado e mouse pra dentro do guest ou pra fora no host apertando as teclas control da esquerda e da direita do teclado de uma só vez. No Wiki explica também se quiser mudar essa combinação de teclas. Só que na minha configuração de hardware em particular, o mouse não passa como deveria. Vamos entender porque. No terminal, podemos dar <code>cat</code> nesses dispositivos. Olha o que acontece se der <code>cat</code> no teclado.</p>
<p>Viram? Toda tecla que digito aparece o código aqui. É isso que o sistema operacional fica recebendo antes de traduzir pra letras. Vamos fazer a mesma coisa no primeiro dispositivo de mouse. Dou <code>cat</code>  e mexo o mouse e nada acontece. Isso porque estou usando um receptor sem fio USB que é esse outro dispositivo Logitech USB Receiver aqui em baixo. Tem 4 deles, mas depois de tentativa e erro descobri que é esse que termina com &ldquo;event-mouse&rdquo;. Só dar <code>cat</code> nele e olha só recebendo eventos. Portanto devo mudar o XML pra ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;evdev&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;source</span> <span class="na">dev=</span><span class="s">&#34;/dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/input&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;evdev&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;source</span> <span class="na">dev=</span><span class="s">&#34;/dev/input/by-id/uinput-persist-keyboard0&#34;</span> <span class="na">grab=</span><span class="s">&#34;all&#34;</span> <span class="na">repeat=</span><span class="s">&#34;on&#34;</span><span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/input&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;mouse&#34;</span> <span class="na">bus=</span><span class="s">&#34;virtio&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">&#34;keyboard&#34;</span> <span class="na">bus=</span><span class="s">&#34;virtio&#34;</span> <span class="nt">/&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, agora sim o meu mouse vai funcionar. Por isso precisa entender o que realmente está acontecendo e o que cada configuração significa. Falta só mais uma coisinha pra continuar que é baixar a imagem ISO de CD com os drivers pra todos os dispositivos virtuais que configuramos. Sabe, todo lugar que selecionamos a opção de &ldquo;virtio&rdquo;? O que é VirtIO? Como o nome diz é &ldquo;Virtual I/O&rdquo;, é um nome genérico pra um conjunto de drivers pra esses dispositivos virtuais. O instalador do Windows não tem idéia de que diabos são dispositivos virtio, então precisamos dar os drivers pra ele.</p>
<p>O link vai estar nas descrições abaixo, mas quem mantém esses drivers é a RedHat e tem todas as versões nesta página da comunidade do Fedora. Só escolher a versão mais recente aqui embaixo e agora baixar este arquivo de ISO de 500 mega. Todos os drivers que vamos precisar estão aqui.</p>
<p>Baixado, vamos voltar pra tela de configuração do Virt Manager. Adicionamos um novo hardware de storage, selecionamos o ISO que acabamos de baixar e configuramos pra ser um CD-ROM SATA. Olha aqui que tem outras opções, tem SCSI, tem USB e tem Virtio. Virtio não pode ser, porque Windows não sabe ainda o que é Virtio sem os drivers que estão justamente nesse ISO. USB não sei pra que serve. SCSI também não rola, porque o instalador do Windows também não detecta SCSI. Por eliminação vai ser SATA.</p>
<p>Padrão SATA é o mais comum e genérico e até o Windows consegue entender na instalação. Por isso tanto o ISO do instalador de Windows quanto o ISO dos drivers, vamos configurar como dispositivos de CD-ROM SATA. Mas o HD virtual, formato qcow2 que criamos, está como SATA, mas Virtio tem performance melhor, então mudamos. Só que assim o instalador não vai detectar o HD. Não tem problema, pra isso baixamos os drivers. Finalmente, chegou a  hora de bootar essa máquina.</p>
<p>Olha só como o video sai pelo Display SPICE que mostra aqui na própria janela. É basicamente um streaming do video gerado pela placa de video virtual Virtio. Ele vai detectar o CD-ROM e podemos fazer o boot, exatamente como faria se fosse instalar via pendrive no seu PC de verdade. Vou passar rapidinho essas primeiras telas porque todo mundo já viu isso antes.</p>
<p>Pronto, chegamos na parte que o instalador tenta achar o HD pra particionar, formatar e copiar os arquivos. Mas não acha nada, porque como expliquei antes, não tem nenhum HD SATA. Mas tem essa opção de &ldquo;Load Driver&rdquo;. Podemos navegar pro CD do Virtio e ir pra pasta &ldquo;amd64&rdquo; logo no começo. Meio burro que o Windows não vasculhe o CD inteiro pra achar os drivers, mas beleza. E olha só, achou o driver da RedHat de controlador SCSI Virtio. E uma vez carregado, sucesso, agora sim ele acha o HD virtual e vai conseguir formatar e instalar. A partir daqui vou acelerar bastante a instalação pra terminar rápido. Terminando em, 3 &hellip; 2 &hellip; 1 &hellip;.</p>
<p>Terminou! Eis que estou no novo Windows virtualizado. Mas não acaba aqui. Primeira coisa a fazer é abrir o Gerenciador de Dispositivos. Olha só que coisa feia, um monte de dispositivos que o Windows não tem idéia do que fazer. Pra resolver isso temos que manualmente clicar com botão direito e mandar instalar o driver a partir do CD do Virtio. E pronto, um dispositivo configurado, vamos pros próximos.</p>
<p>Agora sim, o Windows detectou corretamente tudo que precisava. Esses drivers Virtio são o que chamamos de drivers paravirtualizados. Em uma virtualização completa, o Windows não saberia que tá sendo virtualizado, ele ia ser iludido a achar que tá numa máquina de verdade. E todo driver responderia como se fosse um dispositivo de verdade por baixo e não um arquivo formato qcow2 fingindo ser um HD de verdade. Ou uma ponte com NAT fingindo ser uma placa de rede.</p>
<p>Já drivers paravirtualizados falam pro Windows: &ldquo;ow, tu sabe que isso não é de verdade né?&rdquo; e o Windows &ldquo;porr, sérião? beleza, valeu por avisar, vamos trabalhar juntos nisso então&rdquo;. E sabendo que é virtualizado ele pode se comportar diferente de um driver normal pra otimizar performance. É uma explicação bem grosseira, mas o importante é que o Windows guest sabe que tá numa máquina virtual e vai agir de acordo e cooperar com o host por baixo pra ter mais performance.</p>
<p>A partir daqui é vida normal que segue. Vamos deixar o Windows Update atualizar o que precisa e baixar o resto dos drivers. Enquanto isso podemos ir no site da NVIDIA e baixar o GeForce Experience, que serve pra otimizar as configurações da GPU pra cada jogo. Ele também serve pra monitorar atualizações dos drivers e vamos instalar por ele. De novo, baixar e instalar é demorado então vou acelerar e terminar em 3 &hellip; 2&hellip; 1&hellip;</p>
<p>Com todos os drivers instalados e tudo atualizado, rebootamos a máquina e tá tudo funcionando. O que podemos fazer agora é desligar a máquina e remover a placa de video virtual Virtio e o monitor virtual SPICE. A partir de agora o Windows vai usar direto só a RTX 3090 de verdade. Então vamos fazer isso. Sei lá porque sempre dá erro pra remover esses dispositivos e o mais fácil é entrar na configuração XML e apagar manualmente. Olha só que chato.</p>
<p>Feito isso, vamos bootar de novo. Mas dessa vez vou bootar outro Windows que já tá configurado, com Steam e tudo só pra demonstrar como fica. Nesse caso vou deixar o OBS também rodando por trás pra gravar o jogo.</p>
<p>(jogo)</p>
<p>O que acham? Isso é um game pesado rodando com velocidade normal como se estivesse num PC de verdade. Mas isso só é possível porque estou usando uma CPU parruda, a Ryzen 9 5950. Estou usando só metade dos núcleos, que são 16 threads, com uns 20% menos performance por thread. Mas isso é quase o equivalente a   rodar um processador Ryzen 5 por exemplo, que já é mais do que suficiente pra maioria dos jogos. </p>
<p>Agora deixa mostrar um jogo incompatível com máquina virtual por causa do anti-cheat. Vamos carregar Elden Ring, esperar um pouco  e &hellip; olha só, dá pau e fecha. Ainda bem que no caso específico do Elden Ring a gambiarra pra carregar offline é simples. Basta ir com o explorer no diretório do jogo, apagar esse executável que carrega o anti-cheat e renomear o executável do jogo propriamente dito com o mesmo nome. Pronto. Vamos carregar de novo e olha só, não deixa entrar online mas pelo menos posso jogar sozinho, que é o que eu queria.</p>
<p>Eu não jogo online, mas quem joga uns Valorant da vida, não vai funcionar em máquina virtual pelo mesmo motivo: o anti-cheat vai detectar que tá em máquina virtual e se não tomar cuidado você pode ser banido. Assim como Elden Ring, alguns jogos tem jeitos de burlar isso mas de novo, tome cuidado pra não ser banido. Mas existe uma última saída caso eu quisesse muito jogar isso. Vamos abrir a configuração desta máquina virtual no Virt Manager.</p>
<p>Diferente da máquina virtual de testes que tava montando pra esse video, essa outra máquina que mais uso não foi instalado num HD virtual qcow2. Eu já tinha esse Windows em dual boot instalado direto no SSD NVME. Era meu sistema principal antes de instalar o Manjaro. Olha aqui a configuração de armazenamento no Virt-Manager. Em vez de apontar pra um arquivo .qcow2 está apontando direto pro dispositivo <code>/dev/nvme0n1</code>. </p>
<p>Digamos que eu queria muito jogar Elden Ring ou qualquer outra coisa que tenha anti-cheat online. Não tem jeito, a única forma é rodar num Windows nativo sem virtualizar. Nesse caso tenho que desligar meu Manjaro, rebootar e no Grub escolho Windows. Vamos acelerar aqui o boot e olha só, notaram? É exatamente o mesmo Windows que tava funcionando virtualizado. Felizmente, até o Windows é inteligente o suficiente pra detectar os dispositivos de verdade e carregar os drivers nativos certos em vez dos VFIO da máquina virtual. Eu posso usar o mesmo Windows tanto em dual boot nativo quanto virtualizado pelo QEMU/KVM.</p>
<p>E a partir daqui é um dual boot normal, sem nada de virtualização, com o Windows tendo acesso a 100% da CPU e às minhas duas placas de video. No caso de games, é onde vou ter o máximo possível de performance, caso precise. Mas se precisar de alguma coisa do Linux, preciso fechar tudo e rebootar de novo, um puta saco. Mas vamos voltar pro Manjaro de novo e abrir o Virt Manager, quero mostrar uma última coisa.</p>
<p>Se eu iniciar o Windows virtualizado por aqui, veja no gráfico que tá carregando, mas meu teclado e mouse páram de funcionar e não vejo nada no monitor. Isso porque o EVDEV que configurei sequestra meu teclado e mouse, mas isso é simples, basta apertar as duas teclas de control ao mesmo tempo, e o controle volta pro host Linux, olha só. E o Windows tá mandando sinal de video pela RTX 3090 que liga na segunda entrada de video do monitor. Eu preciso apertar o botão de menu aqui atrás e navegar até achar o lugar que muda qual sinal de HDMI o monitor vai mostrar. Olha só, tá aqui o Windows.</p>
<p>Vamos voltar pro cabo que sai da placa AMD e ficar no Linux um pouco. Notaram uma coisa irritante? Toda vez que eu abrir uma máquina virtual que usa a placa da NVIDIA precisa botar a mão atrás do monitor, caçar o botãozinho às cegas e mudar o input. Não, tem que ter jeito melhor, e de fato tem. Pesquisando, esbarrei nesta ferramenta chamada &ldquo;ddcutil&rdquo;. Vale ler toda a documentação, mas deixa demonstrar o que ela faz.</p>
<p>Primeiro, vamos abrir o terminal e rodar <code>ddcutil detect</code> com sudo. E  detectou meus dois monitores. O primeiro é o Samsung vertical e o segundo é o Asus principal. Agora posso fazer <code>ddcutil capabilities --display 2 --verbose</code> e  lista todos os comandos que meu monitor Asus suporta. Olha só, posso mexer no brilho, contraste, cores, mas o que me interessa é esse comando hexadecimal <code>60</code> que é input source.</p>
<p>Teoricamente, se rodar <code>ddcutil setvcp 60 0x01</code> deveria mudar pra entrada de DisplayPort e com <code>0x03</code> deveria ir pra entrada DVI. Mas por alguma razão essa lista tá errada ou incompleta, porque não listou os HDMIs. Eu não lembro mais onde foi que achei mas pro meu monitor Asus ROG TUF encontrei em algum lugar que os valores corretos são <code>0x0f</code> e <code>0x12</code>. Olha só, <code>ddcutil setvcp 60 0x0f</code>. Viram só? Mudou pra saída da placa NVIDIA. Podemos repetir o comando só que com <code>0x12</code>  e olha só, voltou pra saída da placa AMD. </p>
<p>Antes que alguém comente, sim, o que fiz com <code>ddcutil</code> teoricamente era pra funcionar com o comando <code>xrandr</code> mas por alguma razão não  funciona. Por isso procurei alternativas. Essas porcarias de servidor X.org e Wayland e essa transição até hoje ainda não tá 100%. Aliás, uma das razões de estar usando essa placa pequena AMD RX 6400 é justamente pra conseguir usar Wayland decentemente em vez de X.org. Dizem que NVIDIA já suporta ou vai suportar XWayland, mas ainda não parei pra ver isso. NVIDIA é um dos responsáveis pelo atraso na adoção de Wayland nas distros. Se for usar primariamente Linux, o recomendado é escolher placas de video da AMD.</p>
<p>Por último mesmo, tem um problema. Meu Manjaro eu deixo configurado pra depois de algum tempo, 1 ou 2 horas sem usar, fazer o sistema dormir pra economizar energia. Não gosto de desligar a noite antes de dormir e ter que esperar bootar de manhã. O problema é que enquanto estou usando a máquina virtual, o host tá como se ninguém estivesse usando. Então depois de 1 ou 2 horas, o host resolve que quer dormir. Só que quando isso acontece, congela a máquina virtual, porque o hypervisor vai dormir também, e aí trava tudo e eu preciso forçar um reboot no botão do PC.</p>
<p>Então criei um script chamado <code>bin/suspend</code> que aceita opção &ldquo;on&rdquo; ou &ldquo;off&rdquo; pra ligar ou desligar a função de dormir do GNOME usando o comando <code>gsettings</code>. Tem outras formas de fazer, se estiver em KDE ou outro gerenciador vai ser diferente, mas isso deve dar uma dica pra quem estiver interessado. Com esse comando, antes de ligar a máquina virtual eu rodo <code>bin/suspend off</code> e isso vai desligar a suspensão. E quando desligar a máquina virtual rodo <code>bin/suspend on</code> pra religar a função de suspensão da máquina e pronto.</p>
<p>Aproveitando, resolvi criar um script chamado <code>bin/vmstart</code> onde passo o nome da máquina. Olha o que ele faz: primeiro muda o governador das CPUs pro modo de performance. Em seguida desliga a configuração de suspensão do host. Finalmente, usa o comando <code>virsh start</code> pra iniciar a máquina virtual. Nesse momento a máquina vai bootar mas não adianta mudar a entrada do monitor já pra placa da NVIDIA porque até o Windows carregar não vai ter nenhum sinal de video. Então forço um sleep de 30 segundos e depois rodo o comando <code>ddcutil</code> pra mudar a entrada automaticamente. Vamos ver funcionando?</p>
<p>Rodo <code>bin/vmstart win11-real</code> que é minha máquina Windows principal onde tem meu DaVinci Resolve, Photoshop e onde edito os videos aqui do canal. Mais alguns segundos &hellip; e pronto, olha só, Windows carregado. Agora posso iniciar qualquer das minhas máquinas virtuais via linha de comando, sem precisar abrir o Virt-Manager. </p>
<p>Esse video já tá longo pra caramba, e isso porque pulei o que são Huge Pages na configuração de memória RAM, pulei falar de I/O threads e como reservar threads só pra operações de I/O, pulei falar de como dinamicamente mover coisas como meus gamepads USB pra máquina virtual automaticamente via &ldquo;udev&rdquo;, pulei falar sobre como é mais fácil plugar um hub USB. Enfim, o Wiki tem um montão de detalhes a mais. É uma aula de como configurar seu hardware e mesmo que você não vá montar uma configuração complicada como a minha, recomendo dar uma boa lida com muita atenção.</p>
<p>Além da página de Wiki do Arch também recomendo ler a página de Wiki do Gentoo. Ele tem informações extras que complementam a do Arch e uns truques e correções importantes. Como eu disse, esses dois Wikis são excepcionais e pra montar configurações complicadas assim, toda informação extra é bem vinda. </p>
<p>Com isso, finalmente consegui fazer uma coisa que vinha querendo faz mais de uma década: ter uma máquina com duas placas de video e conseguir passar uma delas pra máquinas virtuais funcionarem com performance máxima, especialmente pra games. Essa solução é melhor que dual boot porque posso subir quantas instalações de Windows ou Linux quiser, destruir quando não quiser mais, sem me preocupar em estragar o boot ou perder alguma coisa. Meus arquivos de HD Virtual qcow2 ficam no meu NAS que tá conectado com rede de 10 Gigabits, então mesmo bootando remotamente, a performance é muito boa. </p>
<p>Como falei no começo, o objetivo era documentar tudo que tive que pesquisar pra montar esse setup antes que começasse a esquecer os detalhes. A partir daqui vou sempre ficar mexendo scripts, coisinhas aqui e ali pra ajustar performance ou conveniência.</p>
<p>O mais bizarro que não vou mostrar no video é que hypervisors suportam nesting, ou seja, máquina virtual rodar outra máquina virtual. Eu consigo virtualizar o Windows e, de dentro dele, instalar WSL e rodar Linux via Hyper-V em cima do Windows que tá em cima do QEMU/KVM. É Inception na veia. Além disso posso rodar o Garuda Linux, abrir o Steam que roda via Proton, que é uma camada que usa Wine e várias outras bibliotecas pra emular Windows. </p>
<p>E falando em emulador, no outro Windows só pra jogos também instalei vários emuladores como o PCSX2 de PS2 ou RPCS3 de PS3 ou Yuzu de Nintendo Switch e mais. Emulador rodando dentro de emulador. Esse é o nível que eu esperava atingir 21 anos atrás quando comecei a mexer com máquinas virtuais. E por hoje é isso, se ficaram com dúvidas ou tiverem sugestões em cima desse setup, mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>games</category><category>hardware</category><category>performance</category><category>akitando</category></item><item><title>[Akitando] #133 - Tornando sua App Web Mais Rápida! | 4 Técnicas de Otimização</title><link>https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/12/12/akitando-133-tornando-sua-app-web-mais-rapida-4-tecnicas-de-otimizacao/</guid><pubDate>Mon, 12 Dec 2022 09:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/KyqFXVVgvIs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Por que a aplicação que você tem feito vendo tutoriais e cursos online é BEM diferente do que equipes profissionais realmente fazem de verdade? Por que só &amp;ldquo;estar funcionando&amp;rdquo; não é o suficiente?&lt;/p&gt;
&lt;p&gt;Se você já é um profissional da área, tudo que vou dizer é o arroz com feijão, mas estou facilitando seu trabalho: compartilhe com os júniors ao seu redor. E se você é iniciante, é hora de começar a ver o que diferencia sua pequena aplicação das de verdade.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/KyqFXVVgvIs"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Por que a aplicação que você tem feito vendo tutoriais e cursos online é BEM diferente do que equipes profissionais realmente fazem de verdade? Por que só &ldquo;estar funcionando&rdquo; não é o suficiente?</p>
<p>Se você já é um profissional da área, tudo que vou dizer é o arroz com feijão,  mas estou facilitando seu trabalho: compartilhe com os júniors ao seu redor. E se você é iniciante, é hora de começar a ver o que diferencia sua pequena aplicação das de verdade.</p>
<p>O episódio de hoje não vai focar em nenhuma linguagem nem framework específicos. Os conceitos de otimização de aplicações web servem pra qualquer um. Cada uma das técnicas é assunto pra um curso inteiro. Tem muitos detalhes, muito caso-a-caso, mas a idéia hoje é dar uma visão geral da importância de não só &ldquo;sair codando&rdquo; e prestar atenção no que está se codando.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>01:19 - CAP 1 - Recapitulando como Requisição Web Funciona</li>
<li>04:55 - CAP 2 - Multi-processos e Proxy Reverso - Balanceando Carga</li>
<li>11:35 - CAP 3 - Calculando Uso de Recursos - Pool de Conexões</li>
<li>18:00 - CAP 4 - Estratégia de Caching - Economizando Recursos</li>
<li>21:46 - CAP 5 - Bancos de Dados Relacionais, NoSQL, difenças e quando usar - Replicas</li>
<li>28:23 - CAP 6 - Jobs Assíncronos - Devolvendo Rápido</li>
<li>37:05 - CAP 7 - Resumo Até Agora - Diagramas</li>
<li>41:32 - CAP 8 - Calculadoras e Monitores - Dados Analíticos</li>
<li>43:58 - CAP 9 - CDN - Assets mais Rápidos</li>
<li>48:31 - Bloopers</li>
</ul>
<h2>Links:<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Heroku DB Connection Calculator (<a href="https://judoscale.com/heroku-postgresql-connection-calculator/"target="_blank" rel="noopener">https://judoscale.com/heroku-postgresql-connection-calculator/</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Hoje vai ser um tema bem complicado de acompanhar, mas é pra vocês programadores iniciantes fazendo cursos superficiais ou aprendendo a fazer suas primeiras aplicações Web. O objetivo é tentar explicar qual a diferença do seu app que acabou de conseguir fazer subir na sua máquina depois de ver alguns tutoriais e o que se faz em aplicações de empresas de verdade como Amazon ou Mercado Livre da vida. Por que o ecommerce que você fez passo a passo assistindo um video no YouTube não se parece com nada com uma aplicação de verdade? Qual a diferença? Vamos entender.</p>
<p>(&hellip;)</p>
<p>O que vou mostrar hoje existe detalhado em diversos tutoriais, blog posts, documentação, pra cada framework web. Eu poderia mostrar exatamente qual o jeito que se faz em Ruby on Rails, ou em Spring Boot, mas eu quero que o foco não seja a linguagem ou framework, mas sim os conceitos. Em outro episódio talvez explore um pouco mais no nível do código, mas pra hoje os conceitos são mais importantes do que o código em si, que você pode achar facilmente no Google. Tudo que eu disser aqui é válido pra qualquer framework web. Não existe nada que uma linguagem ou framework faça que outra também não faça. Muita gente pensa que o segredo é escolher a linguagem certa, mas não, é saber como as coisas funcionam além do passo a passo.</p>
<p>Vamos recapitular rapidinho. Você abre seu navegador e chama amazon.com.br. O navegador empacota esse pedido no que chamamos de um HTTP Request, uma estrutura de dados que segue o protocolo HTTP. Esse pacote sai da sua máquina via rede, cai na internet, como expliquei nos episódios sobre redes, e chega até um dos servidores da Amazon. Seja lá no que é feito a aplicação web deles, possivelmente Java, recebe essa requisição. Ele monta a página HTML, empacota num HTTP response, de resposta, e devolve pela mesma conexão de volta pro seu navegador que, finalmente, renderiza a homepage deles pra você ver.</p>
<p>Se abrir o Developer Tools e ir na aba de Rede dá pra ver exatamente o cabeçalho do pacote de requisição aqui, olha só o endereço que pedimos e outros detalhes como que navegador está usando. E em seguida temos o pacote de resposta, que contém um cabeçalho com um código de resposta 200, que significa que deu tudo certo e no corpo vem o HTML.</p>
<p>Se estiver estudando front-end ou mesmo se baixar um template pré-pronto, acho que todo estudante assistindo aqui consegue imaginar fazendo essa página em Laravel, Express, Spring, Django ou Rails. Basta ter um banco de dados qualquer seja em MySQL ou Postgres ou Mongo, montar o HTML e devolver. Um pseudo código seria mais ou menos assim: um template HTML, e uma lógica que faz select na tabela de produtos, pega os 10 ou 20 primeiros, e monta a lista de produtos em HTML.</p>
<p>Qualquer um que tenha assistido qualquer curso online sabe fazer até aqui, certo? Se for uma aplicação em Rails você sobe ela com o comando <code>rails server</code>, se for Django sobe com <code>python manage.py runserver</code>, se for express pode subir com <code>npm run dev</code> ou seja lá como chamou a task. Se for Laravel vai subir com <code>php artisan serve</code>. Se for Spring vai subir com <code>gradlew bootRun</code>. Se for Elixir Phoenix vai subir com <code>mix phx.server</code>. Todo framework tem uma forma de iniciar o servidor web que vai servir a aplicação.</p>
<p>Se funciona assim na sua máquina, provavelmente já imaginou que bastaria então criar um servidor remoto na DigitalOcean, Linode, AWS, Azure, instalar a linguagem, clonar o código fonte do seu projeto e executar o mesmo comando no servidor, e pronto, tá terminado né? Você já é um programador web. Fácil. Basta agora registar um domínio, apontar pra esse servidor e pronto, seu site está no ar. E sim, esse é o jeito errado de fazer. Agora que a coisa começa.</p>
<p>Já notou que quando sobe uma aplicação web na sua máquina, o framework costuma subir numa porta como 3000 ou 4000 ou 8080? Tudo menos porta 80? Mas acho que até o mais iniciante, ou se assistiu meus videos de rede, sabe que o navegador, por padrão, manda requisições HTTP na porta 80 ou 443 dos servidores. Então porque o framework já não deixa sua aplicação rodando na porta 80 direto? Dois motivos. Primeiro porque pra subir na porta 80 precisa ter privilégios de administrador, daí você seria obrigado a subir usando o comando <code>sudo</code> toda hora, o que não é recomendado. Mas em segundo lugar, porque nenhum framework web foi feito pra rodar diretamente exposto na internet na porta 80.</p>
<p>Por exemplo. Tirando Java e Elixir, que são linguagens com suporte a threads reais e multi-thread, todas as linguagens de script, Javascript, PHP, Python e Ruby, são feitos pra rodar primariamente numa thread só. Sim, eles tem um certo suporte a múltiplas threads, mas via de regra, assuma que cada processo que sobe, tem capacidade de saturar somente um dos núcleos da CPU da sua máquina ou do servidor. Pode fazer o teste, faça um código que fica sei lá, fazendo um loop infinito fazendo qualquer coisa, e rode. Agora abra um monitorador de CPU como htop, e vai ver que sua máquina tem várias CPUs, mas a maioria delas não está sendo usada.</p>
<p>Se for instalar num servidor digamos, de 4 núcleos, o certo é subir sua aplicação web 4 vezes, um processo por núcleo. Talvez um pouco menos, depende da carga, já falo disso, mas só um processo vai desperdiçar sua CPU. Um dos processos pode subir na porta 3000, o segundo na porta 3001, a outra na 3002 e assim por diante. Cada processo precisa dar bind numa porta diferente. Mas aí fodeu, como que o navegador do usuário vai saber com que porta conectar? Você nunca teve que digitar amazon.com.br dois pontos 3002, por exemplo.</p>
<p>Como eu disse, nenhum framework web foi feito pra ser exposto direto na internet. Processos web sempre ficam atrás de um balanceador de carga, um outro programa como HAProxy ou, o mais comum hoje em dia, NGINX. Eles fazem um processo chamado Proxy Reverso. Quem fica exposto na porta 80 ou 443 é um NGINX. Os usuários mandam requisições pra esse balanceador de carga que, por sua vez, sabe que tem 4 processos nas portas 3000 a 3003 de pé no servidor por trás e fazem round robin ou alguma outra estratégia de balanceamento pra mandar uma requisição pra cada processo, tentando manter todos igualmente ocupados.</p>
<p>O usuário final não sabe que na primeira vez caiu no processo na porta 3002, quando clicou num link pedindo outra página, caiu na porta 3003 e assim por diante, pra ele é transparente, porque só enxerga o balanceador de cargas. E o processo atrás, também não sabe que existe um balanceador na frente, ele recebe a requisição HTTP como se o usuário tivesse conectado direto nele, como se faz na sua máquina local de desenvolvimento quando pede &ldquo;localhost:3000&rdquo; no navegador. Mas esse é o primeiro jeito mais simples do que chamamos de escalabilidade.</p>
<p>Faz de conta que a lógica que você fez no seu código, pra puxar produtos do banco de dados, montar o HTML e tudo mais leve em média 100 milissegundos. Significa que esse um processo que você subiu na porta 3000 tem capacidade de responder até 10 requisições por segundo, já que um segundo tem mil milissegundos. Se vier mais do que 10 requisições no mesmo segundo, os que vieram depois ficam numa fila esperando o processo desocupar. Por isso subimos mais de um processo na mesma máquina. Se subirmos 4 processos nos servidor, a grosso modo, passamos a suportar 40 requisições por segundo. É isso que chamamos de throughput do servidor, que é a capacidade de quantas requisições ela consegue responder por segundo.</p>
<p>Nenhum programador iniciante pensa nisso, mas o código que você faz tem um limite de quanto consegue responder por período de tempo. Não é mágica. Quanto mais gente for usar sua aplicação, ou subimos mais processos, em mais servidores, ou damos um jeito de fazer a requisição ser respondida mais rápido que 100 milissegundos pra ter mais capacidade no mesmo processo. Por isso que a gente tira tanto sarro de programador copy-e-paste de stackoverflow, porque vocês que fazem isso às cegas, estão adicionando código que diminui a capacidade de resposta de cada processo.</p>
<p>Um proxy reverso como NGINX serve como primeira barreira de segurança. Ele é um software mais simples, melhor otimizado, mais seguro, e que muda muito menos que o servidor web que vem com seu framework, seja o Tomcat de Java ou o Puma de Ruby ou o Cowboy de Elixir. É boa prática esconder eles atrás do NGINX. Mas não só isso, além de servir pra balancear a carga de requisições entre vários processos, é nele que também instalamos coisas como o certificado SSL pra abrir conexões seguras via HTTPS. O navegador fecha uma conexão encriptada com o NGINX, mas atrás dele, o NGINX manda HTTP não criptografado pra sua aplicação. Dessa forma, sua aplicação não precisa se preocupar em lidar com coisas com certificados, o que facilita muito mais o gerenciamento de infraestrutura pros devops.</p>
<p>Sem entrar em detalhes, no mundo Kubernetes esse é o papel do controlador Ingress, que pode usar o NGINX como proxy reverso também. Em resumo, uma aplicação de verdade vai ter NGINX balanceando carga pra múltiplos processos da sua aplicação web, possivelmente em múltiplos servidores. Só que mesmo NGINX também não escala infinitamente. E se meu balanceador de carga estiver no limite?</p>
<p>Tem vários jeitos de resolver isso e um deles é direto no DNS. Quem assistiu meus videos de rede vai lembrar que quando pedimos pro DNS resolver um domínio, o DNS devolve um endereço IP. Esse é o endereço IP pro servidor que tem o NGINX ou equivalente. Mas no caso de serviços muito grandes como Google ou Amazon, eles devolvem múltiplos endereços IPs. E essa é a razão: porque tem vários servidores de balanceamento de carga. Inclusive tem um conjunto de servidores específicos pra cada região geográfica. Se pedirmos amazon.com do Brasil, vai voltar um conjunto de endereços pra servidores daqui. Mas se estiver no Japão, vai receber um conjunto de endereços diferentes pra servidores da Ásia. É assim que uma Amazon da vida consegue escalar em nível mundial.</p>
<p>Lógico, pra maioria de nós, um único servidor de NGINX costuma ser mais que suficiente, mas é bom saber que essas técnicas existem. E aí você pensa. Beleza, entendi. Se eu fiz uma homepage que responde em 100 milissegundos, e eu subir 4 processos num droplet da DigitalOcean, significa que consigo responder até 40 requisições por segundo. Se ver no Analytics que minha campanha de marketing deu certo e o tráfego está subindo pra 80 requisições por segundo, das duas uma, ou eu faço upgrade na máquina pra ter 8 núcleos pra subir o dobro de processos, ou subo um segundo servidor pra subir 4 processos novos. Reconfiguro o NGINX pra saber que existem esses 4 novos processos e ele passa a balancear a carga pra lá também.</p>
<p>Quando fazemos upgrade da mesma máquina, chamamos de &ldquo;escalabilidade vertical&rdquo;, quando colocamos máquinas novas embaixo do balanceador de carga, chamamos de &ldquo;escalabilidade horizontal&rdquo;. E falando desse jeito parece simples. Se o tráfego aumentar pra 400 requisições por segundo, é só subir 10 servidores embaixo do NGINX certo? Só que não, quem dera fosse fácil assim. O que vou falar agora tem muitas nuances por causa do comportamento de múltiplas threads, mas vou tentar simplificar pra ficar fácil de visualizar, só entenda que é mais complicado que isso.</p>
<p>Lembram do video de Heroku? Eu ainda recomendo como melhor lugar pra iniciar, mesmo tendo cancelado os planos gratuitos. Pra só brincar, precisa achar outro lugar, mas se pretende colocar um ecommerce no ar e queria colocar num plano gratuito, você precisa repensar seu negócio. Uma aplicação que sequer consegue pagar a própria infraestrutura, tem problemas muito mais sérios do que custo de máquina. Uma das vantagens é que essa parte toda de balanceador de carga NGINX é transparente. Eles gerenciam, você só precisa mandar subir os processos no servidores deles, que eles chamam de dynos. E o balanceador é automaticamente reconfigurado.</p>
<p>Tem uma calculadora online que vai nos ajudar a ilustrar o que eu quero dizer aqui. Olha só. Digamos que estamos naquele cenários de 400 requisições por segundo. Num dyno pequeno de 4 cores, a recomendação é subir 3 processos. Se pegar um dyno maior o recomendado é subir o total de cores menos um. Lembra que o sistema operacional também precisa de um pouco de CPU pra gerenciar o que roda por cima, por isso não queremos saturar tudo só com nossa aplicação. Você tem que saber pensar essas coisas. Então vamos subir 3 processos, só que aí, pra responder 400 requisições por segundo, 10 requisições por processo, dividido por 3 processos, vamos precisar de pelo menos 13 dynos.</p>
<p>Eu disse que linguagens de scripts lidam com múltiplas threads diferente de Java ou Elixir. Eles tem threads, só não conseguem rodar todas em paralelo o tempo todo. Eu falo sobre isso no episódio sobre concorrência e paralelismo. Mas na prática podemos configurar cada processo pra aceitar, digamos, 5 threads, ou seja, 5 conexões simultâneas. Não pensem muito sobre isso agora. Mas o importante é o número a seguir: 5 conexões a banco de dados no pool de conexões. O que diabos é um pool de conexões?</p>
<p>Conexão a banco de dados é uma operação que custa recursos do banco de dados. Não dá pra ficar abrindo conexão infinitamente. Estão notando um tema no video de hoje: nada é infinito em programação. Você como iniciante só não sabe disso porque só testando seu programinha do tutorial sozinho é muito pouco pra chegar no limite. Mas na internet, com milhares de usuários de verdade usando, toda hora vai esbarrar em limites e sua função, como programador, é saber como gerenciar esses limites. Pensa assim, cada sessão de uma conexão no banco de dados precisa ser registrada em algum lugar. Esse registro vai gastar memória e CPU. Quanto mais conexões, mais memória e CPU vai se gastar pra gerenciar todo mundo.</p>
<p>Seja o processo da sua aplicação web ou o processo do banco de dados, sem um limite, uma de duas coisas vai acontecer: ou vai acabar memória, ou vai acabar processamento. Mas um dos dois vai acabar. Quando isso aconteder, tudo trava e pára de responder. Não queremos que isso aconteça, então configuramos limites, pra conseguirmos pelo menos ter processamento pra rejeitar e devolver erro caso um dos limites seja atingido. Antigamente a gente deixava rodando até passar do limite e era o caso onde páginas na web ficavam com ampulheta de &ldquo;carregando&rdquo; por minutos, sem resposta. Hoje tomamos um timeout pelo menos.</p>
<p>Uma das técnicas que adotamos lá na virada do século foi pool de conexões. Digamos que minha aplicação Java mal feita saísse abrindo conexões e esquecendo de fechar elas. Rapidamente o limite do banco ia estourar. Se o mesmo banco fosse usado por várias aplicações diferentes, bastava uma mal feita, pra saturar todas as conexões. Em vez disso limitamos a quantidade que cada aplicação pode usar. A aplicação deixa de conectar direto no banco, em vez disso ela se conecta num pool, e esse pool mantém um certo número fixo de conexões abertas.</p>
<p>No exemplo da calculadora, cada processo da sua aplicação pode ser configurado com 5 conexões. Se tiver 3 processos no mesmo dyno, cada dyno vai consumir 15 conexões com o banco. Se o banco suportar até 150 conexões, dá pra subir até 10 dynos, em teoria. E isso deveria ser suficiente. Se cada processo aceita até 5 threads simultâneas, e cada thread precisar de uma conexão, vai ter pelo menos 1 pra cada. Quando a thread terminar de usar a conexão, ela devolve pro pool, pra poder ser reusada por outra thread.</p>
<p>O pool faz meio que o papel do NGINX entre o navegador do usuário e sua aplicação, é um intermediário de balanceamento. No caso, a função do pool de conexões é permitir múltiplas threads da aplicação reusar conexões já existentes em vez de toda hora tentar abrir novas conexões. E com isso conseguimos controlar a quantidade máxima de conexões sendo utilizadas ao mesmo tempo.</p>
<p>Serviços como o Heroku Posgres ou AWS RDS tem diversos planos de tamanhos e preços diferentes. Um iniciante pensa num plano de banco de dados mais como dólares por capacidade de armazenamento de dados. Sei lá, 10 dólares pra 1 terabyte, 100 dólares pra 10 terabytes ou algo assim. Mas mais importante que total de armazenamento é qual o máximo de conexões que cada plano oferece. Um plano básico de Heroku Postgres, pode oferecer, digamos 120 conexões no máximo.</p>
<p>Ou seja, de acordo com a simulação que fizemos na calculadora. Subindo minha aplicação em 13 dynos, com 5 threads cada, com um pool de 5 conexões, 13 vezes 5 vezes 5, significa que preciso de no mínimo 195 conexões pro banco de dados. Ou seja, um plano básico de 120 conexões máximas, já não vai dar. Se realmente tiver as tais 400 requisições por segundo, das duas uma, ou eu pago mais e aumento meu plano de Postgres, ou uso um paliativo que é colocar um gerenciador de conexões como o pgbouncer na frente do Postgres.</p>
<p>A grosso modo funciona assim. Antes, na sua máquina, sua aplicação conectava direto no banco de dados. Não fazemos mais isso, todo framework decente já trás um pool de conexões pra você usar e é ele quem se conecta no banco. Mas podemos colocar uma camada a mais e o pool também não fala mais direto com o banco e sim com outro intermediário, o pgbouncer. O pgbouncer vai usar no máximo uns 75% do máximo de conexões do banco, nesse exemplo seriam 90 conexões. Mas ele tem capacidade de deixar até 10 mil pedidos de conexão pendurados em cima dele antes de dar erro.</p>
<p>Significa que não vamos tomar erro de falta de conexão, mas as requisições precisam esperar mais tempo e se ficar pesado demais, tomar timeout de qualquer jeito, só estamos tentando esticar um pouco mais. É como se fosse a fila de um Poupatempo ou cartório da vida. Só tem 90 atendentes, mas tem 195 pessoas na fila. As primeiras 90 são atendidas imediatamente, o pgbouncer são as cadeiras de espera pras outras 105 ficarem esperando com a senha pra serem atendidos. A solução de todos os limites em programação é a mesma de uma repartição pública: fazer fila e esperar a vez.</p>
<p>Só que isso é um paliativo. Significa que se antes cada requisição era respondida em até uns 100 milissegundos, agora pode levar mais tempo, talvez 120 milissegundos. Isso muda a conta toda que fizemos até agora. Se o número de processos por dyno é fixo, precisamos subir mais dynos. Mas se subirmos mais dynos e carregarmos mais processos, isso vai pressionar mais o pool de conexões, que vai exigir mais conexões ao banco de dados. Estão ententendo como as coisas não são fáceis?</p>
<p>Existem várias formas de resolver isso. Chegamos ao limite do que só configurando infraestrutura pode fazer. Será que não dá pra resolver isso no código? Opa, talvez sim. Dos 100 milissegundos que sua aplicação web leva pra montar a homepage, 50 é gasto só mandando a query de select pro banco, recebendo as linhas de resposta e processando esses dados. Mas pensa comigo, no seu ecommerce pequeno, literalmente todo mundo que pedir a homepage vai acabar vendo os mesmos produtos, não vai? Se vieram 400 requisições de homepage, você fez 400 pesquisas no banco de dados que ficou respondendo 400 vezes a mesma coisa. Isso é um puta desperdício.</p>
<p>Todo framework web que se preza já tem embutido recursos pra fazer cache. Cache é guardar a última resposta pra não reprocessar tudo de novo a mesma coisa. Se fizermos a implementação mais simples e tosca, fazemos um código pro seguinte: procure por um arquivo local chamado &ldquo;cache-homepage&rdquo;. Se não tiver, faça a mesma coisa de antes: solicite uma conexão ao banco pro pool, mande o select pra fazer a pesquisa, receba as linhas de resposta, processe e monte o HTML baseado nos produtos recebidos. Mas agora, antes de devolver pro usuário, grave um arquivo local chamado &ldquo;cache-homepage&rdquo;. Pronto.</p>
<p>Na segunda requisição por homepage que vier, agora ele vai encontrar esse arquivo, podemos checar que esse arquivo não é mais velho que 5 minutos, faz de conta, e pulamos toda a parte de falar com o banco de dados, devolvendo direto o que estiver nesse cache. Entenderam? Vamos fazer as contas. Digamos que subimos só 8 dynos, com 3 processos cadas, 5 threads e 5 conexões no pool, o que vai consumir todas as 120 conexões que o banco Postgres que contratei suporta no máximo desse plano. Daí começa a vir requisições. Digamos que o NGINX balanceou perfeitamente e as primeiras 8 requisições que chegaram mandou um pra cada um dos 8 dynos.</p>
<p>Em cada dyno, o processo que respondeu primeiro fez conexão com o banco e deixam esse arquivo &ldquo;cache-homepage&rdquo; pra trás. Agora chegou o resto das 392 requisições durante 1 segundo. Sabe quantas conexões pro banco foram abertas nesse segundo? Antes precisava de 195. Agora precisou de zero. Sacaram? Zero! Porque toda requisição que veio depois que o arquivo de cache foi gravado, leram direto desse arquivo e não precisaran conectar nem no pool, nem no banco. E se arbitrariamente estabelecermos que a lista de produtos não corre risco de sofrer alterações durante 5 minutos, por 5 minutos, essa homepage passa a consumir zero conexões com o banco de dados.</p>
<p>Esse é o tipo de otimização simples com alto impacto. Antes precisávamos de 195 conexões com o banco por segundo, agora precisamos de não mais que uns 10 a cada 5 minutos. E como 50 milissegundos por requisição era gasto só nesse trabalho com o banco, significa que na média eu diminuí o tempo de cada requisição pela metade! Sem trocar linguagem, sem trocar framework, sem reescrever a aplicação. Eu poderia diminuir pela metade a quantidade de dynos pra responder as mesmas 400 requisições por segundo. Ou deixo como está e posso fazer mais marketing pra dobrar o tráfego e ainda consigo responder todo mundo com folga.</p>
<p>Vou insistir no conceito pra martelar na cabeça de vocês. Trocar de linguagem ou framework, sair de Python e ir pra Javascript, ou sair de Dotnet e ir pra Java, sair de Laravel e ir pra Express, ou qualquer coisa assim, pode dar alguma diferença pequena em algumas partes. 5%, 10%. Mas técnicas como essa de cache, colocadas em lugares inteligentes de alto tráfego, podem economizar recursos em ordens de grandeza, 5x, 10x, ou até mais. É esse o tipo de otimização que vale a pena fazer. Gastar semanas, pra ter ganho de 1%, 2%, não vale a pena. Gastar horas pra ganhar 5x, 10x, isso sim vale a pena.</p>
<p>Eu falei arquivo só pra explicar o conceito mais fácil, mas na realidade não usamos arquivos pra cache. Usamos um segundo banco de dados mais leve, como um Memcache ou Redis. São bancos que não tem as garantias de um Postgres de atomicidade, consistência, integridade ou durabilidade, ou seja, pode ser que eu mande gravar e ele não grave um registro, pode ser que grave mas quando for ler não está lá ainda, pode ser que grave duas vezes.</p>
<p>Se fosse um banco de dados de pagamento isso seria um problema. Mas pra cache não. Não tem problema, porque a lógica da aplicação que fizemos é: se existir no cache e não expirou, usa, se não existir, conecta no banco, remonta o HTML e grava um novo cache.</p>
<p>Não ter essas garantias significa que o Memcache ou Redis conseguem executar muito mais rápido e aceitam uma ordem de grandeza mais conexões simultâneas. Por isso configuramos um Memcache, que é um serviço barato, e fazemos essa lógica de cache pra ler e gravar do memcache em vez de um arquivo. A desvantagem é que precisamos pagar mais um serviço. Mas ele é barato e economiza mais ainda os recursos do banco, porque antes cada dyno precisava gerar seu próprio arquivo de cache, então quanto mais dynos, mais continua precisando puxar do banco na primeira vez. Agora só precisa uma vez e todos os dynos lêem do mesmo memcache.</p>
<p>Memcache como o próprio nome diz, é Memory Cache. É literalmente um cache em memória. Por isso que quando o servidor de memcache reinicia, perde tudo que tinha armazenado. Mas não tem problema, porque toda aplicação que depende do cache sabe como reaquecer esse cache, ou seja, reconstruir os dados que precisa em cache pra próxima requisição. Um Redis é parecido, mas com a vantagem que também grava em disco, daí se reiniciar, ele sabe reaquecer sozinho. Ele é pouca coisa mais lento mas tem durabilidade. De novo, é um trade-off. Em ambos os casos, significa que não precisamos aumentar o plano de banco de dados.</p>
<p>Mas a coisa não termina aqui. Isso foi só Hello World do Hello World. Bancos de dados é outro assunto infinito que daria pra fazer um canal inteiro só sobre esse assunto, mas o importante é que todo banco de dados tem perfis de performance de escrita e de leitura. Um banco de dados relacional como MySQL, Postgres, SQL Server, Oracle, costuma ter perfil de leitura rápida mas escrita lenta, justamente porque tem como característica as tais garantias ACID que falei antes. É lento pra escrever, porque se ele diz que escreveu, é porque escreveu e escreveu garantidamente correto. Ninguém vai receber um pedido duplicado, ou ser cobrado duas vezes, ou deixar de receber porque deu erro na gravação e ninguém ficou sabendo.</p>
<p>Os tais bancos NoSQL, tem outras características. Um Redis, Mongo, Cassandra, te dão menos garantias, por isso podem ser mais rápidos pra escrever. Eu já fiz um video sobre NoSQL, depois assistam. Mas quando faz sentido ser rápido mas não ter garantias? Pensa um tipo de aplicação que se perder um registro não faz diferença. Por exemplo, um Google Analytics. Todo site que tem configurado vai mandando pro Analytics cada clique e cada visita dos usuários no site. Se uma dessas visitas se perder e não registrar no Analytics, não vai fazer diferença. Ou seu Apple Watch, que fica monitorando batimentos cardíacos a cada X segundos. Ele manda pro servidor pra ir montando um perfil da sua saúde. Se um desses batimentos se perder e não gravar, não importa, o importante é a média ao longo do tempo. São casos onde não ter as garantias ACID permite usar um outro banco mais rápido.</p>
<p>Um MongoDB pode ser quase ACID se configurar  pra esperar pra dar ok na escrita, pra garantir que realmente gravou. Só que aí perdemos a característica de ter escrita mais rápida e vai ter que esperar de qualquer jeito. Um Cassandra tem escrita atômica, mas consistência eventual. Ou seja, até garante que escreveu, mas se consultarmos imediatamente talvezresponda que ainda não está lá, porque a leitura caiu num outro node que ainda não sincronizou. Isso é útil quando o que queremos é replicação do banco em múltiplos servidores.</p>
<p>Não existe um tamanho que serve tudo. Por isso que pra decidir qual banco usar, precisa entender a diferença nesses perfis de todos em detalhes. Não vai ter uma resposta fácil pra tudo. Mas no geral, pra grande maioria da população que não é uma startup unicórnio, você realmente só precisa de um banco de dados relacional como Postgres sendo auxiliado por um servidor de cache como Memcache ou Redis. Mais do que isso e não é vendo um video que vai ajudar.</p>
<p>E mesmo quando falamos leitura. Existem leituras simples, como puxar uma lista de produtos pra montar uma página, e leituras complicadas que é gerar um relatório das vendas do semestre organizado por região e tudo mais, que cruza os dados de todos os pedidos feitos no semestre, com os dados de todos os clientes, e seja lá quantas outras tabelas pa montar um único relatóriozão. Quem entende de SQL é aquela query com 500 joins, outer joins, sub-selects. Se já trabalhou em algo assim, já viu esse problema: quando seu ecommerce ou sistema fica lento ou até pára de responder toda vez que precisa gerar esses relatórios gigantes.</p>
<p>Uma coisa é uma pesquisa simples que responde em poucos milissegundos. Outra coisa são relatórios que podem levar múltiplos segundos travando o banco inteiro. E se você tiver um tráfego de 400 requisições por segundo, de repente, todo mundo tendo que esperar na fila até o relatório terminar, e levar 10 segundos pra terminar, são 4000 requisições que estamos deixando de responder, centenas de pessoas tomando timeout no navegador e ficando irritados que o site não responde.</p>
<p>Por isso que num sistema de verdade, dividimos essas coisas em múltiplos servidores. Temos o banco principal, que está ocupado gravando pedidos de pessoas de verdade e não pode ser interrompido quando algum gerente pede um relatório gigante. Por isso que em serviços como Heroku Postgres ou AWS RDS tem fácil pra se gerar servidores de réplica só pra leitura. É uma cópia que pode estar até alguns segundos atrasado do banco principal e que não deixa ninguém gravar nele, mas como fica num servidor separado do banco principal, mesmo se rodarmos coisas pesadas nele, não interrompemos o tráfego do site, que conecta em outro servidor.</p>
<p>Na verdade é bem comum ter um banco principal só pra escrita e uma réplica só pra servir dados pra montar as páginas do site, e outra réplica exclusiva pra tarefas administrativas como gerar relatórios. Quem fala que quer usar um NoSQL porque bancos relacionais não escalam, não sabem o que estão falando. Em escala de empresas normais não-unicórnios, não precisa. De novo, soluções exotéricas de NoSQL só são necessários se você for o Netflix. Mas pros resto da população, um banco de dados tradicional relacional com réplicas de leitura, já resolve a grande maioria dos problemas. Lembra? Soluções simples de alto impacto que não exige reescrever tudo. É isso que sempre queremos.</p>
<p>Mas ainda temos um último problema de banco de dados. Até que não é tão difícil ter réplicas só de leitura. E eu digo só de leitura porque ter replicação bi-direcional de leitura e escrita, é pouco prático e só deve ser considerado em situações especiais. No geral é melhor ter um único servidor que concentra todas as escritas e envia os dados pra réplicas só de leitura. Mas isso significa que em algum momento, se o tráfego do site aumentar, numa situação de semana de Natal, Black Friday da vida, vai ter mais gente tentando gravar pedidos do que o servidor principal consegue suportar.</p>
<p>Uma lógica comum numa página de checkout de ecommerce é mais ou menos assim: a lógica do carrinho de compras já checou que tem disponibilidade dos produtos, seu front-end já coletou os dados de entrega, os dados de pagamento, e agora iniciamos a transação pra gravar o pedido. Dentro da lógica dessa transação, conectamos com o meio de pagamento, seja MercadoPago, Paypal, ou o que for, e esperamos responder ok. Se foi confirmado, terminamos a transação e fechamos o commit na tabela do banco de dados e, finalmente, respondemos o HTML pro usuário com o número do pedido que acabamos de gerar. Durante esse período todo o navegador do usuário ficou pendurado, com ampulhetinha rodando, esperando carregar.</p>
<p>Num dia normal de pouco movimento, ou na sua máquina de desenvolvimento, isso funciona perfeitinho. Mais um exemplo de coisa que parece simples só porque você não tem noção da realidade. Mas agora imagina. Você não controla coisas externas como o meio de pagamento. E num dia de Black Friday, ele também pode ficar mais lento. Num dia anormal, tem um monte de gente pendurado nos seus servidores, todo mundo tentando dar checkout nos pedidos. O banco de dados tem limite de conexões, tem limite de transações, e chega uma hora que mesmo tendo fila, o tempo é tão longo que ele é obrigado a começar a desconectar as pessoas pra conseguir trabalhar. É quando o cartório tá tão cheio que não cabe mais gente dentro. O que você faz, manda todo mundo embora?</p>
<p>Parte disso já expliquei no video sobre o Ingresso.com. Onde resolvemos a primeira parte do problema, na parte da frente da loja, que é dar senha pras pessoas e manter uma fila muito mais longa do tipo &ldquo;volta mais tarde pra ver se já chegou sua vez&rdquo;. E pelo menos o navegador dos usuários não ficam ativamente pendurados nos servidores web, consumindo recursos. Eles podem desconectar, e uma lógica no navegador conecta de tempos em tempos pra checar ou usa protocolos apropriados pra isso como Web Sockets. Mas e depois que as pessoas estão na loja e querem pagar o pedido?</p>
<p>E, pra surpresa de ninguém, não é diferente, a solução é fazer mais filas. Muita gente associa o número do pedido com o ID da gravação da linha na tabela que o banco de dados auto-incrementa e devolve quando deu tudo certo na gravação. Mas se já estamos no cenário onde passamos do limite físico de gravações por segundo que o banco aguenta, não podemos depender disso. O correto é ter uma API ou um microserviço cuja única função é me dar um número que é garantidamente único e não duplicado. Tem várias formas de fazer isso. Mas só entenda o conceito que desassociamos o número do pedido do ID da tabela de pedidos.</p>
<p>Em seguida, não gravamos mais o pedido direto no banco. Em vez disso pegamos os dados do pedido, com esse número e jogamos num serviço de fila. Já falo disso, mas ao fazer isso, quando o usuário preenche seus dados no formulário de checkout e clica em pagar, imediatamente devolvemos a página HTML de resposta dizendo &ldquo;obrigado, o número do seu pedido é XYZ, e assim que confirmarmos você receberá um e-mail&rdquo;. E pronto, liberamos essa pessoa pra ir embora, e ela pára de consumir recursos no nosso sistema tendo que ficar esperando. É o equivalente de fazer a pessoa ir embora do cartório ou correios e dar espaço pra outra pessoa entrar logo, fazendo a fila andar mais rápido.</p>
<p>Existem vários serviços de fila. Podemos usar o próprio Redis que usamos pra cache pra ser o gerenciador da fila. Podemos usar um serviço feito exclusivamente pra filas como o RabbitMQ ou Apache Kafka ou ActiveMQ ou vários desses MQ que significa Message Queue ou Message Brokers. A AWS oferece o ActiveMQ ou RabbitMQ como serviço ou podemos usar o pŕoprio dele que é o AWS SQS de Simple Queue Service. Todos são Queues, ou filas. A vantagem de serviços de fila é que podemos ficar enviando milhares de mensagens pra eles enfileirarem, que vai ser muito mais rápido e eficiente do que gravar no nosso banco de dados que já tá no limite de processamento.</p>
<p>Eu falei Redis, ele funciona direitinho, mas não é tecnicamente feito pra ser uma fila de verdade. Protocolos de fila de verdade, como implementado num RabbitMQ tem garantias como um banco de dados relacional tem ACID. Um serviço de fila de verdade precisa nos dar garantia de entrega, ou seja, se ele responde que entregou, precisa ter entregue de verdade, e não pode duplicar a mensagem ou coisa assim. Além disso um erro que alguns cometem é criar uma tabela no banco de dados pra usar como fila. Mas se estamos querendo justamente tirar pressão do banco de dados usando uma fila, não faz nenhum sentido botar a fila no banco.</p>
<p>Uma vez na fila, daí nós programadores precisamos fazer uma segunda aplicação separada, que chamamos de workers. Workers são programinhas curtos que ficam de pé esperando alguma coisa aparecer numa fila dessas. Eles ficam escutando essas filas, normalmente falamos que assinam um canal dessas filas. Uma fila pode ter um canal de recebimento de pedidos, outro canal de envio de emails, canal de realizar pagamentos. Cada canal vai ter um worker que sabe o que fazer com a mensagem que tiver naquele canal. O Worker que assina o canal de realizar pagamento sabe como se conectar com o meio de pagamento.</p>
<p>A vantagem é que separamos as atividades que não controlamos, como confirmação de pagamento, e não precisamos deixar o usuário pendurado esperando uma resposta. Mandamos o usuário embora o quanto antes, e mesmo que o meio de pagamento resolva demorar bem mais que o normal, tudo bem, só vamos demorar um pouco mais pra mandar o email de confirmação pro usuário. E não precisamos torturar o nosso banco de dados com várias transações abertas penduradas que começam a dar timeout e rollback sem parar.</p>
<p>De novo, cada framework web moderno tem sua forma de lidar com isso que chamamos de asynchronous jobs. No Ruby on Rails tem ActiveJob, que é uma classe que herda de ApplicationJob e implementa um método chamado <code>perform</code>. Na configuração geral dizemos que vai escutar de um Redis ou outro serviço de fila. No Node Express podemos usar a biblioteca Bull, onde configuramos um objeto de Queue que também diz de que fila consumir e os workers, que ele chama de consumers, onde temos uma função chamada <code>process</code> pra processar cada job que vier da fila.</p>
<p>Em Spring podemos integrar com Apache Kafka, fazendo uma classe consumer com um método <code>consume</code>. Em PHP o Laravel tem suporte nativo, onde fazemos uma classe que herda de ShouldQueue e implementa um método <code>handle</code>. Acho que deu pra entender. E cada um tem estratégias de como configurar esses workers, como fazer deploy e tudo mais. Vocês precisam estudar a documentação oficial. Em qualquer outro framework, pesquise no google por &ldquo;asynchronous jobs&rdquo;.</p>
<p>Mas claro, nem tudo são flores. Você poderia pensar. &ldquo;Boa, então é só eu pendurar 500 workers atrás dessas filas que tudo vai ser processado rapidinho então&rdquo;. Mas não. Alguns desses workers, no final, continuam precisando gravar coisas como o pedido na tabela do banco de dados principal. Eles podem demorar mais do que a aplicação web, porque não tem um usuário pendurado esperando, mas o pedido final precisa ser consolidado no banco e aí caímos de novo no problema de máximo de conexões e processamento do servidor de banco.</p>
<p>Se voltarmos naquela calculadora do Heroku, note que embaixo tem uma parte que eu não tinha mostrado. O Heroku suporta subir workers, mas cada worker vai consumir conexões no banco também. Se subir 10 dynos, com 1 processo cada, 10 threads por processo e 10 conexões no pool, vai precisar de 100 conexões no banco de dados, além das conexões que já precisava pros dynos de web. Por isso, de novo, não é mágica. Temos que ficar balanceando quantos processos web versus quantos processos workers queremos ter pra um certo orçamento que estou disposto a pagar pela infraestrutura.</p>
<p>Sacaram? Organizar esses recursos é parecido com jogar RPG, quantos pontos queremos de vida, quantos pontos queremos de mágica, quanto quero gastar em upgrade  de arma. Não adianta ter a melhor arma, mas pouca vida. Configurar arquitetura web é que nem jogar RPG.</p>
<p>Mas como começamos a colocar cache na camada web, o que diminui a necessidade de dynos web, sobra mais conexões pra colocarmos mais workers. E é assim que vamos evoluindo de uma aplicação de um único processo que roda na sua máquina pra uma que divide os recursos e nos dá oportunidades de balancear onde queremos usar mais do que.</p>
<p>Vamos ver o que acabei de falar num diagrama pra facilitar. Começamos assim: uma caixinha que representa um processo da nossa aplicação web, aquela que você sobe na sua máquina com comandos como <code>npm run dev</code> ou <code>gradlew bootRun</code>. Esse processo vai configurado pra conectar num banco de dados como Postgres. Seu navegador se conecta direto nessa caixinha, via <code>localhost:3000</code>. E na sua máquina tudo funciona.</p>
<p>Mas numa infraestrutura de verdade, como AWS, Azure, Google Cloud ou Heroku que gosto de usar de exemplo pela simplicidade, já começa que na frente da caixinha da sua aplicação vai ter um balanceador de carga, como um NGINX. É nele que seu navegador vai se conectar quando digitar o domínio da sua aplicação. É o endereço IP dele que você configura no registro do seu domínio pra DNS. No painel de todos  tem como configurar certificados SSL e tudo mais. Ele que vai pegar o tráfego dos usuários e direcionar pra containers web, os dynos. DigitalOcean chama de droplets. Em Kubernetes chamamos de pods. Mas enfim, é onde roda o processo da sua aplicação.</p>
<p>Na realidade, em produção não vai ter só uma caixinha da sua aplicação. Vai ter vários. Cada um numa porta separada no tal container ou dyno. E o balanceador de carga vai distribuindo uma requisição pra cada uma delas, tentando manter todos ocupados por igual, se possível. Agora, todas essas caixinhas continuam precisando se conectar no banco de dados. Mas já sabemos que existe um limite máximo dependendo do plano que pagamos. Pra não perder controle, cada caixinha vai ter um pool de conexões, com um máximo de conexões reusáveis que ele pode manter.</p>
<p>Pra não desperdiçar recursos do banco de dados, a técnica mais importante é estrategicamente colocar cache nos lugares ou mais pesados ou que são acessados com mais frequência e cujos dados não mudem muito, ou que pelo menos não mude muito no espaço de alguns minutos que seja. 5 minutos que suas caixinhas não se conectam no banco faz muita diferença. Lembrem-se, temos muitas caixinhas ativas ao mesmo tempo! Então subimos um novo serviço, um Memcache. Um serviço de cache também tem um limite de quantos megabytes ou gigabytes de RAM queremos usar de cache.</p>
<p>E não precisamos nos preocupar se encher esse espaço porque o Memcache é inteligente de ir apagando cache mais velho pra dar espaço pra cache mais novo. E eu só dei exemplo de cachear algo que todo mundo vê igual, como lista de produtos numa homepage, mas podemos fazer cache por usuário. Assim se o usuário ficar dando refresh na página de perfil dele, não precisa ficar toda hora indo no banco pra pegar os mesmos dados. Vai estar no cache dele. Estratégias de cache são importantíssimos, vale estudar em detalhes.</p>
<p>Mas à medida que nossa aplicação cresce, temos funcionalidades pesadas, integrações com serviços de terceiros, como meios de pagamento, sistemas de gestão como um ERP, várias coisas que aumentam o tempo de resposta e fazem nossos usuários esperar tempo indeterminado. E nosso banco de dados também não tem processamento infinito. Mesmo colocando cache, ainda assim precisamos controlar esses recursos.</p>
<p>Pra isso podemos começar distribuindo a leitura em servidores de réplica desse banco. Todo bom framework tem como configurar pra toda leitura ser feita num banco de dados e toda escrita ser feita em outro banco de dados. E esse banco de escrita é responsável por atualizar as réplicas. Mesmo se as réplicas ficarem um pouco atrasadas, alguns segundos, pra coisas como relatório semestral de vendas ou algo assim, não vai fazer diferença. E pra coisas como confirmação de número de pedido, podemos até deixar no cache do usuário, enquanto os bancos de dados ainda não se sincronizaram. Assim ninguém vai notar a diferença de tempo.</p>
<p>Mas só isso não é suficiente. O mundo ideal é que toda requisição HTTP feita por um usuário seja devolvido imediatamente, sem precisar esperar ninguém. Por isso não chamamos serviços externos como pagamento ou outras APIs no mesmo processamento de requisição HTTP do usuário. Devolvemos imediatamente pra ele um &ldquo;aguarde, depois mandamos um email confirmando&rdquo;. E criamos uma tarefa assíncrona que jogamos numa fila como SQS, Kafka ou mesmo Redis, então instalamos uma nova caixinha aqui que vai ser responsável só por isso.</p>
<p>E por fim, criamos workers ou consumers pra essas filas, pra fazer o processamento a posteriori, realizar pagamento, integrar com ERP, integrar com nota fiscal eletrônica, envio de e-mails e tudo mais, e só aí confirmamos gravando no banco de dados principal. Como essas tarefas podem demorar um pouco mais, também não saturamos o banco de dados principal e tudo continua funcionando de forma mais estável.</p>
<p>Mas não é só ir subindo caixinhas aleatoriamente. Pra isso é bom ter em mente aquela calculadora que mostrei antes. Existem várias calculadoras assim pra coisas como AWS, só procurar. Mas não espere que alguém te dê uma resposta exata. Tempo médio de uma requisição não o único número que você precisa. Numa aplicação web, páginas de conteúdo como lista de produtos, detalhes de produtos, perfil do usuário, são coisas facilmente cacheáveis e que respondem rápido.</p>
<p>Mas páginas de gerenciamento de carrinho que checa disponibilidade de produtos, checkout com integração de pagamentos, são páginas que demoram muito mais. Chutando, numa página de conteúdo conseguimos responder em menos de 50 milissegundos, mas uma página mais complicada de checkout poderia levar 500 milissegundos. São diferenças assim de 10x que se encontra em diversas partes de uma aplicação Web.</p>
<p>Se tirar a média, vamos chegar num número tipo 100 milissegundos, que não serve pra muita coisa, porque estamos escondendo as páginas pesadas que precisaríamos otimizar, e talvez estejamos otimizando páginas de conteúdo que já são rápidas o suficiente. Por isso eu sempre recomendo que se instale uma ferramenta online de monitoramento. A melhor que conheço é o produto RPM da New Relic.</p>
<p>Tem boas alternativas como o Scout e outros que não conheço. Mas o New Relic dá um raio X mais detalhado. Ele mostra quem são os principais ofensores, e se ele suportar o framework web que você usa, como Ruby on Rails, é capaz de dizer qual controller, qual model, qual query que realmente tá pesada. Assim não precisamos ficar adivinhando.</p>
<p>Otimização nunca deve ser feita no chutômetro. &ldquo;Ah, eu acho que é essa classe, ah eu acho que se refatorar esse job fica melhor&rdquo;. Olhem os números. 80% dos problemas de performance costumam ser causados por 20% da sua aplicação. Não precisa reescrever tudo. Em 10 funcionalidades, deve ter 2 que precisamos focar e conseguir economizar metade dos recursos da infraestrutura inteira. Como no exemplo do cache que mostrei no começo.</p>
<p>Toda otimização deve ser baseada em dados analíticos de uso real em produção, dados de uso de usuários de verdade usando sua aplicação. Por isso precisa de um New Relic. Uma vez estabelecido quem é o ofensor, sabemos quanto tempo tá consumindo. E quando fizer a correção e subir em produção, basta medir algumas horas e dá pra saber quanto foi efetivo ou não. Por isso não basta funcionar só na sua máquina, testando só com um usuário. Assuma que você não sabe nada até medir em produção.</p>
<p>Pro pessoal de front tem uma última otimização importante: aprender a usar CDN. O problema é o seguinte. Quando se monta o HTML e o servidor devolve pro navegador, esse HTML vai conter várias tags de imagens, css, scripts. Agora o navegador precisa voltar pro servidor e ir pedindo todos esses assets. Então uma requisição se multiplica em 20, 50, sejá lá quão complexo é o HTML que você fez. Normalmente um servidor web é bem rápido pra devolver essas coisas, já que não envolve conectar com banco de dados, APIs nem nada disso, mas mesmo assim, em grande volume, também pesa. Aquelas caixinhas da sua aplicação tão perdendo tempo devolvendo imagens sendo que podiam estar usando esse tempo pra gravar mais um pedido da sua loja.</p>
<p>Cada framework web, de novo, tem formas de integrar com um CDN como o AWS CloudFront ou Azure CDN ou Fastly. CDN funciona assim: em vez de escrever uma tag de imagem com o source relativo ao seu domínio, explicitamente escrevemos a URL do serviço de CDN tipo &ldquo;minhalojablabla.cloudfront.com&rdquo; ou seja lá qual URL que o serviço te der. E reescrevemos a tag de imagem usando um source absoluto. Só que isso seria um saco por dois motivos. Enquanto se está desenvolvendo a aplicação, nem tem o CDN configurado ainda, daí você,  desenvolvedor front-end, nem ia conseguir visualizar o que tá fazendo. O segundo problema é se escrever na mão as URLs pro CloudFront mas aí o pessoal de infra decide que prefere mudar pro Azure. E aí? Sai mudando todas as URLs na mão?</p>
<p>Lógico que não. Pra isso usamos helpers. Funções que escrevem a URL pra você. Em vez de escrever URL relativa ou absoluta pro CDN, use a linguagem de templates do seu framework e chame funções. Por exemplo, no Node Express, usando EJS, poderia escrever a tag de imagens assim como nesse exemplo. Essa função vai checar se está rodando na sua máquina local, daí escreve a URL relativa normal. E quando estiver em produção, fazemos uma configuração que indica pra função qual é a URL do CDN e ele reescreve usando URL absoluta.</p>
<p>Esse exemplo é usando express-cdn mas tem várias bibliotecas que fazem a mesma coisa. É importante que todo desenvolvedor de front saiba fazer isso pra depois não ter que ficar caçando URL hard-coded pra sair mudando em massa um dia antes do primeiro deploy. A vantagem de fazer isso é que a partir de agora, se feito direito, sua aplicação web devolve o HTML pro navegador. O navegador encontra essa tag de imagem apontando pro servidor de CDN e vai pedir pra lá. O servidor de CDN vai checar &ldquo;hum, eu tenho essa imagem? Não&rdquo;, daí ele vai pedir só uma vez pra sua aplicação web e vai cachear a imagem de resposta. Todo mundo que pedir a mesma imagem vai pegar do cache do CDN e não vai mais pesar na sua aplicação.</p>
<p>De novo, é outra coisa que remove uma quantidade grande de requisições feitas na sua infra e move tudo pra infra de CDN com outra vantagem: CDNs grandes costumam sincronizar seus dados em servidores de diversas regiões geográficas pelo mundo. Então, mesmo que você tenha feito deploy da sua aplicação num servidor do Brasil, se alguém do Japão acessar, pelo menos todas as imagens, css, arquivos de javascript, podem ser puxados de servidores de CDN do Japão, o que vai proporcionar uma experiência melhor pros usuários. No final todo mundo ganha e sua infraestrutura fica melhor balanceada.</p>
<p>Finalizando, estude estratégias de cache usando componentes como Memcache ou Redis. Estude jobs assíncronos usando serviços como AWS SQS ou Kafka. Estude como monitorar e conseguir métricas reais da sua aplicação em produção. E estude como integrar CDNs no seu front-end. São 4 das principais técnicas de otimização que toda aplicação web precisa. E isso porque eu pulei, mas vale mencionar, que saber fazer queries SQL otimizadas, instalar índices adequados, também melhora tudo uma ordem de grandeza. Só porque seu framework esconde o SQL não significa que você pode ignorar SQL. Um monte de problemas de performance são causados por SQL mau feito.</p>
<p>Mas por hoje vou acabar aqui. Eu só queria rapidamente mostrar alguns dos aspectos que separam uma aplicação web que funciona só na sua máquina, mas que não escala de verdade em produção, e como isso não tem nada a ver com sua escolha de linguagens, frameworks, mas sim do seu não-entendimento de como uma aplicação de verdade realmente funciona. Eu mostrei só o pico do iceberg e tenho certeza que muita gente experiente vai complementar o que falei nos comentários então se ficaram com dúvidas não deixem de mandar aqui embaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>performance</category><category>desenvolvimento-web</category><category>sistemas-distribuidos</category><category>akitando</category></item><item><title>[Akitando] #110 - Como fazer o Ingresso.com escalar? | Conceitos Intermediários de Web</title><link>https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/12/08/akitando-110-como-fazer-o-ingresso-com-escalar-conceitos-intermediarios-de-web/</guid><pubDate>Wed, 08 Dec 2021 11:33:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/0TMr8rsmU-k"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Por que sites como Ingresso.com baleiaram nas vendas da pré-estréia do Homem Aranha? Hoje finalmente vou responder com técnicas de verdade como se faz pra suportar sites de grandes volumes de acessos e transações.&lt;/p&gt;
&lt;p&gt;Vou aproveitar pra explicar rapidamente como cursos e tutoriais levam iniciantes a uma versão correta mas não-escalável, e vou explicar 3 grandes técnicas que sistemas de alta escalabilidade realmente usam no mundo real. Em particular vamos tentar imaginar qual seria a solução pra resolver o problema de escolha de assentos, que é o principal ofensor num site de venda de ingressos.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/0TMr8rsmU-k"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Por que sites como Ingresso.com baleiaram nas vendas da pré-estréia do Homem Aranha? Hoje finalmente vou responder com técnicas de verdade como se faz pra suportar sites de grandes volumes de acessos e transações.</p>
<p>Vou aproveitar pra explicar rapidamente como cursos e tutoriais levam iniciantes a uma versão correta mas não-escalável, e vou explicar 3 grandes técnicas que sistemas de alta escalabilidade realmente usam no mundo real. Em particular vamos tentar imaginar qual seria a solução pra resolver o problema de escolha de assentos, que é o principal ofensor num site de venda de ingressos.</p>
<p>DISCLAIMER: o autor não tem nada contra o Ingresso.com e este video não é uma tentativa de denegrí-los, meramente usar de exemplo educativo por ser um dos maiores de seu mercado.</p>
<h2>Conteúdo:<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Introdução - Homem Aranha</li>
<li>02:02 - Entendendo o ingresso.com</li>
<li>04:13 - POS de Cinemas - sistemas porcaria</li>
<li>05:21 - Final do Checkout</li>
<li>05:43 - Site feito por Iniciantes 1</li>
<li>08:18 - Minha definição de &ldquo;Iniciante&rdquo;</li>
<li>09:00 - Site feito por Iniciantes 2</li>
<li>09:58 - Raio X rápido de apps Web</li>
<li>12:20 - Tempo hipotético de uma Requisição</li>
<li>13:36 - DDoS do Aranha</li>
<li>17:45 - 1a técnica: CDNs</li>
<li>19:24 - 2a técnica: Caching</li>
<li>24:43 - 3a técnica: Jobs em Background</li>
<li>28:26 - memória compartilhada</li>
<li>30:41 - async/await vs jobs</li>
<li>33:51 - O Ofensor: Escolha de Assentos</li>
<li>34:57 - Sala de Espera</li>
<li>39:57 - Resumo da Solução</li>
<li>44:39 - Não faça o que eu falei!</li>
<li>45:19 - Otimização sem Métrica: Perda de tempo</li>
<li>46:37 - Otimização sem Testes: Tortura</li>
<li>47:35 - Conclusão</li>
</ul>
<h2>Links:<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Thread no Twitter: <a href="https://twitter.com/AkitaOnRails/status/1465390018961289217?s=20"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1465390018961289217?s=20</a></li>
<li>Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek: Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek | AWS Architecture Blog</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Alguns dias atrás eu fiquei putaço tentando comprar meus ingressos pro novo filme do Spiderman. Se você tá assistindo esse video muito no futuro, em 29 de Novembro de 2021 os sites de venda de ingresso abriram pra vender ingressos no mundo todo e todos os sites ficaram de joelho frente à demanda de gente querendo comprar. O filme vai estrear dia 16 de Dezembro e o hype ao redor dele é quase comparável ao que foi Avengers Endgame, é absurdo. Se o filme entregar metade do hype, já vai ser épico.</p>
<p>Agora, o que não foi épico, foi como os sites de vendas de ingresso se comportaram, especialmente nas primeiras horas. Se você tentou comprar naquele dia, sabe do que estou falando. Foi horroroso, foi tenebroso. Como sempre, sites de ingressos continuam sendo uma grande merda. Vamos colocar em contexto. Um site como o ingresso.com existe faz mais de década. Não lembro quando lançou de fato, mas o domínio foi registrado no ano 2000. Não é um site novo.</p>
<p>É possível que eu esteja sendo mais arrogante que o normal aqui por causa da experiência frustrante que foi ficar 1 hora tentando comprar sem sucesso, enquanto sala após sala ia ficando cheia e o site continuava me dizendo &ldquo;verifique sua conexão com a internet&rdquo;, tipo, mensagem de erro genérica do tempo de internet discada. Vai se fuder.</p>
<p>Então vou aproveitar o calor do assunto pra explicar porque eu acho que o site tem tantos problemas em eventos como esse, como um iniciante faria um site desses e como uma empresa desse tamanho e dessa idade já deveria estar em termos de arquitetura em 2021. Hoje o episódio vai ser bem mais prático mas ao mesmo tempo não vou mostrar código particular de nenhuma linguagem, vou explicar conceitos que servem pra todas as linguagens em projetos de verdade. Já aviso desde agora que o episódio de hoje vai ser super denso. Dava pra picotar esse video em meia dúzia, mas preferi falar tudo de uma só vez. Então preparem o bloco de notas e vamos lá.</p>
<p>(&hellip;)</p>
<p>Vamos começar do começo. Um site como um ingresso.com é basicamente um e-commerce. O produto do site são assentos em salas de cinema. O estoque é limitado, existem somente uma quantidade X de assentos pra cada filme, em cada sala, em um determinado horário. Uma vez que encheu a sala naquele horário, não tem mais o que vender. Não é muito diferente em conceito a um site de venda de assentos pra vôos, como um decolar.com por exemplo.</p>
<p>Se você é iniciante, provavelmente consegue delinear os principais elementos. Certamente começaria criando tabelas pra cadastrar cada rede de cinema, depois cadastrar cada sala em cada cidade. Daí em cada sala iria registrar cada assento com cada código independente. A sala provavelmente teria uma imagem com o mapa dos assentos. Depois faria o cadastro de cada novo filme, filmes ativos agora, filmes em pré-estréia, daí associar filmes com salas. A página principal é só uma listagem desses filmes, com categorias ou tags como &ldquo;Em Alta&rdquo;, &ldquo;Em Cartaz&rdquo;, &ldquo;Em Breve&rdquo;. Coisas secundárias seriam festivais, promoções e coisas assim que iriam pra outras tabelas, mas de qualquer forma a página principal ia puxar tudo isso dessas tabelas.</p>
<p>Daí quando escolhe um filme, cai na página separada desse filme. Ele vai puxar as salas associadas com esse filme, as datas em que cada sala vai ter o filme disponível. Você deixa o usuário escolher o tipo de sala que quer como IMAX ou VIP, qual dia que quer assistir, e embaixo vai filtrando a lista de salas. Cada sala tem uma tabela que associa o filme e os diferentes horários disponíveis nesse dia. Aliás, não tente levar ao pé da letra o que estou falando, só estou listando de cabeça. Se você procurar 5 minutos no Google provavelmente vai achar vários tutoriais dando exemplo de como criar tabelas pra um site de ingressos desse tipo.</p>
<p>Agora vem a parte chata. Uma vez escolhido o horário, quando clica em comprar, ele entra num processo de checkout dividido em 3 partes. A primeira parte é finalmente escolher o produto: o assento exato na sala, horário e dia escolhido. Pra fazer isso eu chuto que o site precisa sair da infraestrutura dele pra acessar um sistema de terceiros que realmente gerencia os assentos da sala, que imagino que é proprietário de cada rede de cinema. Porque precisa ser o mesmo sistema que é usado na frente de caixa quando alguém vai comprar o ingresso pessoalmente direto no cinema.</p>
<p>Se não for o mesmo sistema, o site poderia acabar vendendo um assento online que já tinha sido comprado direto no cinema por outra pessoa e aí dá um monte de problemas. Portanto, temos que assumir que sites como o ingresso.com precisam integrar com esses sistemas, que provavelmente devem ter tecnologia da idade das pedras, lentos pra caramba, totalmente instáveis e mau feitos, relíquias do começo dos anos 2000 ainda usando tecnologias dos anos 90. Pra simplificar imagine que o ingresso.com tem um cadastro de endereços IP de um servidor de cinema, com uma API com funções simples como listar assentos num horário e função pra reservar um assento temporariamente e outro pra reservar um assento permanentemente.</p>
<p>Precisa ter uma reserva temporária, que parece que é de uns 2 minutos, porque online o assento ainda não foi pago. Então você segura a escolha do assento até confirmar o pagamento nas etapas seguintes. Uma vez que o usuário escolheu o assento, agora ele precisa escolher se é ingresso inteira, se é meia - que é outra relíquia que não faz sentido existir. Enfim, daí fazer upselling de coisas como pipoca, refrigerante e o que mais der pra enfiar goela abaixo praquela compra de impulso de última hora.</p>
<p>Finalizado essa segunda etapa, finalmente chegamos na hora de pagar. Agora é escolher o tipo de pagamento que pode ser cartão de crédito direto, ou integrando com meios de pagamento como Google Pay, Apple Pay e assim por diante. Confirmado o pagamento, o site envia a confirmação pra reservar o assento permanentemente, envia o ingresso digital pro e-mail do usuário e todo mundo fica feliz.</p>
<p>Um site feito por um iniciante, sozinho ou seguindo algum tutorial ou curso, teria um banco de dados como um MySQL da vida, com tabelas como eu falei antes: tabela de salas com informações como a cidade, estado, endereço, tipo de sala. Daí uma tabela de filmes, com informações como data de estréia, data de pré-estréia, caminho pras imagens como cartaz. Agora teria uma tabela many-to-many que associa a sala com o filme, chamada horário, por exemplo. E assim por diante.</p>
<p>Daí teria um tabela chamada carrinho ou shopping cart, que associa um usuário com uma escolha de sala, filme, horário e o que voltar da API do cinema com informações do assento, items de upselling como pipoca, tipo de pagamento e um estado que indica se ainda está pendente de pagamento, aguardando confirmação e confirmado ou rejeitado.</p>
<p>O iniciante usaria um framework web comum como Django de Python, Laravel de PHP, Rails de Ruby, Express de Javascript ou algo maior como Spring de Java. Na prática, não importa qual você escolher. Os problemas que eu sofri dia 29 de Novembro tentando comprar Homem Aranha não teriam sido mitigados pela escolha de framework nem de linguagem, então literalmente tanto faz qual você escolher. Eu vou explicar porque já já.</p>
<p>Independente do framework, você começaria criando controllers e views, ou páginas como homepage, página de cada filme, página pra escolha de assento, página pra escolha de pipoca e refrigente, página pra escolher tipo de pagamento, página de confirmação pro ingresso. E claro, tô de propósito pulando um monte de outros itens como página de cadastro, login, recuperar senha, mas a essa altura qualquer framework decente já tem dezenas de bibliotecas pra fazer esses itens em questão de minutos. Se você leva dias pra fazer míseras páginas de cadastro de usuário e login, você é um enorme incompetente que precisa aprender a usar o Google. E eu não tô exagerando. Toda vez que eu vejo um projeto que a página de recuperar senha não funciona, eu já sei que foi feito por um completo amador.</p>
<p>De qualquer forma, o projeto seria feito num frameworks desses, todos os elementos num mesmo projeto, conectando num mesmo banco de dados único. Os elementos de deployment seriam um servidor web como NGINX, o banco de dados MySQL ou Postgres da vida, um diretório com o código fonte do projeto, seja em Javascript, Python ou PHP da vida. Normalmente instalado tudo junto num único servidor e pronto. Acabou. Se você se considera um iniciante em programação web, deve ser capaz de fazer uma versão miniatura de um ingresso.com em questão de poucos dias e todos vão chegar nesse mesmo tipo de projeto. Se tem um curso ou tutorial que implementa um site de venda de ingressos, eles vão parar por aqui também.</p>
<p>Aqui eu faço uma tangente. Eu vi alguns gatos pingados reclamando do meu video de Conhecimento Básico pra Iniciantes dizendo que o que eu listei naquele video é coisa demais pra um iniciante. E eles tem razão. Todo mundo parece que considera iniciante como uma pessoa que não sabe nada e vai começar do zero. Não tá errado, mas eu considero &ldquo;iniciante&rdquo;, todo mundo que já tem conhecimento suficiente pra entrar no mercado de trabalho, ou seja, sabe o mínimo pra não ser um mala sem alça, um peso morto. Sempre que eu falar iniciante é isso: alguém que já minimamente sabe programar. E sim, pra mim um iniciante deve ser capaz de codificar o que eu acabei de explicar aqui em aproximadamente 1 ou 2 semanas. Não mais que isso.</p>
<p>Um iniciante que não consegue fazer uma versão miniatura do ingresso.com ainda não é iniciante, é um hobista, ou um estudante. A versão que todos vocês vão conseguir fazer vai funcionar pra ir pro ar, suportar algumas dezenas ou com sorte, até centenas de usuários. Mas nunca, jamais, nem de perto conseguiria suportar a pré-estréia do Homem Aranha ou um evento nível Avengers Endgame. Jamais. Eu já disse isso várias vezes antes e vou repetir: a grande maioria dos cursos é feita pra ser simples, didática, e fazer você achar que aprendeu algo útil. Mas eles não ensinam o que vou explicar a partir de agora, simplesmente porque a maioria das pessoas que escrevem esses tutoriais nunca trabalharam em sistemas grandes. E isso faz uma diferença enorme.</p>
<p>Mas não tem nenhum problema. Ninguém em sã consciência usaria o código de um iniciante pra um evento desses. Ou pelo menos não deveria. Agora é hora de eu explicar qual é a diferença de um e-commerce feito por um iniciante e um feito por um sênior com experiência de verdade. Vamos começar do começo.</p>
<p>Tudo que um usuário fizer no seu site, listar filmes, procurar salas, comprar pipoca, serão requisições HTTP pra um servidor web, como um NGINX. Esse servidor vai pegar a URL e mandar pra sua aplicação feita em Django ou Express da vida, via proxy reverso. Se você não sabe o que é NGINX ou proxy reverso, vai colocando no seu bloquinho de anotações pra estudar depois. Todo framework vai pegar essa URL, as informações que vieram no cabeçalho HTTP como cookies, a informação de sessão pra saber se é um usuário logado ou um não-logado, passar pra algum tipo de processamento de rota e enviar as informações da requisição pra uma classe de controlador, pra chamar uma função como <code>index</code> de uma classe FilmeController, por exemplo, que vai listar todos os filmes.</p>
<p>Esse controlador vai precisar da ajuda de outras classes como repositórios ou modelos, que no fim mapeiam pras tabelas no seu banco de dados. Talvez tenha um modelo chamado Filme, que vai fazer uma pesquisa SQL no banco de dados MySQL. Agora o MySQL precisa processar a pesquisa, e enquanto faz isso, desde a ponta do navegador do usuário, passando pelo NGINX, passando pelo processo do seu framework, tá tudo bloqueado, esperando o MySQL retornar. Isso é muito importante de entender, que tudo fica pendurado esperando o próximo passo terminar.</p>
<p>Quando o MySQL retornar o resultado da pesquisa, agora o modelo vai processar essa informação e transformar numa lista de objetos, e entenda que isso mais que duplica a memória do resultado porque você tem a informação crua que voltou do banco e a transformação disso em objetos, que ocupa mais que o dobro de memória por conter diversos metadados como conversão de dados e tudo mais. Agora essa lista de objetos passa pra processamento de alguma classe de visualização que vai pegar esses objetos, um template de Django, um template Blade de Laravel, enfim um template de HTML que tem espaços em branco pra serem preenchidos pelos objetos que voltaram do MySQL.</p>
<p>Quando o HTML final for gerado, agora o framework devolve isso pro NGINX, que vai devolver pro navegador do usuário que estava esperando até agora. E finalmente o navegador desenha a página na tela pro usuário poder continuar. Do momento que o usuário digitou a URL até receber a página de volta, digamos que demorou 100 milissegundos, que não é muito ruim. Parte desses milissegundos depende mesmo da conexão de internet do usuário que é o caminho do navegador até o servidor. Digamos que foi 10 milissegundos.</p>
<p>Uma vez no servidor, daí tem o tempo do NGINX processar e mandar pra sua aplicação, e isso costuma ser muito rápido, digamos 5 milissegundos. Agora o framework precisa processar a sessão, a rota, carregar o controlador e tudo mais, digamos que isso leve 20 milissegundos. Daí chega na parte que conecta no servidor MySQL e precisa esperar o SQL processar a pesquisa, e isso vai ser mais uns 30 milissegundos. Finalmente, com os dados e o template em mãos, precisa gerar o HTML final, e digamos que isso leve 25 milissegundos. E com isso, os 10 milissegundos restantes é pra devolver o HTML pro navegador.</p>
<p>No geral, a gente fala que a escolha do framework não importa tanto porque dos 100 milissegundos totais nessa requisição de exemplo, você só vai impactar 2/3 do tempo se trocar. O resto do tempo é rede e conexão com serviços externos como o MySQL. 1/3 do tempo foi no banco de dados e isso não vai mudar só escolhendo um framework diferente. Ah sim, daí o iniciante vai logo mandar um &ldquo;ah, é só trocar o MySQL por MongoDB&rdquo;. Não sua anta. Não faça isso. Agora você escolheu girar um parafuso com um martelo. Num parafuso pequeno parece que funciona porque você bate com o martelo e na força bruta ele entra na madeira, mas isso Não vai continuar funcionando assim por muito tempo. Vou mencionar NoSQL mais pra frente.</p>
<p>Agora, imagine a situação do evento do Homem Aranha nesse site de iniciante. Em questão de segundos seu site estaria efetivamente sofrendo o equivalente a um ataque de Denial of Service. Imagine o seguinte cenário: deu meio dia, todo mundo do Brasil inteiro digita <code>ingresso.com</code> nos navegadores pra abrir a homepage do site. Pronto, ninguém vai conseguir. Seu servidor não vai aguentar e vai abrir o bico.</p>
<p>Eu estou usando números hipotéticos neste episódio inteiro porque dependendo de como você programar, em que tipo de servidor instalar e tudo mais, os números variam. Não existe um número absoluto igual pra todo mundo. Mas digamos que, no mínimo, a requisição pra montar a homepage leve esses 100 milissegundos, como expliquei. Agora digamos que você instalou num servidor baratinho de 8 cores que conseguiria processar 600 requisições por segundo. É até bastante coisa. Mas na realidade é menos que isso, porque seu servidor de banco de dados tem um limite de umas 100 conexões de cada vezes. Então vai ser menos de 600 requisições por segundo, porque algumas requisições precisam esperar outras terminarem de pesquisar no banco antes de pode prosseguir, então digamos que isso vai cair pra uns 400 por segundo. Vocês tem que ter em mente que tudo tem limites, velocidade teórica não é a mesma coisa que velocidade na prática.</p>
<p>Mas pra atingir esse pico, precisa de menos de 400 usuários por segundo. Especialmente quando o mesmo usuário tentar abrir vários navegadores e ficar apertando F5 o tempo todo pra carregar. O que acontece no servidor? Até chegar no NGINX não costuma ter problema, o NGINX não precisa processar quase nada, é muito rápido e consegue aguentar milhares de requisições por segundo nessa máquina. O problema é quando atingir sua aplicação.</p>
<p>Sua aplicação talvez consiga ir um pouco acima de 400 requisições por segundo, mas o banco de dados não consegue, não só pelo limite de conexões, mas porque pesquisas custam tempo de processamento. Toda requisição que não conseguir ser atendida porque a CPU já tá em 100% vai começar a entrar numa fila do NGINX. Mas a fila também não é infinita. E como tá tudo na mesma máquina, chega uma hora que o NGINX, sua aplicação e o banco de dados vão estar brigando por tempo de CPU e por memória, que já tá no máximo, e todos vão começar a ficar lentos até chegar num ponto onde o gerenciador de memória do sistema operacional vai chegar num pico. A memória vai acabar e um deles vai crashear. E quando o banco ou sua aplicação crashear, daí toda requisição que tava na fila vai receber uma mensagem de erro 500 de volta.</p>
<p>Daí sua aplicação reinicia, mas aí todo mundo que tava na fila esperando, recebeu mensagem de erro, vai todo mundo tentar dar reload no navegador, em segundos sua fila vai encher de novo, sua aplicação vai crashear de novo, e vai ficar nesse loop infinito de crash até que uma boa parte das pessoas comece a desistir e vai diminuindo a quantidade de requisições até o limite onde o servidor começa a conseguir responder pra algumas pessoas. Entenda, as pessoas vão fazer uma de duas coisas: ou esperar até conseguir comprar ou esperar até a frustração não valer mais a pena. Do jeito que sua aplicação foi feita, você vai forçar a grande maioria das pessoas a desistir por frustração.</p>
<p>De novo, não tem nada de errado nisso. É nível iniciante, ninguém espera usar sua aplicação pra um evento desses. Mas é um aviso pra quem contrata juniors pra fazer aplicação nível avançado mas não entende a diferença e acha que pode pagar barato pra ter o mesmo resultado. E não, a culpa não é do iniciante. Um hospital não colocaria um residente pra fazer um transplante de coração. Se colocar e o paciente morrer, a culpa não é do residente, é do hospital que obrigou ele a fazer um procedimento que não era qualificado pra fazer. Mas o ingresso.com de verdade pelo menos não tá no nível totalmente iniciante. Quando eu tava tentando comprar, um comportamento era bem óbvio: o site não baleiava na homepage. Se você tentou comprar naquele dia também, vai lembrar que a página principal e a página do filme carregavam até que rápido, sem nenhum problema.</p>
<p>Existem algumas razões pra isso. Você poderia ter uma aplicação web separada pra páginas de conteúdo e outra aplicação só pra fase de checkout, que são as 3 etapas que eu falei de escolher assento, escolher tipo de ingresso e fazer o pagamento. Sendo aplicações separadas, você tem mais oportunidade de escalar a aplicação de conteúdo que é mais leve e tomar porrada numa infra separada pro checkout.</p>
<p>Mas eu acho que a solução é mais simples do que isso. Pra começar, ele não roda a aplicação web junto com o NGINX e junto com o banco de dados, no mínimo cada um deles está em servidores físicos separados. Em segundo lugar, provavelmente usa CDNs pra cachear assets e conteúdo estático. Veja o código fonte da página inicial do ingresso.com. Todo lugar que precisa carregar coisas como stylesheets de CSS, scripts javascript, imagens e tudo mais, ele não puxa do domínio ingresso.com e sim do domínio ingresso-a.akamaihd.net. A Akamai, assim como uma AWS Cloudfront, Google Cloud CDN ou Azure CDN, é um Content Delivery Network.</p>
<p>Serviços de CDN tem servidores espelhos espalhados por diversas regiões no mundo. Quando você faz deploy da sua aplicação, também faz upload de todos os assets da sua aplicação pro CDN. Daí o CDN vai começar a espelhar, ou seja, copiar esses assets, ou arquivos, pra dezenas de outros servidores no mundo todo. Quando o usuário tenta abrir a homepage, uma das coisas mais pesadas é baixar todos esses arquivos javascript, imagens, CSS, mas ele não vai bater no seu servidor de aplicação, vai bater nos servidores de CDN mais próximos.</p>
<p>Só de fazer isso, uma porção considerável do peso do seu site é deslocado pra outros servidores e não vai pesar na sua aplicação. Todo framework web que se preza tem suporte a CDNs, de tal forma que na sua máquina de desenvolvimento ele vai puxar javascript, CSS e imagens direto da sua máquina e só em produção vai automaticamente fazer upload do que precisa e converter os templates de HTML pra escrever o domínio do servidor de CDN, como o ingresso.com faz com a Akamai.</p>
<p>Mas não é só isso. Se o ingresso.com estivesse feito da forma ingênua como expliquei, ela não ia conseguir abrir a homepage mesmo com CDNs. Um dos problemas é ter que ir no banco de dados. Toda vez que um processo web faz isso, ela fica bloqueada, esperando o serviço externo retornar. Vai ficando um monte de processos esperando, e se tiver muitos, uma hora vai estourar. Normalmente costuma ter uma configuração de timeout. Ou seja, a aplicação web espera o MySQL retornar ou, se chegar num tempo limite, pára de esperar, corta a conexão e devolve erro 500 pro usuário.</p>
<p>Eu falei que no nosso exemplo, o MySQL demora uns 30 milissegundos pra devolver. Eu posso colocar um timeout de meio segundo. Mas aí aquele monte de gente conectando tentar abrir trocentas conexões pro banco, e todo mundo pendurado por pelo menos meio segundo, enquanto o MySQL tá sofrendo pra conseguir devolver uma resposta, porque não tem CPU sobrando na máquina pra processar tanto SQL. E o banco tem limite de conexões. O NGINX tem limite na fila. Ambos tem timeouts, e vai ser uma cascata de timeout com um tráfego do nível da audiência do Homem Aranha. A grande maioria vai tomar erro 500, que no caso do ingresso.com ele devolve aquela mensagem tosca de “verifique sua conexão com a internet”, que não tem nada a ver.</p>
<p>Existem várias coisas que podem mitigar isso, por exemplo, garantir que suas tabelas tenham índices corretos pra cada tipo de pesquisa. Índices são como tabelas extras pré-compiladas. Quanto mais índices você botar numa tabela, mais rápido vai ser determinadas pesquisas, mas mais lento vai ser pra inserir ou excluir uma linha nessa tabela, porque precisa também atualizar os índices. Então você corta tempo de pesquisa pra aumentar tempo de inserção. Mas como num site como esse você tem um volume muito maior de pesquisas do que de inserções, sempre é uma boa prática colocar índices corretos. A única pessoa que sentiria alguma lentidão extra, se existir, seria o administrador pra cadastrar novos filmes e novas salas, e isso é uma atividade que ele precisa fazer só uma vez por semana, se muito. Mas só adicionar índices vai ajudar mas não vai resolver nosso problema.</p>
<p>A homepage, durante um dia inteiro, é igual o tempo todo, pra todo mundo. Se você carregar a página ao meio dia, que é quando o filme do Homem Aranha ficou ativo, até a meia noite do mesmo dia, eu tenho certeza que nada mudou. Aliás, se a página é sempre igual mesmo se for num período super curto de 5 minutos, tá excelente, porque se você tá tendo 10.000 requisições por minuto, quer dizer que por 5 minutos você só precisa gastar o tempo de processar o HTML da homepage uma vez e servir a mesma página pra todas as 50 mil requisições durante esses 5 minutos. É uma economia monumental.</p>
<p>Isso é o comum na grande maioria de sites de conteúdo, incluindo e-commerces, onde é raro produtos mudarem com muita frequência. O que pode mudar são informações secundárias, como disponibilidade ou estoque do produto. Por exemplo, a página do filme talvez tenha a mesma listagem de salas, mas à medida que as salas forem ficando cheias, precisa desabilitar da tela a opção de conseguir escolher assentos nela. Mas a listagem das salas em si é a mesma. Só se um administrador esqueceu de adicionar uma sala, e cadastra de última hora, que vai mudar alguma coisa. Mas no geral, a página é a mesma.</p>
<p>E de novo, todo framework web que se preza suporta configuração de cache. Eu posso simplesmente dizer que a página de índice do FilmeController, depois que gerar o HTML da primeira vez, na próxima requisição em diante, por 5 minutos, não processa mais nada. Por um período de 5 minutos, ele vai devolver o HTML direto de um cache. Um cache seria basicamente o HTML que foi gerado gravado como um arquivo estático no sistema de arquivos ou guardado em memória mesmo.</p>
<p>Lembram dos tempos que eu falei? A requisição total levou 100 millissegundos no nosso exemplo. 10 millissegundos na entrada e 10 millissegundos na saída foram só o tempo de internet mesmo. 5 milissegundos foi o NGINX. Todo o resto foi a aplicação web conectando no banco, esperando processar a pesquisa, montando o HTML final com os templates. Se cachear o HTML final significa que podemos eliminar esse tempo todo. Vamos cortar os 30 milissegundos da pesquisa no banco e 25 segundos pra montar o HTML. Digamos que achar o cache e devolver ele leve só 10 millissegundos.</p>
<p>Então dos 100 milissegundos originais, os próximos usuário vão esperar só 35 milissegundos pra receber a página. Ou seja, sem mudar framework, sem mudar linguagem, sem mudar banco, dá pra reduzir quase 60% do tempo original. Por isso eu falo que todo mundo que sugere mudar de linguagem ou banco como primeira opção é certamente um iniciante. Porque você consegue diminuir bastante o tempo de resposta só sabendo usar um recurso que seu framework provavelmente já tem e você que não sabe como usar, que é habilitar e configurar o cache.</p>
<p>Gerenciar cache é uma técnica média, nem iniciante e nem avançado. Se você já tentou e ficou preocupado em apagar cache antigo, tá fazendo errado. Cache tem time to live ou TTL, você diz pra durar 5 min, durante esse período devolve o que tá cacheado e quando expirar, daí passa pelos controladores, modelos, views e tudo mais de novo pra renderizar o novo HTML e cachear de novo. E cache é quem faz a maior diferença numa aplicação web. Você pode cachear uma página inteira, só componentes dentro dessa página. Vale a pena aprender os detalhes das diferentes estratégias de cache como entender o que é o status 304 Not Modified do protocolo HTTP cuja estratégia implementada nos frameworks se chama Conditional HTTP Caching.</p>
<p>Vou pular da página principal pra última ponta do checkout que é o pagamento, ainda tem outra coisa que é importante explicar antes de continuar. Na ponta de pagamento tem gente que faz errado e atrela a página de confirmação de pagamento com o pagamento em si. Isso é comum quando você não usa um serviço de pagamento como Stripe, PagSeguro, Paypal ou semelhante, ou seja, quando você precisa integrar manualmente com um integrador de cartão de crédito, por exemplo. Acho que hoje em dia isso não é mais tão comum porque um MercadoPago da vida é bem mais simples de integrar, por outro lado costumam cobrar taxas maiores, daí depende do seu modelo de negócios.</p>
<p>O modelo de pedido ou ordem de compra costuma ser implementado como uma máquina de estado. Ele inicia com um estado como “pendente de pagamento”, daí tem o pagamento em si que transiciona o modelo pra “pendente de confirmação”, daí espera o integrador de pagamento responder, e transiciona pra “confirmado e pendende de entrega” - caso seja um e-commerce de produtos - ou então transiciona pra “rejeitado”, e registra o motivo dado como número de cartão inválido. Dali pode transicionar de volta pra pendente de pagamento, por exemplo. Se você nunca ouviu falar de máquinas de estado, estude sobre isso. É conhecimento básico.</p>
<p>De qualquer forma, se você fizer do jeito ingênuo, vai fazer um controlador ou uma página que recebe os dados de cartão de crédito, daí vai chamar a API do serviço de forma síncrona, ficar esperando a resposta - que você não tem controle porque é um serviço externo. E aí corre o risco dele demorar, dar timeout e estourar o tempo do servidor web, e ter que devolver um erro 500 e ficar com o pedido num estado inconsistente, com ainda pendente, sem saber se o serviço de pagamento foi até o fim ou não. Daí normalmente vai acabar contratando um monte de gente de suporte pra ficar conciliando os pedidos manualmente.</p>
<p>A mesma coisa vale pra atividades pesadas num banco de dados, onde você manda um SQL pesado, fica esperando, dá timeout e não sabe se foi até o fim ou não. Quando você tá sozinho na sua máquina, codando, isso não faz diferença, porque você tem poucos dados, só tem você acessando, então raramente vai cair numa situação que dá timeout e quebra seu fluxo. Mas em produção é o que mais vai ter: com dezenas de usuários simultâneos todos concorrendo pra usar um recurso escasso que são serviços externos como banco de dados ou pagamentos ou qualquer outro serviço de terceiros.</p>
<p>Por isso você deve entender máquinas de estado e entender o que é processamento assíncrono. Alguns devem pensar, “ah eu sei o que é isso, eu sou de Javascript ou C# ou Go ou alguma linguagem que suporta o pattern de async/await”. É só chamar a função demorada com async, daí o processo fica livre pra fazer outra coisa, e quando o serviço retornar, vai voltar no await e continua dali. Quem é de Go vai falar a mesma coisa, só soltar um gofunc. Quem for de Elixir vai falar, ah só soltar um Task async. E assim por diante.</p>
<p>E tá errado. A menos que seja um processamento que se você perder não faça diferença, não faça desse jeito pra tudo. Você precisa ter uma solução separada de controle de fila. Num serviço de fila você vai fazer o seguinte: vai jogar numa tabela no banco chamado Fila ou Jobs, o próximo trabalho, ou Job como chamamos. E vai deixar um outro processo separado, que chamamos de Worker, puxar esse próximo Job da fila e processar separado.</p>
<p>Num framework como Rails temos embutido essa funcionalidade num ActiveJob. Num Node.js temos bibliotecas como o Bull que faz a mesma coisa. Num Django temos o Celery. Em Spring Boot de Java podemos usar o Rqueue. O Laravel já vem com um serviço chamado Queues. Hoje em dia a arquitetura básica é mais ou menos a mesma. Eu falei colocar o job numa tabela no banco, mas na realidade a maioria grava o job no Redis. Se você ainda não estudou sobre Redis, deveria, é o único NoSQL que de fato a maioria dos projetos acaba usando na prática.</p>
<p>Eu falei rapidamente de Redis no episódio sobre NoSQL. Mas pense num Redis como um banco de dados em memória que tem a opção de persistir os dados em storage. Dessa forma se ele crashear, não perde os dados como um Memcached. Memcached e Redis funcionam mais ou menos como uma memória compartilhada. Se você é iniciante esse conceito pode parecer estranho, mas isso é porque você tá acostumado a rodar tudo na mesma máquina, daí não enxerga a necessidade de um serviço pra memória compartilhada.</p>
<p>Mas pense agora em produção, onde sua aplicação web pode estar rodando em vários servidores em paralelo, embaixo de um load balancer como o próprio NGINX. O usuário começa a requisição batendo no servidor A, mas à medida que vai navegando, vai bater no servidor C, depois no E, daí volta pro A. E tem que ser tudo transparente, o estado que ele tinha no servidor A tem que ser o mesmo no servidor C. Parte disso é resolvido porque a aplicação vai baixar dados do mesmo banco de dados. Mas parte é porque vai ter coisas num Memcached ou Redis, uma memória compartilhada que todos esses servidores acessam ao mesmo tempo, em particular nossa fila de Jobs.</p>
<p>O Redis ainda tem outra funcionalidade, que chamamos de PubSub ou publisher e subscriber. Quando cadastramos o tal Job no Redis, estamos publicando. E do outro lado pode ter processos em background, os nossos workers que mencionei antes, assinando ou subscrevendo um canal. Quando um novo job é publicado o worker que tá assinando é notificado, daí puxa o job e começa a processar em background.</p>
<p>Então o fluxo é assim: você tem uma operação custosa pra fazer, onde seu processo vai ter que cruzar os braços e ficar esperando e arriscando bater timeout e estourar. Então ele publica o Job numa fila, como num Redis, e vai fazer outra coisa. Daí um processo separado, provavelmente em outro servidor separado, vai puxar esse Job e processar, e nesse worker ele pode levar muito mais tempo porque não tem uma página web ou um usuário esperando pendurado. Mesmo se o worker crashear ou der timeout por algum motivo, você tem o registro do Job na fila que é persistida e pode mandar outro worker reiniciar o Job. A maioria dos sistemas de fila dos frameworks tem telas de admin pra você poder manualmente monitorar as diversas filas. Assim mesmo se tudo crashear, você não perde a informação na fila.</p>
<p>E porque eu não posso só soltar um async/await, que vai ser mais ou menos a mesma coisa só que sem a complexidade de um Redis e workers separados? Simples: porque um servidor web tem que estar preparado pra morrer a qualquer momento. Esse é outro conceito que você precisa ter em mente: servidores web devem poder ser derrubados e reiniciados a qualquer momento. Você não deve gravar arquivos localmente num servidor web e nem confiar em deixar coisas importantes na memória RAM. Esse é o conceito de “shared nothing” ou nada compartilhado, que você precisa aprender o quanto antes. A maioria dos problemas de escalabilidade acontecem quando você não sabe disso.</p>
<p>Se precisa gravar arquivos, você vai jogar num serviço externo, como um AWS S3. Que qualquer um dos seus servidores vai conseguir acessar. No mínimo vai gravar num diretório compartilhado na rede. E se precisar deixar coisas em memória, não vai deixar na RAM, vai deixar ou num serviço separado de Memcached ou Redis ou direto no banco de dados, que vai ser bem mais lento. Mas o conceito é que se o servidor web crashear, quando voltar e o usuário bater nele, vai carregar tudo normalmente, sem corromper nada.</p>
<p>Se você soltar dezenas de async nesse servidor e ele crashear, perdeu esses asyncs todos e nunca vai voltar o await. Agora os dados podem ter ficado num estado corrompido. O pedido ficou em estado de pendente e você não tem como saber quem foi confirmado, quem foi rejeitado, e aí como que vai fazer agora? Por isso você mantem jobs como de pagamentos numa fila como no Redis. Mesmo se seu servidor web crashear, os workers não tão nem aí. Eles vão continuar processando e atualizando o estado dos pedidos normalmente. Quando o servidor web reiniciar, ele checa o estado atual do pedido e vai estar correto, porque a fila manteve as informações consistentes.</p>
<p>O que eu acabei de falar deve ter deixado pelo menos metade de vocês meio confusos. Lembram o que eu falei mais no começo? A maioria dos cursos e tutoriais que você acha por aí vai fazer uma aplicação web num framework qualquer, daí vai ensinar e instalar tudo junto num servidor como num Linode ou DigitalOcean da vida. E na sua cabeça tudo fica de pé 100% do tempo, e crashear é uma coisa rara. Quando só tem um usuário testando, você raramente vai enxergar gargalos e falta de recursos, por isso que “na sua máquina tudo funciona”.</p>
<p>Mas na verdade em produção de alta escala de verdade, servidores como de banco de dados, Redis e outros serviços tendem a ser mais parrudos e estáveis, e seus containers de web tem que ser voláteis e permitir crashear a qualquer momento. Novos containers web podem aparecer se precisar escalar pra aguentar mais gente. E quando o tráfego for caindo, eu preciso poder derrubar containers web a qualquer momento pra diminuir os custos. Isso que chamamos de servidores elásticos ou Elastic Compute Cloud como a Amazon costuma chamar seus EC2.</p>
<p>Entendam esse conceito: você deve programar sua aplicação web pra poder ser derrubado a qualquer momento. Por isso tudo que depende de estado local, seja em disco, seja na memória, como async/await, deve ser evitado, ou usado muito pouco em situações específicas e nunca em qualquer lugar sem pensar nas consequências. Você nunca deve sair por aí criando centenas de asyncs locais sem controle nenhum. Sempre pense no que pode acontecer num crash e como você recupera o estado das coisas.</p>
<p>Mas agora chegamos ao principal ofensor do ingresso.com, a escolha de assentos que precisa conectar com o tal serviço bosta proprietário de cada cinema. Esse serviço não temos nenhum controle e podemos assumir que é um grande lixo. Digamos que, se muito, ele aguenta uns 10 usuários simultâneos tentando escolher assentos. Se tiver 1000 usuário de uma vez, 990 vão ficar na fila, vai dar timeout porque escolher assentos pode levar de vários segundos a minutos, e todo mundo vai ficar dando reload na página e isso só vai estendendo a fila até ninguém conseguir fazer nada a não ser desistir e tentar mais tarde.</p>
<p>Como você faria pra resolver isso? E aqui, de novo, não adianta vir com solução fácil como trocar de framework ou trocar de linguagem. É independente, porque o serviço externo não tem como reescrever, e você não tem controle. Quando eu tweetei a solução, falei o seguinte: o serviço externo (estou assumindo) de escolher assento deve ser o pior ofensor e não dá pra escalar. Pra isso deveria haver uma fila, onde cada um que entra por ordem de chegada ganha um ID e espera. Daí vai liberando em batches pra não engargalar a escolha de assentos.</p>
<p>Depois que fiz esse tweet alguém me mandou uma DM com um artigo do blog da AWS. Tem gente que fica me perguntando onde procurar novidades, e assinar os diversos blogs das principais plataformas como blog do Google, da Amazon ou da Microsoft. A tradução do título é &ldquo;Construindo uma Sala de Espera Virtual com Amazon DynamoDB e AWS Lambda na SeatGeek&rdquo;. Eu imagino que SeatGeek seja tipo um ingresso.com lá dos Estados Unidos. E você não precisa usar serviços da AWS pra fazer uma Sala de Espera ou Waiting Room, mas com certeza facilita. Como o blog é da Amazon, obviamente ele vai sugerir usar tudo da AWS, mas entenda que isso é só uma das opções.</p>
<p>O conceito é simples: ninguém entra direto na página de escolha de assentos, que é a parte mais lenta, o pior gargalo, e você não tem controle nem como deixar mais rápido. Então todo mundo entra primeiro numa página de Sala de Espera. Assim como quando você vai nos Correios ou no Cartório, você ganha um código. Quando seu código for chamado, daí você entra na Zona Protegida, que é a escolha de assentos.</p>
<p>A situação de gargalo é quando não tem essa senha. Imagina lá no cartório você chega e fica toda hora perguntando pro atendente, “é minha vez? É minha vez?” Daí o atendente nem consegue terminar a pessoa que já tá atendendo e vai ficando mais lento à medida que chega mais gente. Por isso eles usam o sistema de senhas. Aqui eu vou resumir super resumido pra não complicar o conceito, mas lembre-se que não importa que linguagem ou framework estiver usando. Dá pra implementar em qualquer uma com resultados idênticos. O fluxo é mais ou menos assim:</p>
<p>Primeiro você tem um serviço que gera tokens de acesso únicos, a tal senha de espera. Cada usuário que chega na Sala de Espera ganha um token único associado à sua sessão web. Mesmo se o usuário perder a paciência ficar dando reload na página, vai dar reload numa página cacheada da Sala de Espera, e o token continua sendo o mesmo. Isso evita jogar tráfego desnecessário na página lenta de escolha de assentos. A página de espera em si você coloca um contador em Javascript e usa técnicas como já falei acima de cache, CDN e tudo mais, então você consegue controlar a carga nela e escalar horizontalmente se precisar.</p>
<p>A quantidade de tokens disponíveis vai ser o mesmo número de assentos da sala. Se acabar os tokens, você já de cara diz pros próximos usuários que acabou os lugares nessa sala e manda ele escolher outra sala, que tenha tokens ainda disponíveis. Aqui você vai refinando esse número ao longo do tempo, tipo, já sabe que 10% dos usuários desiste na sala de espera, então da próxima vez distribui 10% a mais de tokens e faz um overbooking, que é o que companhias aéreas fazem também, de reservar mais assentos do que tem no avião porque eles sabem que uma certa porcentagem sempre desiste em cima da hora.</p>
<p>A página do usuário nesse ponto vai ser um timer ou contador que fica falando &ldquo;daqui X minutos mais ou menos, você vai ser atendido&rdquo;. Isso é uma estimativa que você consegue fazer baseado no tempo médio que um usuário costuma levar pra escolher lugares e quantidade de gente na sala de espera. Não é difícil.</p>
<p>A amarração de usuário e token pode ficar armazenado num Redis, como eu vim falando até agora ou, no caso desse blog post, ele sugere deixar no DynamoDB que é outro tipo de NoSQL. Aqui depende da sua aplicação e nenhuma das opções é necessariamente melhor ou pior. Você precisa estudar os detalhes de ambas pra decidir qual é melhor pra você. Eu não recomendo usar Dynamo se você não sabe como ele funciona, as vantagens e desvantagens em cima de outros bancos mais convencionais.</p>
<p>Pra controlar a zona protegida, que é a página de escolha de assentos, você cria um serviço ou um worker que o blog post chama de Bouncer. Se você não sabe, Bouncer é o leão de chácara, o cara grandão na entrada da balada que mantém a ordem. O Bouncer é quem vai ficar monitorando quem já escolheu lugar e deixar outra pessoa entrar pra escolher. Pode ser um de cada vez, pode ser de 10 em 10 pessoas de cada vez. O blog post sugere usar um AWS Lambda, que funciona como o conceito de worker que eu falei acima.</p>
<p>E o AWS API Gateway como a API pra gerenciar os tokens. De novo, é opcional usar esses serviços. Você pode usar o Celery do DJango se estiver fazendo em Python em vez de usar o Lambda. O Lambda vai ser mais conveniente de escalar e instalar, o Celery vai ser um pouco mais barato e mais fácil de programar da primeira vez, mas aí você precisa controlar o deployment e escalabilidade na mão. De novo, depende da sua solução. Você tem que estudar todas as opções antes de sair escolhendo automaticamente só porque um blog post falou.</p>
<p>No final, o importante é que se sua aplicação estiver usando caches como eu falei, CDNs pros assets, evitar ficar batendo em serviços externos como o banco de dados, e implementar uma sala de espera, mesmo com um serviço bosta de escolha de assentos dos cinemas, é possível segurar a multidão do Homem Aranha sem brigas nem empurra empurra. Basta todo mundo ficar na sala de espera e ser corretamente notificado que não adianta esperar porque já tem mais gente esperando do que assentos disponíveis na sala. Daí notifica sugerindo outras salas próximas do mesmo tipo ou outra data mais pra frente.</p>
<p>Fazendo isso não só você controla a carga no serviço bosta e consegue fazer todo mundo pegar o assento que quer de forma ordenada, e consegue dividir o tráfego pra outras salas que as pessoas não escolheriam primeiro. Assim, todo mundo consegue os ingressos que quer, pra data que quer, sem sobrecarregar os serviços. Porque a regra é tornar escalável o que você mesmo fez e controlar o acesso do que você não tem propriedade.</p>
<p>Se subir a aplicação num Heroku ou usando ferramentas como a AWS Elastic Beanstalk ou CloudFormation, ou se for uma plataforma realmente grande usar um Kubernetes da vida, você deve ter servidores web voláteis, que pode escalar pra cima ou pra baixo, ou seja, iniciar novos processos da sua aplicação ou derrubar esses processos, sem perder informações das sessões dos usuários. Os processamentos mais pesados como pagamentos estão sendo controlados por Jobs, seja num Bull de Node.js em outro servidor, seja num AWS Lambda. E as partes mais sensíveis como a escolha de assentos está numa zona protegida por um serviço de Bouncer e seus usuários estão todos em salas de espera que é uma página leve que, mesmo que todo mundo fique dando reload, não vai pesar quase nada, porque a maior parte tá cacheado.</p>
<p>Daí, na frente disso tudo, como o blog post sugere, você ainda pode adicionar um AWS Shield que é um serviço de proteção de Denial of Service e o AWS WAF que é Web Application Firewall. Isso pra mitigar o caso de scalpers, que são robôs de cambistas que ficam tentando comprar ingressos de forma automatizada pra revender mais caro depois num eBay da vida. Outra opção é o Cloudflare, que eu particularmente gosto, pra evitar isso DDoS e cambistas também. Cambista tem que morrer, raça desgraçada.</p>
<p>Eu nem mencionei, mas é claro que coisas como o banco de dados MySQL ou Postgres, que eu prefiro, o Redis, tudo isso hoje em dia você não instala mais na mão e muito menos no mesmo servidor físico que sua aplicação Web. No caso da Amazon vai usar os serviços RDS que é banco de dados como serviço e o ElasticCache que é Redis ou Memcache como serviço. E eles se viram pra aguentar o tráfego que você der pra eles. Tem facilidades como gerar instâncias replicadas e também pra arquivar dados velhos que não usa mais num serviço como Glacier ou RedShift.</p>
<p>Tem muita gente que fica ainda com essa idéia de bala de prata e solução mágica e acha que a solução é só jogar tudo pra um NoSQL como um Dynamo da vida. Mas isso tá errado. Você vai querer extrair relatórios depois, e não tem nada mais prático pra extrair relatórios, fazer auditoria e tudo mais do que um SQL de verdade. Você sempre deve escolher SQL por padrão e pra usar um NoSQL no lugar dele, precisa ter uma justificativa muito forte e muito bem embasada em números. Se for só no chute, não escolha um NoSQL. Caso sua aplicação seja algo com características de alto volume de dados como um FitBit da vida, que fica mandando dados de batimento cardíaco de cada pessoa o dia inteiro, daí vai gerar volumes gigantes de dados que na maioria dos casos é inútil. Você vai compilando agregados desses dados por período de tempo, daí um banco de dados de Time Series pode ser mais adequado que um SQL, mas no final você ainda vai carregar o agregado por período em SQL pra gerar relatórios por exemplo. É caso a caso.</p>
<p>Além disso, tanto num Heroku ou AWS RDS da vida, você tem fácil a opção de fazer replicação dos dados. Ou seja, pode criar uma instância principal e várias instâncias seguidoras que vão receber uma cópia das modificações. Assim você pode extrair relatórios de uma instância que sua aplicação web não acessa, daí não fica concorrendo com seus usuários quando precisar tirar um relatório super pesado, por exemplo, que é outro erro muito comum que iniciantes fazem.</p>
<p>Aliás, se você entendeu o que expliquei até aqui, não só faria SQL pesados de relatórios numa instância replicada separada como jogaria processamento de relatórios pra um Job, pra um worker processar e entregar o arquivo de Excel depois. Se outro administrador pedir o mesmo relatório, já vai ter o cache desse arquivo, daí não precisa processar tudo de novo, toda hora. Sacou? O iniciante ia mandar processar o relatório na mesma instância principal do banco de dados que sua aplicação usa. Nessa hora um monte de tabelas ia ficar bloqueada e sua aplicação web ia engargalar esperando. E se outros administradores pedissem o mesmo relatório, ia mandar processar tudo de novo, deixando tudo lento. Mas usando filas e cache, você minimiza isso.</p>
<p>Filas e cache sozinhos resolvem 80% dos problemas de escalabilidade e isso são conceitos que independem da linguagem ou framework que você tá usando. Um site feito por um iniciante, onde uma página pode levar 100 milissegundos, dá pra baixar pra 30 milissegundos, sem trocar de linguagem nem nada drástico desse tipo. Você consegue aumentar de 1000 usuários simultâneos pra 100 mil usuários simultâneos, sem trocar nada, só fazendo a arquitetura correta como falei.</p>
<p>No tamanho de um ingresso.com, com mais de 10 anos de idade, já era pra ter implementado o que falei, uma parte de cada vez, ao longo dos anos. Ninguém deveria fazer o que eu falei no dia um, porque não se justifica a complexidade da quantidade de peças móveis. Mas a essa altura do campeonato já é injustificável não ser assim. Nada do que eu falei hoje é considerado avançado. São técnicas que existem faz anos, mais de década, todo grande site com as características de ter que acessar serviços lentos como escolha de assentos sabe o que é uma porcaria de uma Sala de Espera. Porra, até a porcaria de cartórios físicos usa esse conceito. O conceito precede a existência da internet.</p>
<p>Outra coisa importante. Eu usei números e métricas inventadas só pra dar de exemplo. Como eu disse, depende do seu sistema, onde você instalou e quantos usuários acessam seu sistema de uma só vez. Pra saber os números reais da sua aplicação, antes de tentar implementar qualquer coisa que falei, você deve ter métricas reais pra comparar antes e depois. Todo framework que se preza tem bibliotecas de profiling e métricas que você deve instalar e rodar em produção. Ou usar serviços como o New Relic, que vai instalar um plugin na sua aplicação web e registrar todos os tempos de tudo da sua aplicação, assim você vai encontrar gargalos muito fácil.</p>
<p>A coisa mais errada que existe é começar a otimizar código sem ter métricas. Você vai gastar dias otimizando uma parte do sistema que achava que era gargalo, mas na real, nenhum usuário nem acessa muito. Otimizar sem métricas é uma enorme perda de tempo. Normalmente tem no máximo meia dúzia de gargalos de performance que se você resolver eles primeiro, 80% dos problemas de performance são resolvidos. Você instala o New Relic, deixa rodando alguns dias, e volta pra ver quem são os Top 3 principais ofensores e vai resolver eles primeiro. De cara, vai descobrir que o principal ofensor é uma tabela grande que você esqueceu de colocar um índice. E fazendo isso metade da lentidão vai embora, sem modificar uma linha de código.</p>
<p>Agora, você instalou o New Relic e vai começar a otimizar, fazer uma fila, transformar um pedaço de código síncrono ou com async num job assíncrono de verdade com workers. Só que ao fazer isso, necessariamente vai introduzir bugs inesperados. Não é uma questão de &ldquo;se&rdquo;, é literalmente uma questão de &ldquo;quando&rdquo;. Não importa se você é um iniciante ou um sênior, você vai introduzir bugs. Especialmente se for a primeira vez fazendo uma arquitetura assim.</p>
<p>Antes de otimizar performance ou mudar arquitetura você precisa criar uma suite mínima de testes. E precisa ir adicionando novos testes e dando manutenção nos antigos, à medida que for implementando a solução nova. Você precisa ter confiança que o que tá fazendo está de fato funcionando. Senão vai ver no New Relic que sim, melhorou a performance, mas só porque você introduziu um bug que tá pulando o código que precisava executar. Daí tudo ficou mais rápido não porque você otimizou certo, mas porque apagou o pedaço que era lento e agora não roda mais. Parece louco isso, mas acontece.</p>
<p>Então antes de falar em fazer Salas de Espera, tokens de acesso, CDNs, Redis e tudo mais, você precisa aprender a instalar e monitorar métricas pra identificar objetivamente os gargalos e erros do seu sistema e precisa aprender pra ontem como fazer testes unitários, testes de aceitação e tudo mais. Só DEPOIS disso que você pensa em adicionar caches e filas e jobs assíncronos. Ficou claro? E espero que com o que expliquei até agora tenha dado uma luz sobre a diferença de sistema feito depois que você só assistiu um curso ou seguiu um tutorial e um sistema de verdade. Tem muito mais coisas além disso. Como eu falei, nunca tive acesso ao código do ingresso.com então estou supondo muita coisa a partir do comportamento que eu experimentei e odiei. Pode ter vários outros gargalos que eu não pensei hoje. Mas é isso aí, se você ficou com dúvidas vai jogando nos comentários, e nada de spoilers do Homem Aranha heim! Se curtiu deixa um like, não deixe de assinar o canal e compartilhar com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>desenvolvimento-web</category><category>performance</category><category>sistemas-distribuidos</category><category>akitando</category></item><item><title>[Akitando] #92 - RANT de 200K! | Upgrade do meu PC</title><link>https://akitaonrails.github.io/2021/02/11/akitando-92-rant-de-200k-upgrade-do-meu-pc/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/02/11/akitando-92-rant-de-200k-upgrade-do-meu-pc/</guid><pubDate>Thu, 11 Feb 2021 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/FIp1uZq-RSE"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Depois de 2 anos e meio chegamos aos 200 mil assinantes!!&lt;/p&gt;
&lt;p&gt;No video de hoje vou começar com um mini-Rant só pra não perder o costume mas a maior parte do vídeo vai ser apresentando e discutindo os upgrades que eu fiz no meu PC de edição principal e porque eu escolhi determinados componentes.&lt;/p&gt;
&lt;p&gt;Todo mundo sempre me pergunta sobre teleprompter, camera, meu setup, então hoje vou mostrar um pouco disso.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/FIp1uZq-RSE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de 2 anos e meio chegamos aos 200 mil assinantes!!</p>
<p>No video de hoje vou começar com um mini-Rant só pra não perder o costume mas a maior parte do vídeo vai ser apresentando e discutindo os upgrades que eu fiz no meu PC de edição principal e porque eu escolhi determinados componentes.</p>
<p>Todo mundo sempre me pergunta sobre teleprompter, camera, meu setup, então hoje vou mostrar um pouco disso.</p>
<p>ÍNDICE:</p>
<ul>
<li>00:00 - Intro</li>
<li>00:56 - Mini-Rant: os objetivos do Canal</li>
<li>04:48 - Organização do Conteúdo do Canal</li>
<li>07:11 - Bastidores: Scripts</li>
<li>12:01 - Bastidores: Teleprompter</li>
<li>12:30 - Bastidores: Câmera</li>
<li>12:58 - PC: Storage</li>
<li>13:59 - PC: SATA vs PCIe</li>
<li>16:08 - PC: GTX 1070</li>
<li>17:17 - PC: RTX 3080</li>
<li>21:55 - PC: Intel i9-9900K</li>
<li>24:34 - PC: Memória</li>
<li>25:49 - PC: setup antigo</li>
<li>26:22 - PC: cuidado com DRAM-less</li>
<li>27:04 - PC: quanto custou?</li>
<li>27:47 - PC: alternativa barata?</li>
<li>29:54 - PS5</li>
<li>30:27 - Sony A7iii</li>
<li>31:26 - Conclusão</li>
</ul>
<p>LINKS:</p>
<ul>
<li>Maybe upgrading is pointless…..(<a href="https://www.youtube.com/watch?v=vZ_2kRO-RPg"target="_blank" rel="noopener">https://www.youtube.com/watch?v=vZ_2kRO-RPg</a>)</li>
<li>RTX 3090 8K Gaming - FIRST in the WORLD (<a href="https://www.youtube.com/watch?v=JDUnSsx62j8"target="_blank" rel="noopener">https://www.youtube.com/watch?v=JDUnSsx62j8</a>)</li>
<li>Linus was right. (<a href="https://youtu.be/pPeCNrNTr3k"target="_blank" rel="noopener">https://youtu.be/pPeCNrNTr3k</a>)</li>
<li>AMD X570 vs. Intel Z390 Chipset: Which Mainstream Platform Is Better? (<a href="https://www.tomshardware.com/reviews/amd-x570-vs-intel-z390-chipset-faceoff,6240.html"target="_blank" rel="noopener">https://www.tomshardware.com/reviews/amd-x570-vs-intel-z390-chipset-faceoff,6240.html</a>)</li>
<li>Nvidia RTX 3080 vs. Microsoft Xbox Series X vs. Sony PlayStation 5 (<a href="https://www.digitaltrends.com/computing/nvidia-rtx-3080-xbox-series-x-playstation-5-gpu/"target="_blank" rel="noopener">https://www.digitaltrends.com/computing/nvidia-rtx-3080-xbox-series-x-playstation-5-gpu/</a>)</li>
<li>What is NVMe™ and why is it important? A Technical Guide (<a href="https://blog.westerndigital.com/nvme-important-data-driven-businesses/"target="_blank" rel="noopener">https://blog.westerndigital.com/nvme-important-data-driven-businesses/</a>)</li>
<li>PCIe Lanes explained (<a href="https://cotscomputers.com/blog/pcie-lanes/"target="_blank" rel="noopener">https://cotscomputers.com/blog/pcie-lanes/</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Hoje o video vai ser bem leve comparado com o normal do canal. Faz algumas semanas que ando fazendo upgrades técnicos no meu PC de edição e no meu setup em geral, e acho que posso fazer algumas tangentes aproveitando o assunto. Tava querendo soltar logo esse material porque ando brincando com isso desde antes do natal de 2020. E se nada der errado deve coincidir com o canal atingir a marca dos 200 mil assinantes. Acho que não tá ruim pra pouco mais de 2 anos de canal.</p>
<p>O primeiro video que soltei em 2021 foi meio que um resumão dos conteúdos mais recentes do canal e acho que esse vale ser um resumão dos bastidores do canal. E claro, o começo vai ser um mini-rant mas se quiser pular só pra parte dos upgrades, você pode pular pra esse tempo aqui embaixo, ou usar os bookmarks na descrição do video.</p>
<p>(&hellip;)</p>
<p>Aliás, o YouTube deve ter ficado mesmo com medo de eu quebrar a placa de 100 mil assinantes, porque já passamos dos 200 mil e até hoje necas. Se você não sabe do que estou falando, assista a partir do minuto 20:36 do vídeo de Aprendendo a Aprender pra entender.</p>
<p>Tanto pra quem é novo no canal ou quem já acompanha faz tempo, acho que vale a pena eu repetir o objetivo do canal. Vai ser um mini-rant mas já já eu volto a falar do meu PC. Vamos lá: Eu não vendo cursos, eu não vendo coaching, eu não vendo nada aqui. Eu venho sistematicamente recusando todas as ofertas de patrocínio de cursos e produtos que já me mandaram.</p>
<p>O canal é feito pra ser meio que um backup do meu cérebro. Eu expliquei isso num dos primeiros videos do canal, o Diário de Henry Jones. Eu não estou aqui pra defender marcas, evangelizar ferramentas, ou qualquer coisa assim. O canal foi originalmente projetado pra ter um único espectador, que seria eu mesmo. E se no caminho for útil pra mais uma pessoa que seja, ele já serviu seu propósito.</p>
<p>Eu não monetizo o canal by design. Por 2 anos eu mantive a configuração de monetização desligada, mas acabei ligando porque o próprio YouTube começou a forçar Ads mesmo eu não querendo. Então eu liguei e o que o canal está arrecadando, que é menos de 300 dólares por mês, eu tento doar pra projetos educacionais sem fins lucrativos. Eu explico isso em detalhes no video acima. Aliás, eu quase não tenho recebido sugestões de projetos. Se você conhece algum professor ou profissional que toca projetos educacionais e precisa de ajuda, peça pra mandar detalhes neste e-mail aqui embaixo: akitando arroba codeminer42.com.</p>
<p>Este canal é uma extensão da minha casa que eu escolhi deixar aberto pra vocês. E na minha casa tem algumas regras: eu produzo conteúdo que eu mesmo quero consumir e não o que vai necessariamente atrair mais público.  &ldquo;Eu&rdquo; sou o público alvo. Eu não monetizo nem aceito patrocínios exatamente pra ninguém achar que pode vir chutando a porta e demandando coisas. Vai fazer o que? Xingar meu patrocinador, que sou eu mesmo? Tentar me cancelar pra diminuir monetização que é 100% pra doação? Quer uma plataforma pra falar, faz a sua. 2 anos atrás eu comecei sozinho do zero também.</p>
<p>No contexto de ser minha casa, pensa só, vira e mexe aparece gente mau educada, sabe aquele cara folgado que você convida pra sua casa e ele entra com a bota suja de lama pisando no seu tapete, chutando seu gato do sofá e abrindo sua geladeira pra comer suas coisas e ainda reclama que não tem a marca de cerveja que ele gosta? Pois é. babaca eu xingo e expulso, é simples assim. Famoso: caguei.</p>
<p>Eu não sou influencer. Eu não vendo nada. E desde o começo eu fiz videos inteiros dizendo isso. Por exemplo, nos de Matando Semi-deuses eu mando um aviso: eu não considero ninguém um semi-deus, não sigo ninguém cegamente nem preciso ser validado por ninguém. E isso foi um aviso pra vocês não me enxergarem como um semi-deus também, eu sou só um cagador de regras. E no video de Não Terceirize suas Decisões eu aviso de novo. Se não ficou claro: eu não sou dono da verdade. Eu explico o porquê das coisas por experiência própria. E explico porque algumas opções podem limitar suas escolhas depois. Mas suas decisões são só suas. Só porque eu não falei bem da sua decisão, não torna ela menos certa pra você.</p>
<p>E aviso pela terceira vez: se você precisa que eu valide alguma opção que você escolheu, senão se sente &ldquo;mal&rdquo; ou &ldquo;ofendidinho&rdquo;, mas que porcaria de opção essa sua heim? Qualquer opção que precisa alguém passar a mão na sua cabeça pra dizer &ldquo;poxa, que bacana, vai dar tudo certo&rdquo; eu diria que é uma opção bem bosta. De novo: não terceirize suas opções de linguagem, plataforma, ferramentas, cursos, carreira pra ninguém, nem pra mim. Faça perguntas, mas não faça porque alguém famoso disse. Vender coco na praia não é menos nobre que ser programador em Silicon Valley. Só você sabe das suas próprias limitações e seus objetivos de vida. E eu não sou pai de ninguém.</p>
<p>Isso tudo dito, vocês já devem ter percebido que eu escolho falar sobre um caminho muito específico de programação: o mais difícil. Os videos de Rant são os mais visualizados, e a palavra &ldquo;Rant&rdquo; significa &ldquo;reclamar pra cacete&rdquo;, como o mais recente que eu xingo mesmo programador chinfrim que faz sistema cheio de buraco pra site de governo, que você pode assistir no link acima. Apesar de trazer tráfego, meu canal não foi feito só pra rants. Mais um motivo de eu cagar pra monetização senão podia acabar só fazendo videos assim. É mais pra eu ventilar na hora mesmo.</p>
<p>Eu prefiro falar sobre a história e as bases da computação, coisa que a maioria que se diz programador desconhece e eu acho que ignorar essa base é limitar demais sua carreira e por isso eu tento preencher algumas dessas lacunas. Eu comecei com uma playlist inteira no começo de 2019 chamado Começando aos 40, respondendo uma pessoa que aos 40 anos queria mudar de área pra programação. Renomeei essa playlist pra Programação pra Iniciantes e ele já tem 22 videos. Tem outra playlist mais completa que inclui mais desses videos chamado &ldquo;Programming Concepts&rdquo; que já tem quase 40 videos. Ou seja, no mínimo uns 40 dos 90 videos que já postei é técnico e não de ranting.</p>
<p>E tem outra playlist que já tem mais de 30 videos que é a Career, onde eu falo sobre faculdade, mercado de TI, agilidade e muitos outros conceitos não-técnicos que eu não sabia quando eu estava pra entrar na faculdade, no meio dos anos 90. Por que eu fiz esses videos? Pra responder uma vez só todo mundo que fica me mandando as mesmas perguntas sobre &ldquo;que cursos deveria fazer&rdquo;, ou &ldquo;que faculdades deveria fazer&rdquo;, &ldquo;quais áreas de TI vale a pena&rdquo; e tudo mais. Repetindo, eu não sei o que é melhor pra você. Ninguém sabe. Não pergunte, pesquise. Nesses videos eu tento dar uma base de referência, mas é impossível qualquer um dizer o que é melhor pra você, individualmente.</p>
<p>Além da minha própria carreira, eu conheci dezenas de programadores, eu já avaliei e contratei literalmente centenas de programadores, já vi gente desde que saiu da faculdade até quase se aposentar. São quase 30 anos de história. E a única constante é que nunca vi constância. A história de cada um é diferente. Portanto você não deve nunca decidir alguma coisa &ldquo;porque o Akita mencionou&rdquo;. Repetindo outra coisa que eu sempre falo, é que nem o moleque que diz que vai largar faculdade porque Bill Gates e Mark Zuckerberg largaram faculdade. Ow, seu bostinha, o Gates largou Harvard. O dia que você for capaz de entrar em Harvard a gente conversa.</p>
<p>Voltando aos bastidores: muita gente ainda me pergunta e sim, eu escrevo scripts pra cada video. Este mesmo de agora, eu estou lendo de um teleprompter na frente da minha camera, como vou mostrar agora, tão vendo? Tem vários apps e sites que fazem isso e não tem nenhum motivo particular pra ser esse mas eu uso um pago chamado easyprompter.com. O texto reflete no espelho e eu configuro numa velocidade que eu consigo ler, nem lento demais, nem rápido demais. Tem gente que acha que eu falo devagar, mas desculpa galera, esse é o máximo. Quando eu tento falar mais rápido eu erro mais, daí a gravação demora mais e dá mais trabalho pra editar depois.</p>
<p>E antes que perguntem, sim, eu publico todos os scripts no meu blog, akitaonrails.com. Vocês vão notar que todos os cortes do video tem exatamente o comprimento de cada parágrafo. É assim que eu escolhi editar. Então eu escrevo os textos exatamente como eu vou falar, com erros de português e tudo que encaixa no jeito mais natural de falar. Fica a dica: não escrevam textos da forma correta escrita e deixar pra improvisar gírias e maneirismos na hora de gravar, só facilita erros e de novo, aumenta o tempo de gravação. Eu só gravo quando o texto tá exatamente como vou ler depois.</p>
<p>Povo queria que eu fizesse mais vídeos por mês e perguntam porque eu não terceirizo a edição, achando que é isso que consome mais tempo. A resposta é simples, a edição não demora tanto. Uma vez planejado direito, a edição em si nem é tão chato, eu até gosto de fazer. A gravação é um saco e demora, pra um vídeo de 30 minutos, se eu errar demais, pode levar 1 hora ou mais gravando. Os videos de 1 hora, às vezes levava 3 horas pra gravar. Por isso tenho tentado segurar um pouco. Mas o que mais demora na real é escrever os scripts.</p>
<p>Sendo mais correto: na hora que eu tenho o texto na cabeça, digitar é super rápido, eu consigo digitar 15 páginas ou mais em 2 horas. E 15 páginas é suficiente pra um video de 30 minutos mais ou menos. O problema é a inspiração sobre o que escrever. E não é falta de tema, eu tenho temas pra vários videos já na cabeça, mas eu demoro muito pra decidir &ldquo;como&rdquo; eu quero apresentar determinado tema. É normal eu começar a escrever algumas páginas, jogar tudo fora e começar tudo de novo várias vezes. Ainda bem que eu não sou escritor profissional, que se não escrever morre de fome, senão eu morria de fome.</p>
<p>Os tais videos de ranting são os mais fáceis. Eu sou reclamão por natureza, e pra piorar eu sou consultor por profissão. E o trabalho de um consultor é achar problemas. Em tudo eu acho problema, então é muito fácil fazer videos de 1 hora reclamando. Os mais chatinhos são justamente os técnicos porque se eu não tomar cuidado pode ficar super maçante e tedioso de assistir. Daí demora bastante pra eu saber o que eu quero dizer. Eu preciso achar uma narrativa divertida que consiga carregar conceitos técnicos chatos.</p>
<p>No começo do canal eu tava ainda em dúvida no formato. Tinha ouvido falar aqueles mitos que o certo era fazer videos curtos e frequentes. E no começo eu cheguei a fazer 2 videos de 15 minutos por semana. Mas eu achei horroroso esse formato. Apesar de me esforçar pra falar pouco, 15 minutos é muito pouco pra explicar alguma coisa mais complicada. Você acaba tendo que assumir que quem está assistindo já tem uma base de conhecimento, mas eu gosto de pelo menos deixar dicas pros iniciantes conseguirem parar pra pesquisar no meio.</p>
<p>Os videos funcionam melhor se vocês assistirem os mais recentes depois de terem assistido os anteriores, porque a cada novo video eu uso o fato de eu já ter explicado o que preciso nos anteriores. Daí eu deixo o link nos cards que aparecem no topo do video e qualquer conteúdo extra sempre deixo linkado nas descrições, então não deixem de ver isso também. A idéia é que se você assistir tudo com calma, um video vai puxando o próximo. Por exemplo, no video de rant de segurança eu xingo o animal que grava senhas abertas em banco de dados. E nos videos de criptografia que fiz meses antes eu explico o jeito certo de fazer isso.</p>
<p>Também por isso, se você é programador mais experiente, deve estar notando que ainda faltam videos sobre algoritmos, arquiteturas distribuídas, design patterns, otimização de sistemas, redes. Mas isso porque eu ainda não cheguei lá, eu vou chegar. Então o desafio dos videos tem sido eu falar assuntos que são pra iniciantes com twists que mesmo alguns mais experientes talvez não saibam, como a história por trás de cada coisa ou algo mais geek. Por isso escolhi falar de introdução à computadores, que é um tema chatinho pra quem já sabe, usando videogames, que é divertido pra qualquer um.</p>
<p>E sim, vocês também já devem ter notado que eu procuro falar os termos em inglês em vez de tentar traduzir tudo pra português. Lá no começo do canal eu fiz um video inteiro falando disso, mas sim, é totalmente possível ter uma carreira em programação sem saber nem essa fundação que eu tanto falo e sem saber inglês também. Dentre as várias histórias de pessoas que eu conheci, estavam casos assim. Mas de novo, vai ser mais limitado e com menos oportunidades. Se você é novo e tem a opção de escolher, porque escolher o caminho mais fácil e, portanto, mais restrito? Mas, você é livre pra fazer diferente e não sou eu que vou julgar. Afinal não sou eu que pago suas contas. Só nunca diga que ninguém avisou.</p>
<p>Do lado técnico, depois de escrever os scripts, revisar, editar, cortar e rearranjar, chega a hora de gravar. Nos primeiros videos eu ainda não tinha uma câmera. Por acaso, numa das minhas viagens, eu tinha comprado uma DJI Osmo que é uma camera estabilizadora. A única razão de eu ter usado ela é porque ela consegue ficar parada de pé, daí eu colocava na frente do meu monitor e conseguia ir lendo atrás dela. E era um saco porque mesmo a estrutura da câmera sendo fina, ela ainda cobria parte das palavras.</p>
<p>Quando eu vi que ia mesmo continuar gravando eu comprei uma camera de verdade com tripé e teleprompter, tudo no mercadolivre mesmo. A camera foi uma Canon EOS Rebel T7i e o teleprompter não tem marca, é um desses genéricos. Inclusive o primeiro eu esbarrei, derrubei e quebrei o espelho. Deveria ser sete anos de azar? Mas não foi. Por acaso os últimos dois anos pra mim foram os mais sossegados que eu tive, já os três anos antes de começar o canal foram um inferno na terra. Mas isso é história pra outro dia.</p>
<p>Essa Canon T7i era Full HD, resolução de 1080p que é mais que suficiente pra videos no meu formato. Muitos iniciantes não consideram que video ocupa muito espaço. O último video antes deste, a pasta de materiais bruto tem 12 gigas. Todos os videos que eu gravei soma mais de 2 terabytes. Então uma coisa que eu vim investindo com o tempo foi em discos maiores e rápidos. No meu PC hoje eu deixo os materiais pra edição num Samsung SSD 860 QVO 4TB.</p>
<p>Sim, um SSD de 4 terabytes, deve durar mais 1 ano até eu ter que começar a usar outro drive. Se o HD for muito lento, na hora de editar vai ser lento. Eu tenho um HD mecânico normal Seagate onde eu coloco coisas que não depende tanto de velocidade, como minha pasta de jogos do Steam. Esse HD mecânico tem velocidade máxima de leitura e escrita sequencial de uns 160 megabytes por segundo. Não é ruim, mas eu prefiro mais veloz. O SSD 860 QVO chega a mais de 500 megabytes por segundo de velocidade sequencial.</p>
<p>Pra quem não sabe. SSDs mais baratos se ligam via a interface SATA-III na placa mãe. Essa interface serial tem velocidade teórica máxima de 6 gigabits por segundo, que é mais ou menos 600 megabytes por segundo. Cada geração de SATA dobra a anterior e permanece compatível, o SATA original era de 1.5 gigabits por segundo, o SATA-II era 3 gigabits e o SATA-III é 6 gigabits. Então o meu SSD 860 QVO a mais de 500 megabytes por segundo tá perto do limite da interface SATA-III e é mais que suficiente pra edição de video.</p>
<p>De curiosidade, recentemente adicionei um drive novo de boot onde deixo o Windows 10 e os programas instalados, daí já é um passo acima de SSD SATA pra SSD PCI Express ou mais como NVME pra slots M.2. SATA é uma interface antiga, feita pra HDs mecânicos, disquetes, CD-ROMs. NVME é uma interface pensada pra SSDs, solid state drives, ou seja, sem partes mecânicas móveis. E ele se conecta a lanes de PCI Express.</p>
<p>Outra tangente, pra quem não sabe, PCI Express é um barramento de comunicação onde você conecta dispositivos de entrada e saída, como SSDs ou USB ou placas de videos e muito mais. Dependendo da geração e da placa mãe ele vem normalmente configurado com até dezesseis lanes ou pistas. Pense em PCI Express como uma rodovia com várias pistas e os dispositivos podem escolher trafegar em um ou mais pistas, dependendo do tamanho da carga.</p>
<p>PCIe é o sucessor de SATA, e assim como SATA também teve gerações, generations ou gens. O PCIe Gen 2, que foi o anterior, suporta lanes de até 500 megabytes por segundo, um pouco menos que o SATA-III que eu falei. Mas a geração atual é PCIe Gen 3 que suporta lanes de até 1 gigabyte por segundo, por lane. No caso do meu NVME Samsung 970 EVO Plus, eu chego a velocidades sequenciais de leitura e escrita de mais de 3 gigabytes por segundo, ou seja, ele deve estar usando até 3 pistas dessa rodovia. Isso é mais de 6 vezes mais rápido que o SSD 860 QVO que falei antes.</p>
<p>E falando em pistas, quem mais usa lanes de PCIe hoje em dia são as placas de video. Normalmente usam 8 lanes, o que daria uma velocidade máxima teórica de 8 gigabytes por segundo. Desde 2018 eu tava usando uma placa NVIDIA GTX 1070Ti de 11 gigabytes de VRAM, que é bastante até pra hoje em dia. Ele tranquilamente rodava qualquer jogo pesado Triple-A nível Red Dead Redemption 2 em Full HD com mais de 60 frames por segundo, muitos jogos acima de 100 fps. Mas acima de 1080p que é Full HD ele já sofria um pouco mais.</p>
<p>Além de jogos, o Adobe Premiere Pro, que eu uso pra editar meus videos, tem certo suporte a GPUs e conseguia fazer renderizações de video quase 1 a 1, ou seja pra cada 1 minuto de video ele gastava 1 minuto renderizando, o que é até razoável. Mas no fim do ano passado, eu comecei a olhar os videogames de nova geração, PS5, Xbox Series X, e eu achava que não ia ficar com vontade então não reservei na pré-venda. Mas aí quando saiu eu passei vontade &hellip; Então resolvi aproveitar essa vontade pra fazer upgrades no meu PC pra ficar melhor que um Xbox Series X e pra isso passei algumas semanas procurando uma placa NVIDIA RTX 3080.</p>
<p>Mas não são só os videogames que andam em falta. A nova geração 3000 das placas RTX da NVIDIA também estão com dificuldade de atender a demanda. Parte porque a demanda realmente tá alta, e parte porque as quarentenas prejudicaram as cadeias de suprimento e produção das fábricas pelo mundo. Pra ter uma idéia, no mercadolivre mesmo, cheguei a encomendar com uns quatro vendedores diferentes e todos cancelavam o pedido depois de pago. Ainda bem que o mercadolivre no geral lida bem com estorno. Mas finalmente, no fim de dezembro, achei alguém com preço razoável vendendo uma Gainward RTX 3080, sendo a Gainward uma das marcas que vende RTX mais bufados.</p>
<p>Dêem uma olhada, primeiro vamos desparafusar e tirar a antiga GTX 1070 Ti de guerra, que renderizou quase todos os videos deste canal. E agora vamos colocar o monstrão no lugar dela. Vocês podem ver que só de tamanho ela é absurda e vem com um suporte transparente que é pra ajudar a aguentar o peso mas não vou precisar colocar por enquanto. E pra alimentar ela, precisa de 3 cabos PCI-ex cada uma acho que carregando até 100 watts pra um total de absurdos 300 watts que essa placa sozinha pode consumir em momentos de pico.</p>
<p>Essa placa é violenta. Puxa energia como se não houvesse amanhã e esquenta mais que um vulcão. Eu tenho 4 ventoinhas internas no meu gabinete e minha CPU tem resfriamento a água, então eu nunca sentia o gabinete nem morno. Mas com a RTX dentro, quando tô jogando, eu sinto o video do gabinete ficando morno ao toque. Além disso eu sei que a placa tá sendo puxada porque a ventoinha do meu no-break aumenta muito as rotações. Eu nunca tinha visto ele fazer isso. Nem sabia que tinha ventoinha no meu no-break.</p>
<p>Se não me engano, essa placa sozinha puxa até 320 watts de energia. Em comparação a minha pobre GTX 1070 Ti não puxava 180 watts. Por sorte minha fonte Corsair Plus Bronze já era de 750 watts. Então, mesmo se a GPU der picos de energia, ela deve suportar com sobra. E essa potência se justifica. Eu disse que a GTX 1070 ia perto dos 100 frames por segundo só em Full HD. Mas a RTX 3080 vai a 100 frames por segundo em 4K. Claro que varia de jogo pra jogo, mas eu nunca vi ela fazer menos de 90 frames, só quando o jogo tá capado em 60.</p>
<p>A grosso modo podemos dizer que essa placa é uns 50% mais potente no geral que um PS5. Mas claro que depende da aplicação. E depende do que ele puxa mais na placa. Por exemplo, a RTX 3080 tem clock de 1.7 gigahertz mas o PS5 tem 2.3 gigahertz. Por outro lado o RTX 3080 tem banda máxima de 760 gigabits por segundo enquanto que o PS5 tem pouco mais da metade só a 448 gigabits. Em termos de teraflops brutos, o RTX 3080 sai muito na frente com quase 30 teraflops enquanto o PS5 fica na casa dos 10 teraflops.</p>
<p>Na prática, deu pra ficar impressionado que se não me falha a memória, um video de 30 minutos editado no Premiere Pro, levava pelo menos 30 minutos pra renderizar com a GTX 1070. Agora com a RTX 3080 esse tempo tá mais na casa dos 5 minutos. Faz uma diferença enorme, especialmente quando você tá terminando de editar no fim da noite, renderiza, descobre que tem erros, e aí precisa renderizar de novo. Só nisso era 1 hora perdida. Com a placa nova isso vai cair pra 10 minutos ou menos. Às 2 horas da manhã, quase 1 hora economizada fazem muita diferença.</p>
<p>E isso porque acima da RTX 3080 ainda tem a RTX 3090 que dizem que suporta games a 8K. Mas sendo bem honesto mesmo a RTX 3080 é muita coisa pra mim. Eu não trabalho puxando ela no máximo todo dia. Mesmo pra games, se eu tivesse uma RTX 2080 Super que é a placa mais em conta da geração passada, seria suficiente. Dessa vez eu comprei mais porque eu queria mesmo, não por necessidade. Se você for montar uma máquina de custo-benefício, pra trabalhar e pra uns games mais pesados procure pela RTX 2080 Super pra começar.</p>
<p>Mas comparar a RTX 3080 com um PS5 é obviamente comparar maçãs e bananas. Um console é meio como um smartphone, o hardware principal é um SoC ou system on a chip que são todos os componentes, CPU, GPU, memória e tudo mais encapsulado tudo junto, o que facilita fabricação e diminui custos. Além disso uma Sony da vida subsidia os consoles e vende a prejuízo, porque ele ganha com os jogos vendidos, e outras fontes de renda que não o hardware em si. Por isso um PS5 custa na faixa de 5 mil reais. Só a RTX 3080 não sai por menos de 7 mil reais. E ainda precisa do resto do computador então pode colocar mais o dobro disso pelo menos. Um PS5 é de fato uma ordem de grandeza mais barato por performance similar.</p>
<p>Falando nisso, aproveitei que já ia trocar a placa de vídeo pra trocar a CPU e RAM também. Eu já tinha uma CPU Intel i7-8700, 8a geração Kaby Lake e resolvi trocar por uma i9-9900K 9a geração Coffe Lake. Mas na prática a diferença é muito pequena no dia a dia. Do i7 pro i9 o que muda é a quantidade de cores que pula de seis pra oito e menos de 1 gigahertz de clock. Então só em tarefas que puxa todas as threads ao máximo por períodos longo de tempo, como renderização de video, por exemplo, é que vai sentir alguma diferença.</p>
<p>A placa mãe que eu tenho é uma antiga gigabyte Aorus Z370. Uma placa um pouco superior a ela seria uma Z390, mas eu não ia sentir muito a diferença. Um upgrade um pouco maior seria trocar de Intel pra AMD e pegar logo uma Ryzen 9 5900X. Daí eu teria que trocar a CPU, a placa mãe e provavelmente o resfriamento a água que encaixa no soquete da Intel. Já começou a parecer trabalho demais e por isso eu só mudei pro i9-9900K mesmo. De novo, eu ia sentir diferença mais em trabalhos que usam multi-core ao máximo, no dia a dia mesmo comparado ao meu antigo i7-8700 não ia ser tanta diferença assim.</p>
<p>Faz literalmente anos que eu não instalo uma CPU, então a primeira coisa a fazer é desparafusar o cooler do resfriamento a água, daí retirar a CPU i7 com cuidado e só encaixar a nova CPU no lugar. Se você já assistiu qualquer video de montagem de PC de canais como do Linus Tech Tips já ouviu quinhentas vezes como colocar CPUs, que tem a marquinha num dos cantos e tem que alinhar com a outra marquinha no socket.</p>
<p>E como vocês devem ter percebido, é a primeira vez que eu gravo assim e eu deveria ter mudado a posição ou ter tirado a placa gráfica da frente, sorry pelo erro de amador porque não dá pra ver direito, mas tudo bem, meu canal não é de review de hardware então não tenho uma câmera pendurada no teto. Enfim, depois de prender a CPU é hora de aplicar pasta térmica e obviamente preferi escolher uma das mais famosas, a Thermal Grizzly. Ela é meio carinha a mais de 200 reais, mas é o tipo da coisa que você não quer economizar. Depois é só colocar de volta o cooler e parafusar. E voilá, tudo instalado.</p>
<p>Claro, se você estiver montando um PC do zero, eu consideraria uma AMD Ryzen 5 3600X com uma placa mãe Gigabyte Aorus X570 que é a versão da minha pra AMD, ou uma Asus ROG X570. Esses códigos como Z370 ou Z390 é o chipset pra Intel e X570 é o chipset pra AMD. Pra quem nunca ouviu falar nisso o chipset na placa mãe é a responsável pela comunicação entre a CPU, RAM, PCIe e outros periféricos. Por isso ela é específica pra Intel ou pra AMD.</p>
<p>Pra finalizar, como eu tinha 16 gigabytes de memória DDR4 2400 megahertz, eu queria já puxar o máximo com 64 gigabytes de memória Gskill Tridentz DDR4 de 4000 Mhz apesar da minha placa mãe não conseguir usar tudo isso. De novo, 64 gigabytes é mais do que eu preciso, 32 seria suficiente, mas pela primeira vez eu queria mais hardware do que eu realmente preciso, pra brincar de máquinas virtuais depois por exemplo. Instalar memórias é super simples, só garantir pra instalar nos slots certos.</p>
<p>Memórias hoje em dia funcionam em dual channel. E é o que o nome diz, ele provê dois canais de comunicação que podem ser acessados simultaneamente pela CPU, aumentando a velocidade de acesso a dados. Por isso, se você quer ter 16 gigabytes de RAM, deve comprar dois pentes de 8 gigas cada e nunca um único pente de 16 gigas direto. Além disso, se nunca comprou memória, só tomar cuidado pra comprar o formato DIMM que é pra PCs e não SODIMMs que é pra notebooks. E checar quais velocidades o chipset da sua placa mãe e seu CPU suportam. Por exemplo, eu comprei DDR4 de 4000 megahertz mas ele não vai muito acima de 3600 megahertz na prática então 4000 foi demais. Mas tudo bem, se um dia resolver dar upgrade na placa mãe também, essa memória já vai servir.</p>
<p>Então vamos resumir. Eu comecei com um Desktop que comprei pré-montado pela loja Rocketz aqui de São Paulo no fim de 2018 e na época paguei 13 mil reais nela por uma configuração com placa mãe Aorus Z370, CPU Intel i7-8700, 16 gigabytes de RAM DDR4 2400 megahertz, placa de vídeo NVIDIA Geforce GTX 1070Ti com 8 gigabytes de VRAM, uma fonte Corsair CX750 de 750 watts, fora radiador a água Corsair H80i v2 e um componente que eu recomendo você nunca usar, que foi um SSD Kingston.</p>
<p>Outra tangente. SSDs não são todos iguais. Se você ver um SSD muito barato, de marcas como Kingston, Crucial acho que até alguns Sandisk, preste atenção se elas não são DRAM-less. SSDs sem DRAM, que são módulos de memória no SSD, não sustentam velocidades altas de leitura e escrita. DRAM funciona como um cache, um buffer, entre a CPU e o SSD em si. Pelo menos no meu modelo, cache fez uma diferença brutal. Num uso normal de Windows, abrindo e fechando aplicativos, eu podia jurar que sentia o computador literalmente pausando umas horas. Parecia que travava e voltava. O que resolveu foi quando joguei fora esse Kingston porcaria e coloquei um SSD Samsung EVO, que é uma das melhores marcas.</p>
<p>Agora, o upgrade foi caro. A CPU i9-9900K sozinha foi mais de 3 mil reais. 64 gigabytes em 2 pentes de 32 gigas G.skill Tridentz RGB custou 4 mil reais. E como eu disse antes, a Gainward RTX 3080 de 11 gigas de VRAM custou 7 mil reais, então no geral foi um upgrade de mais 14 mil reais, que é mais do que custou o PC original. Se eu fosse trocar por AMD o CPU em si é comparável em preço, com o Ryzen 9 5900X e o i9-9900K por pouco mais de 3 mil reais. Mas a placa mãe Aorus X570 xtreme sozinha custaria entre 6 a 10 mil reais. Esse upgrade deixo pra outro dia, mas se fosse comprar um PC novo nessas configurações seria fácil na faixa de 30 mil reais.</p>
<p>Antes que alguém comece com a pergunta de qual a máquina mínima pra editar videos ou mesmo pra programar, vamos colocar um contexto aqui. Eu tenho mais de 40 anos. Estou comprando uma máquina desse tamanho pra mim só agora. Quando eu tinha 20 anos, mesmo 30 anos, eu não tinha nada perto disso. Pior, quando eu comecei o canal mais de 2 anos atrás eu editei os primeiros videos no meu notebook que era um Dell Inspiron 5570 com Core i7 8a geração, 8 gigas de RAM que eu dei upgrade pra 16 gigas, e acho que a GPU dele era uma Radeon 530 com meros 4 gigabytes de VRAM. Na época eu acho que paguei entre 3500 a 4 mil reais nela.</p>
<p>Pra quem tá nos primeiros dias de aprender a programar por exemplo, pode começar com muito menos que isso. Um Dell Inspiron 15 3000, com 8 gigas de RAM, custa menos de 4 mil reais hoje na loja online da Dell. É muito mais que suficiente pra começar. Dá pra editar videos nivel do meu canal nele. Se você tá com orçamento realmente apertado e quer começar os primeiros passos na programação, não estou brincando, um smartphone ligado num hub USB com teclado, mouse e ligado no monitor via USB-C pode funcionar. Alguns celulares como da Samsung tem essa opção.</p>
<p>Além disso você pode comprar um Raspberry Pi 4 que custa só 75 dólares, mais ou menos uns 400 reais. Vai ser lento? Claro que vai. Vai te impedir de aprender? De jeito nenhum. Eu aprendi a programar num PC dos anos 80 de 33 megahertz, 1 mega de RAM e 20 megas de HD. E estou falando mega e não gigas. Se você não consegue programar num computador fraco, porque você acha que um computador mais rápido vai fazer qualquer diferença?</p>
<p>Pra completar eu também comprei um monitor gamer Asus Tuf de quase 32 polegadas de 144 hertz com freesync e resolução de 2K ou 1440p. Aliás, pra monitor de computador, eu acho que 1440p a 144 hertz é a combinação ideal. 4K faz mais sentido pra TVs acima de 50 polegadas. Por isso meu PS5 eu ligo na minha TV 4K de 75 polegadas. Aliás, 4K em celular nunca fez nenhum sentido pra mim. Só é mais caro e gasta mais bateria. Porque numa tela tão pequena, Full HD ou 1080p é mais que suficiente.</p>
<p>Ah sim, e no final, eu acabei achando o PS5 em estoque no fim de janeiro e consegui comprar. Então agora meu setup pra jogar está excelente. Eu tenho o PC pra jogar qualquer coisa que um Xbox Series X consegue rodar. Tenho um PS5 na minha sala numa TV de 75&quot; ligado num receiver Denon pra home theater e joguei meu Xbox One X no quarto pra poder jogar da cama numa TV 50&quot;. E por isso que meus videos tem demorado um pouco mais pra sair. Assim que eu enjoar eu volto a fazer videos com mais frequência.</p>
<p>E pra finalizar, fiz um último upgrade. Lá no começo eu contei como eu gravei a maioria dos meus videos numa Canon Rebel T7i que grava em 1080p a 30 frames por segundo. Resolvi mudar pra uma Sony A7iii com adaptador pras lentes da Canon pra poder reaproveitar minha Sigma de 24 milímetros e a cinquentinha, ambos de F 1/8. A câmera da Sony é mais nítida, cores melhores e grava em 4K a 60 frames por segundo. Mais do que isso, a Canon era uma câmera crop e essa Sony é full frame.</p>
<p>A idéia não vai ser aumentar a resolução dos videos e sim fazer downsampling de 4K pra Full HD pra ficar com imagem de melhor qualidade. Downsampling é quando você pega uma imagem grande e diminui ela, com isso você ganha em nitidez. Também vou ter mais flexibilidade pra poder mover o video, fazer zoom e coisas assim sem perder nitidez. Agora, processar videos de 4K vai justificar um pouco mais a RTX 3080 e o CPU i9.</p>
<p>Aliás, este é o primeiro video que eu gravo com essa câmera, vocês notaram alguma diferença? Eu escrevi o script e estou gravando antes de ver como realmente vai ficar, então vou saber junto com vocês assistindo depois. Como é a primeira vez eu ainda tô aprendendo a melhor forma de usar ela. No geral eu tô bem satisfeito com esse upgrade. Se quiser gastar mais, ainda dá pra fazer muito melhor que isso, mas pros meus usos o que eu montei já tá sobrando bastante. Vamos ver se ela permanece a mesma por mais uns 2 ou 3 anos pelo menos. Se ficaram com dúvidas mandem nos comentários abaixo, não deixem de assinar o canal e compartilhar o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>hardware</category><category>performance</category><category>akitando</category></item><item><title>[Akitando] #46 - Gerenciamento de Memória (Parte 2) | Entendendo Back-end para Iniciantes (Parte 6)</title><link>https://akitaonrails.github.io/2019/04/03/akitando-46-gerenciamento-de-memoria-parte-2-entendendo-back-end-para-iniciantes-parte-6/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/04/03/akitando-46-gerenciamento-de-memoria-parte-2-entendendo-back-end-para-iniciantes-parte-6/</guid><pubDate>Wed, 03 Apr 2019 17:01:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/DGU1awKrNiA"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente, chegamos no último episódio do tema de Back-End!&lt;/p&gt;
&lt;p&gt;Devo dizer que este foi um dos episódios que eu mais queria explicar. Toda nova linguagem hoje em dia tem Garbage Collector. Mas a maioria dos programadores não tem a mínima idéia de como eles funcionam.&lt;/p&gt;
&lt;p&gt;Mais importante: todos acreditam que garbage collectors são mágicos e &amp;ldquo;simplesmente funcionam&amp;rdquo; mas não entendem quais são os reais motivos de porque eles existem, quais problemas eles resolvem, e quanto eles CUSTAM pro seu programa. Sim! Nenhuma mágica vem de graça.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/DGU1awKrNiA"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente, chegamos no último episódio do tema de Back-End!</p>
<p>Devo dizer que este foi um dos episódios que eu mais queria explicar. Toda nova linguagem hoje em dia tem Garbage Collector. Mas a maioria dos programadores não tem a mínima idéia de como eles funcionam.</p>
<p>Mais importante: todos acreditam que garbage collectors são mágicos e &ldquo;simplesmente funcionam&rdquo; mas não entendem quais são os reais motivos de porque eles existem, quais problemas eles resolvem, e quanto eles CUSTAM pro seu programa. Sim! Nenhuma mágica vem de graça.</p>
<p>Hoje vamos usar o que aprendemos até agora pra finalmente olhar linguagens como Objective-C/Swift, Python, Ruby, Java, Erlang/Elixir, Go e entender como eles diferem no gerenciamento de memória.</p>
<p>Links:</p>
<ul>
<li>Sun Java System Application Server 9.1 Performance Tuning Guide (<a href="https://docs.oracle.com/cd/E19159-01/819-3681/abeio/index.html"target="_blank" rel="noopener">https://docs.oracle.com/cd/E19159-01/819-3681/abeio/index.html</a>)</li>
<li>Garbage collection in Python: things you need to know (<a href="https://rushter.com/blog/python-garbage-collector/"target="_blank" rel="noopener">https://rushter.com/blog/python-garbage-collector/</a>)</li>
<li>What causes Ruby memory bloat? (<a href="https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html"target="_blank" rel="noopener">https://www.joyfulbikeshedding.com/blog/2019-03-14-what-causes-ruby-memory-bloat.html</a>)</li>
<li>Demystifying the Ruby GC (<a href="https://samsaffron.com/archive/2013/11/22/demystifying-the-ruby-gc"target="_blank" rel="noopener">https://samsaffron.com/archive/2013/11/22/demystifying-the-ruby-gc</a>)</li>
<li>Erlang Garbage Collector (<a href="https://www.erlang-solutions.com/blog/erlang-garbage-collector.html"target="_blank" rel="noopener">https://www.erlang-solutions.com/blog/erlang-garbage-collector.html</a>)</li>
<li>Go, don&rsquo;t collect my garbage (<a href="https://blog.cloudflare.com/go-dont-collect-my-garbage/"target="_blank" rel="noopener">https://blog.cloudflare.com/go-dont-collect-my-garbage/</a>)</li>
<li>Tuning glibc Memory Behavior (<a href="https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior"target="_blank" rel="noopener">https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior</a>)</li>
<li>The status of Ruby memory trimming &amp; how you can help with testing (<a href="https://www.joyfulbikeshedding.com/blog/2019-03-29-the-status-of-ruby-memory-trimming-and-how-you-can-help-with-testing.html"target="_blank" rel="noopener">https://www.joyfulbikeshedding.com/blog/2019-03-29-the-status-of-ruby-memory-trimming-and-how-you-can-help-with-testing.html</a>)</li>
<li>HOW THREE GUYS REBUILT THE FOUNDATION OF FACEBOOK (<a href="https://www.wired.com/2013/06/facebook-hhvm-saga/"target="_blank" rel="noopener">https://www.wired.com/2013/06/facebook-hhvm-saga/</a>)</li>
<li>Ruby Memory Environment Variables - Simpler Than They Look. (<a href="http://engineering.appfolio.com/appfolio-engineering/2018/6/27/ruby-memory-environment-variables-simpler-than-they-look"target="_blank" rel="noopener">http://engineering.appfolio.com/appfolio-engineering/2018/6/27/ruby-memory-environment-variables-simpler-than-they-look</a>)</li>
<li>&ldquo;WEAK, STRONG, UNOWNED, OH MY!&rdquo; - A GUIDE TO REFERENCES IN SWIFT (<a href="https://krakendev.io/blog/weak-and-unowned-references-in-swift"target="_blank" rel="noopener">https://krakendev.io/blog/weak-and-unowned-references-in-swift</a>)</li>
</ul>
<p>Olá pessoal, Fabio Akita</p>
<p>Este é o décimo primeiro episódio da série começando aos 40, parte 2 do tema de Gerenciamento de Memória, parte 6 e conclusão do tema de Back-end, finalmente!! Semana passada eu dei uma escovada de bits, expliquei um pouco sobre as diferenças de arquiteturas 64-bits e 32-bits, entendemos rapidamente sobre notação binária e hexadecimal, os diferentes tipos de memória e como os diferentes alocadores fazem seu trabalho pra resolver os problemas de fragmentação e alocação eficiente em ambientes com múltiplas threads reais.</p>
<p>No episódio de hoje vamos uma camada pra cima e explorar mais sobre os famosos garbage collectors, que muita gente ainda associa mais com Java, mas que a maioria das linguagens interpretadas e baseadas em máquinas virtuais possuem. O grande truque é balancear processamento e uso de memória. Se ficar tentando economizar memória, o collector precisa trabalhar mais e, por consequência isso diminui a performance geral da sua aplicação. Por outro lado, se o collector rodar pouco pra aumentar a performance geral, você vai acabar usando mais memória do sistema e em ambientes de servidor onde os processos ficam de pé por dias ou semanas sem descarregar, isso pode ser um problema também. Então, como eles fazem pra balancear os dois?</p>
<p>(&hellip;)</p>
<p>Recapitulando, no episódio anterior falamos um pouco das diferenças de alocadores como o ptmalloc2, tcmalloc do Google e o jemalloc que o Facebook usa. No fim, todo alocador vai querer resolver o problema de fragmentação, o problema de contenção de threads, o problema de cache entre threads. E agora temos os garbage collectors. Pense em garbage collectors como sendo um alocador em user-land. Ele faz syscalls pro sistema operacional, como o malloc() pra pedir blocos grandes de memória e internamente gerencia como esse pedação vai ser usado. Python, Ruby, Erlang, Javascript, .NET, Java e por consequência toda linguagem que roda em cima de Java como Scala, Clojure, Kotlin, usam o mesmo garbage collector da JVM. Assim como schedulers the coroutines em user-land são mais rápidos como eu já expliquei em episódios anteriores, um alocador em user-land também é mais rápido do que fazer syscalls pra funções no kernel-space como mmap ou munmap que é o que funções como malloc() fazem.</p>
<p>Todo garbage collector faz algumas coisas meio básicas. Não dá pra explicar no detalhe como o collector de cada linguagem funciona, mas vamos ver os conceitos comuns. Pra começar, existem linguagens que lidam com ponteiros, ou seja os tais endereços virtuais diretamente, e os que lidam com referências que são como se fossem atalhos pros endereços. Garbage collectors existem principalmente porque nós programadores somos muito ruins em liberar memória manualmente.</p>
<p>Se você teve que lidar com C, sabe que precisa chamar a função free() pra toda memória que deu malloc() antes. Esqueça de dar free e seu programa vai começar a ter leaks ou vazamentos e vai ficar crescendo sem parar. Parece super simples, mas toda regrinha desse tipo você pode ter certeza que vai esquecer. Basta esquecer de digitar uma linha ou apagar uma linha do código com free por acidente. Ou pior, se você der free numa memória que ainda não era pra dar free, vai ficar com um Invalid Pointer por exemplo e seu programa pode crashear. Quando você adiciona classes e objetos em cima do C e gera um C++ a coisa fica pior ainda porque você vai passando ponteiros de objetos pra outros objetos numa árvore gigante de dependências. Quando que você pode destruir um objeto sem deixar outro que dependia dele corrompido?</p>
<p>Uma das formas de resolver esse problema é adicionar a todo objeto um contador. E toda vez que você passar esse objeto pra outro objeto ou função, incrementar o contador. Quanto mais objetos apontarem pra esse primeiro, maior vai ficando o contador. À medida que os objetos que apontam pro primeiro também forem sendo liberados, o contador vai decrementando. E quando o contador voltar a ser zero, daí você sabe que ninguém está mais usando e pode liberar esse objeto. Como estamos falando de contagem de referências, você vai ouvir falar nessa estratégia como Reference Counting. Linguagens como Python e Objective-C e, por consequência, Swift usam essa estratégia.</p>
<p>No caso do Objective-C ele vai um passo além, ele inclui uma classe chamada NSAutoreleasePool, um tanque de alocação. Você instancia um pool e à medida que vai alocando novos objetos esse pool fica sabendo deles. Diferente de um garbage collector, em Objective-C você manualmente instância quantos pools quiser, e tem que manualmente chamar release no objeto que, diferente da tal função free(), vai só decrementando o contador de referência. E quando quiser pode chamar o método release do pool, que por sua vez dá free de verdade nos objetos com contador zerado.</p>
<p>Nas versões mais novas do Objective-C e no Swift, coisa da última década, a Apple adicionou o ARC ou Automatic Reference Counting. Na prática ele vai cuidar de adicionar o código pra dar release do contador usando algumas dicas no seu código, durante a compilação. Vale a pena estudar em detalhes como o ARC funciona se você faz programas pra iPhone ou Mac, mas 90% dos casos você não se preocupa tanto com a memória e vai precisar saber mais quando tiver bugs ou quiser otimizar o uso de memória, por exemplo forçando a limpeza do pool mais vezes. Digamos que você está lendo um arquivo com 500 linhas, em vez de fazer o loop e só no final limpar tudo de uma vez, você pode ir dando release no pool manualmente a cada 50 linhas por exemplo e evitar que o sistema aloque mais memória do que precisa. Se você desaloca memória com muita frequência, vai usar mais CPU e diminuir a performance geral, por outro lado vai consumir menos memória. Se você desalocar com pouca frequência, aumenta a performance, mas também aumenta o uso de memória.</p>
<p>Um problema de contagem de referências é o caso de reference cycle ou seja, você tem objeto A com uma referência pro objeto B daí no B fazemos uma referência pro objeto A, agora ambos os contadores de A e de B nunca vão ser menores que 1, e o pool não vai conseguir desalocar a memória desses objetos mesmo que ninguém mais esteja usando, porque os contadores não são zero. Por isso em Objective-C e Swift você tem a diferença entre strong references que são as referências normais. Mas também temos referências weak e unowned, nenhuma das duas incrementa o contador e isso quebra o ciclo de referências. No caso a referência do objeto B pro objeto A poderia ser declarada como Weak e daí o contador de A chegaria a zero normalmente e ambos poderiam ser limpos. Novamente, você precisa ler em mais detalhes a diferentes de uma referência Weak e Unowned que vou deixar nas descrições abaixo. Linguagens com contadores de referência como Python costumam ter a opção de referências fracas ou Weak.</p>
<p>Controlar os contadores de uso de cada objeto é uma forma de você não precisar manualmente liberar objeto a objeto quando não precisa mais. Basta ter um controlador como o NSAutoreleasePool que consegue limpar todo objeto com contador zerado de uma vez só. Em C++ temos smart pointers que funcionam mais ou menos parecido. Um garbage collector muito simples poderia simplesmente ser uma thread que, de tempos em tempos, manda limpar esse pool. Seria uma forma rudimentar de garbage collector. Mesmo Python trabalha com contador de referências também, mas seu collector é mais sofisticado do que só isso.</p>
<p>Eu falei que em Objective-C você pode criar quantos pools quiser e controla eles manualmente. Outra forma é esse Pool ser tipo uma estrutura global e única, é como funciona em Ruby e em Java e acho que em .NET também. Pense numa lista gigante, onde sempre que precisarmos de memória pedimos pro alocador que vai adicionando nessa lista também, catalogando todos os elementos que vão sendo alocados. Toda vez que você passa a referência desse elemento pra outro objeto, ele vai criando uma árvore de dependência. Objeto A pode ter referências pros objetos B e C, e o objeto C tem referência pro objeto D. Depois de algum tempo, a variável que aponta pra A deixa de existir, por exemplo o método retornou ou você passou nulo pra variável e então ninguém mais aponta pro objeto A.</p>
<p>Com o tempo vários objetos vão ficando nesse estado, onde ninguém mais aponta pra eles a não ser a lista global que falei. Então de tempos em tempos um processo especial chamado collector, numa thread no interpretador ou na VM, poderia ir vasculhando essa lista por todos os objetos que ninguém mais está apontando e ir só marcando. Depois um segundo processo poderia ir na mesma lista, elemento a elemento, desalocando os objetos que foram marcados. Então se a lista tinha 1000 objetos, cada um ocupando 1 slot da memória, na fase de marcação 500 objetos foram marcados como não usados, e na fase de limpeza esses são liberados os 500 slots pra uso. Sendo ultra simplista é isso que chamamos de um garbage collector Mark and Sweep. É um collector de duas fases, onde em cada fase ele precisa vascular elemento a elemento da lista de alocação, fazendo um mark na primeira fase e sweep na segunda.</p>
<p>Porém, quando eu falo desalocar não quer dizer devolver memória pro sistema operacional. Lembra do problema de fragmentação? Se eu ficar devolvendo memória toda hora, muito rapidamente a memória do processo vai ficar fragmentado. Linguagens como Ruby ou Java começam pedindo um certa quantidade de blocos de memória pro sistema operacional. No caso de Ruby tem o equivalente de chunks que ele chama de slots, cada slot tem um tamanho fixo de 40 bytes onde cabe toda a estrutura que define um objeto de Ruby.</p>
<p>Então ele só lida com slots fixos de 40 bytes. Um conjunto desses slots é chamado de Heap. Não confundir com o Heap do malloc de C, é o mesmo nome mas tanto Java quanto Ruby chamam conjuntos de slots ou chunks de Heaps. No caso do Ruby existem várias variáveis de sistema pra configurar o alocador, como por exemplo o RUBY_GC_HEAP_INIT_SLOTS, que por padrão o valor é 1000. Um heap de Ruby costuma ter 16 kbytes. Ou seja, ele vai alocar de 40 bytes x 1000 que seria quase 40 kbytes ou pelo menos 2 heaps.</p>
<p>Se acabar os slots disponíveis, o alocador do Ruby vai pedindo pra alocar mais heaps, vários slots de uma só vez. Digamos que ao carregar seu programa ele precisou de 10 mil heaps, ou seja, uns 156 megabytes. Agora digamos que depois de rodar por um tempo, uns 500 mil slots de objetos foram marcados como não usados mais. Na fase de Sweep você imaginaria que ele vai devolver 500 mil x 40 bytes que dá quase 20 megabytes, de volta pro sistema e em vez de ocupar 156 megabytes ele vai passar a usar 136 megabytes. Mas não, no final do Sweep seu programa vai continuar usando os mesmos 156 megabytes. Por que? Porque ele só vai liberar os slots da lista global, mas a memória de verdade continua alocada. Quando você criar novos objetos nos slots livres, ele vai reusar e escrever em cima do mesmo trecho de memória de antes.</p>
<p>De cara temos estratégias diferentes entre o Heap de uma linguagem como Ruby  ou o Heap de uma linguagem como Java. No caso de Java, todos os objetos ocupam espaço no Heap. No caso de Ruby só a estrutura e dados muito pequenos ocupam os slots de 40 bytes. Mas um array ou um string costuma ser muito maior que 40 bytes, então o Ruby chama a syscall malloc do sistema operacional e guarda o endereço alocado no slots de 40 bytes apontando pro espaço fora do Heap. A vantagem do Ruby é que todo objeto tem o mesmo tamanho dentro do Heap e isso ajuda a não fragmentar tanto, mas ele depende do alocador do sistema operacional pros blobs de dados maiores. No caso do Java ele não tem que se preocupar com o sistema operacional, porque ele usa só sua memória interna do Heap, mas outro lado isso vai causar fragmentação interna e ele tem uma forma de evitar isso, que vamos ver já já. Mas só pra entender que diferentes collectors organizam seus dados internamente de formas diferentes.</p>
<p>Do ponto de vista de programação, se você ainda não entendeu, alocar memória desnecessariamente vai aumentar o tamanho dos heaps ou a quantidade de heaps, dependendo do alocador, e isso vai bloquear memória do sistema operacional e causar fragmentação desnecessária também. Você não pode achar que vai alocar 1 giga de dados, devolver esses 1 giga, e vai continuar tendo bonitinho 1 giga de novo sobrando. Você vai fazer o alocador ter que trabalhar muito pra evitar a fragmentação e se seu sistema tiver pouca memória geral, provavelmente não vai conseguir ter esse espaço livre de volta.</p>
<p>Exemplos de má programação é o famoso caso de abrir um arquivo gigante de vários gigas na memória, ou conectar num banco de dados e não fazer o select com os critérios de where certos e acabar puxando muito mais linhas da tabela do que precisava. Tudo isso vai ocupar espaço, o alocador vai ter que esticar os heaps, mais memória do sistema vai ser consumido, e não necessariamente ele vai liberar essa memória de volta pro sistema. Mesmo depois que o collector limpar os slots ocupados por esses dados, você ficou com um heap inchado e fragmentado. E se seu servidor for pequeno, você vai começar a ter problemas de memória mesmo que teoricamente tenha “memória sobrando” ainda.</p>
<p>Se seu programa rodando em produção tem leaks de memória, normalmente são dados que vem da rede, como dados do banco de dados, ou processamento de arquivos grandes. O ideal é sempre seu código tentar usar memória até um limite. E quando precisa puxar dados externos, ter algumas checagens pra ver se não vai usar memória desnecessariamente. Tipo checar o tamanho de um arquivo antes de carregar ele inteiro na memória. Se for arquivos pequenos de alguns kbytes, dá pra ler tudo e armazenar numa variável de string. Mas se for um arquivo de gigabytes, é melhor abrir como um Stream e ir lendo pedaços do arquivo de cada vez. Ou quando precisa puxar dados de uma tabela num banco de dados, usar coisas como um limit no select pra limitar quantas linhas vai puxar de uma só vez. Famoso caso que o amador fala que na máquina dele funciona, só que na máquina dele ele só testou com arquivos pequenos e com tabelas com 10 linhas de teste. Por isso quando vai pra produção tem esse comportamento. Isso porque é um programador que não tem noção de gerenciamento de memória.</p>
<p>Voltando, outro problema é se seu programa é grande e aloca milhões de objetos. Toda vez que o processo de Mark e Sweep rodar, eles precisam passar por todos eles, um por um, toda vez. Mesmo que só leve uma fração de milissegundo pra marcar e sweepar cada objeto, ainda assim vai levar dezenas de segundos pro processo todo se contar milhões de objetos alocados. É muita coisa. Pior ainda, toda vez que você vai marcar os objetos ou limpar, precisa pausar o programa todo, é o que chamamos de Stop the World. Isso porque você tem que garantir que o programa rodando ao mesmo tempo que o collector não se corrompa tentando acessar um objeto que acabou de ser desalocado. Só que se você pausar seu programa por muitos segundos, ele vai ficar muito lento.</p>
<p>Ou seja, carregar dados desnecessários não só desperdiça memória como desperdiça performance. Agora você só tem 2 escolhas. A primeira é rodar poucas vezes o Mark e Sweep, mas aí você arrisca consumir muita memória e fazer seu heap crescer demais já que sempre que precisa de mais memória e não tem slots pra reusar ele vai pedir mais pro sistema operacional. E eu já disse que toda memória que ele aloca do sistema ele não devolve mais ou vai demorar pra devolver pra evitar fragmentação, daí seu processo vai crescer mais do que devia. A segunda escolha, se você quer evitar isso então tem que rodar mais vezes o Mark e Sweep só que daí a performance geral do programa vai ficar lento. Ou seja, agora você tem que escolher entre performance ou uso de memória.</p>
<p>Pra resolver esse problema é que existe a idéia de Generational Garbage Collectors. Ele vai dividir a memória interna do interpretador ou da máquina virtual em 2 ou mais gerações. Java divide seus heaps em grupos chamados Young, Old e Permanent (por isso você vira e mexe vê configuração em tutorial de JVM pra configurar coisas como o Permsize que é o tamanho desse espaço permanente). O garbage collector G1GC novo do Java chama de Eden, Survivor e Old. Python chama só de geração zero, um e dois.</p>
<p>A idéia vem de uma hipótese chamada Weak Generational Hypothesis. Em linhas gerais essa hipótese diz que objetos novos tendem a morrer jovens e objetos velhos tendem a ficar ativos por muito tempo. Parece óbvio quando falamos assim, né? Eu já expliquei como o sistema operacional divide a memória em arenas por exemplo, pra evitar contenção de threads. Agora vamos dividir mais ainda pra evitar passagens desnecessárias por objetos que não vão morrer tão cedo.</p>
<p>O problema que um generational garbage collector quer resolver é diminuir ao máximo as pausas da fase de mark and sweep. A forma de fazer isso é evitar ficar olhando objetos que nunca vão ser limpos. Por exemplo, objetos singleton que seu programa carrega no começo e vai usar até morrer. Num Ruby, toda classe é um objeto. Mas as classes raramente precisam ser limpas, então pra que perder tempo olhando pra elas toda vez? Em resumo, na primeira vez que o mark passar, ele vai ter que olhar tudo. Objetos que sobrevivem a múltiplas passagens da fase de mark podem ser promovidos da geração Nova pra geração Velha e se sobreviverem mais algumas passadas de mark, podem ser promovidas pra geração permanente.</p>
<p>Podemos dividir as fases de Mark em Minor e Major. O Minor só olha pros objetos mais novos e ativos na geração Nova. O Major olha também pra geração Velha pra ver se algum precisa ser limpo. Mas uma ver promovido pra permanente, podemos esquecer desses objetos. Digamos que um programa normal tenha 100 mil objetos, e que depois de algumas passadas da fase de Mark 50 mil deles tenham sido promovidos pra geração permanente. Os outros 50 mil são objetos que morrem, e novos que são criados e na média ficam sempre uns 20 mil na geração nova, uns 30 mil na geração velha. A performance total da fase de mark é pelo menos o dobro, porque você no mínimo vai passar por metade dos objetos toda vez. E tende a ser mais rápido que isso porque você passa menos vezes na geração velha também. Então é um ganho enorme de performance.</p>
<p>Numa linguagem como Ruby ou Python, esses segmentos como eu disse são só listas com ponteiros pros slots ocupados. Como por baixo é tudo estrutura em C temos endereços e como as extensões em C podem precisar do endereço exato de determinada estrutura não podemos mover os dados de lugar.</p>
<p>Eu menciono isso porque em Java temos uma coisa extra. Java não lida diretamente com ponteiros, ele trabalha com referências no nível da linguagem. Uma variável em Java aponta pra uma referência e a referência, por baixo dos panos, é indexada num endereço. Mas o endereço pode mudar. Diferente de Ruby ou Python, em baixo nível o alocador pode mover o objeto de um slot pra outro nos heaps. Mover fisicamente vai mudar o endereço e aí eu mudo na referência. Mas no alto nível a variável no Java só vê a referência então pra ele nada mudou. Uma coisa é eu dar um produto na mão de uma pessoa. Agora não posso mais tirar o produto da mão dela sem ela reclamar. Outra coisa é eu dar um vale produto pra pessoa. Agora no estoque tanto faz se eu mudar o produto de lugar, o que vale é a referência.</p>
<p>Isso é importante porque na JVM quando acontece a fase de promover os objetos da geração jovem pra geração velha, em vez de só mover o ponteiro de um segmento pra outro de uma lista, na verdade eu estou movendo o objeto de um slot da geração jovem pra um slot da geração velha, fisicamente falando. Estou copiando de um slot pro outro e depois liberando o primeiro slot. Então fisicamente na memória, os heaps de Java tendem a ser menos fragmentados que num Python ou Ruby.</p>
<p>E os objetos de Java podem alocar tudo dentro do objeto em vez de usar a estratégia de dividir o objeto em 2 partes, sendo uma estrutura de tamanho fixo e o resto dos dados alocados fora dos heaps via malloc do sistema operacional. Nesse caso os objetos de Java tem tamanho variável, o que causaria fragmentação como eu já falei mas como ele pode mover os objetos de lugar e ir defragmentando a cada passada do collector, então evitamos fragmentação. É como se o collector do Java fosse também ao mesmo tempo um defragmentador e de fato ele tem uma fase chamada compaction, que move objetos de lugar pra defragmentar se o coletor perceber que o heap acumulou muita fragmentação.</p>
<p>Se não ficou claro, nos diagramas que mostrei até agora, parece que todos os objetos estão movendo de um espaço de memória pra outro. Isso é verdade no Java. Mas num Ruby os objetos não se movem de lugar, os endereços são fixos. Faz de conta que existam listas como uma lista ligada ou um array grande. Cada elemento da lista é um endereço pra memória real, e as listas são as gerações. Então quando eu falo que o objeto é promovido de uma geração pra outra, na memória real nada acontece, só o endereço que sai de uma lista e vai pra outra lista. Num Java, a estratégia de gerações a ajuda a diminuir fragmentação porque à medida que os objetos são promovidos eles movem de lugar na memória e isso vai defragmentando. Mas num Ruby a fragmentação se mantém a mesma, o que vai melhorar é a velocidade do Mark and Sweep.</p>
<p>Esse tipo de processo é o que chamamos de um Compact Copying Collector. A principal função é diminuir a fragmentação da memória. Se você tinha curiosidade, os garbage collectors do Erlang e do .NET são similares. Eles são generational, e tem copy collectors. Os princípios são os mesmos. Só linguagens que lidam com ponteiros e endereços físicos diretamente como as linguagens que compilam nativamente sem um runtime que abstrai a memória ou os interpretadores, não tem como fazer compact ou copy e tendem a fragmentar mais. Não quer dizer que é impossível fazer a parte de copy. Se você mudar um objeto de lugar, ele ganha um novo endereço, agora você precisa garantir que quem tente acessar o endereço antigo caia no novo, algo como uma barreira de leitura. Um dos meus problemas com acesso a ponteiros não é só por aritmética de ponteiro, mas porque dificulta defragmentação via compact copying. Era o que o Go pretendia fazer, mas se não me engano até agora eles não implementaram a fase de copy.</p>
<p>Além disso podemos executar partes dessas fases concorrentemente e paralelamente usando threads reais. Por isso em JVM você ouve falar de nomes como Parallel GC ou ConcurrentMarkSweep GC. O primeiro usa múltiplas threads. O segundo só uma thread, mas ele pausa o mundo só na fase inicial de mark e sweep, mas depois ele roda paralelamente à aplicação. JVM pra servidor usava o Parallel GC por exemplo. Hoje em dia temos o G1GC que usa gerações menores de segmentos 0, 1 e 2 ou Eden, Survivor e Old como expliquei acima, mas ele usa múltiplos segmentos de cada geração em vez de segmentos contíguos e outros algoritmos.</p>
<p>O problema de rodar concorrentemente é a fase de Mark and Sweep, que exige a tal pausa do programa ou Stop the World. Pra evitar isso, linguagens como Erlang implementam uma versão melhor que é mais custosa mas evita a pausa, chamada Tracing collector. Pense uma fase de mark dividida em 3 fases, como se fossem gerações, usando alguma variação do algoritmo chamado Tri-color, onde determinamos cores como preto, cinza e branco e no mark os objetos são movidos pra preto ou branco, e no final quando nenhum objeto for cinza, podemos fazer sweep de todos os objetos que sobraram com a cor branca. A vantagem disso é que podemos fazer essa marcação sem precisar pausar o programa. O Incremental Mark and Sweep do Ruby usa estratégia de Tri-color pro que se chama de Minor GCs ou fases de mark mais rápidas.</p>
<p>O collector do Java tem vários outras estratégias além do que expliquei até agora. De qualquer forma, o que chamamos de um garbage collector, completo, poderia ser um “Parallel Concurrent Generational Incremental Mark and Sweep Copy Compactor Collector”. Ruby e Python fazem algo similar, com a limitação de não poderem ser copy collectors nem rodar collectors concorrentemente. Javascript faz a mesma coisa também.</p>
<p>Generational garbage collectors tendem a usar mais memória pra manter todas as estruturas e segmentos pra cada geração. Ou seja, é um overhead mais alto do que o jeito do Objective-C e Swift. Linguagens compiladas nativamente com um runtime pequeno como C ou Objective-C tendem a depender principalmente do alocador do sistema operacional como o ptmalloc2 ou jemalloc. Linguagens interpretadas são híbridos, ele tem um espaço de heaps alocados com alocador em user-land pra gerenciar os objetos mas usam o alocador do sistema operacional pra blobs maiores como blocos de texto ou arrays grandes.</p>
<p>Uma JVM, se não me engano, pede pro sistema operacional alocar um espaço grande no começo do boot, mas depois ele se vira só em user-land. Vai usar mais memória, mas como não depende de syscalls, tende a ser mais rápido. Ao mesmo tempo, linguagens compiladas e interpretadas dependem do alocador do sistema operacional pra gerenciar a fragmentação de memória como já expliquei no episódio anterior. Uma JVM controla a fragmentação ele mesmo via o processo de copy collector onde ele consegue mover os objetos de lugar.</p>
<p>Falando no alocador do sistema operacional, o ptmalloc2 que é o malloc do glibc que a maioria das linguagens usa, aloca arenas pra cada thread real como já expliquei no episódio anterior. Pior ainda, ele não tem thread-cache ainda como o tcmalloc ou jemalloc. Portanto quanto mais arenas ele alocar, mais memória vai desperdiçar. E o multiplicador pra máquinas 32-bits são de 2 vezes o número de threads reais ou seja, máquinas quad-core com hyperthreading que dá 2 threads por core significa pelo menos 16 arenas. Mas em máquinas 64-bits o multiplicador é 8 então são 8 vezes 4 cores vezes 2 threads que dá 64 arenas!! É muita arena. Em estudos do Heroku que é uma plataforma de serviços para deployment de aplicações em ambiente virtualizado eles recomendam diminuir o teto de arenas. Em máquinas pequenas, com 2 cpus virtuais, em testes empíricos ficou notável que configurar o teto pra 2 arenas é mais que suficiente.</p>
<p>Segundo Hongli Lai, CTO da Phusion, o motivo desse multiplicador ser tão alto pode ser porque o principal desenvolvedor do alocador de memória é da Red Hat que vende produtos e serviços pro mundo enterprise. Lá a prioridade é aumentar performance mesmo que isso custe desperdiçar muita RAM. 10% a mais de performance por 30 vezes mais memória é uma conta que o mundo Enteprise topa pagar. Mas pra gente que precisa balancear performance com uso de memória pra escalar, a recomendação é simples, basta exportar a variável MALLOC_ARENA_MAX pra 2 e pronto. Só isso já vai evitar muitos vazamentos de memória por mau uso de arenas.</p>
<p>Gerenciamento de memória é um assunto absurdamente longo. Até este ponto eu expliquei tudo em linhas gerais. Cada parágrafo deste episódio tem uma literatura gigante que você vai precisar procurar pra entender em mais detalhes. Eu falei de copy collectors numa linha, como se fosse algo trivial, mas não é. Eu falei de thread-cache de malloc como se fosse uma correção simples do ptmalloc2 pro tcmalloc, mas não é. Eu falei como o jemalloc é mais rápido que o tcmalloc, mas procure os relatos de Jason Evans pra ver o tanto de trabalho que eles tiveram pra reescrever várias partes, várias vezes, até realmente chegar na versão atual. Alocadores de memória são super fáceis de escrever errado, de um jeito que vai ser super lento e consumir gigabytes de RAM à toa e ficar vazando em servidores. Nunca tente escrever seu próprio alocador a menos que tenha toneladas de dinheiro e tempo pra jogar fora.</p>
<p>Por outro lado, você precisa se interessar em estudar todas as formas que seu alocador funciona. Não basta ver quanto seu processo consome quando sobe. À medida que ele roda o garbage collector vai movendo objetos entre gerações, vai alocando memória do sistema operacional pras suas heaps internas que ele não vai devolver pro sistema operacional ou vai demorar pra devolver. Você precisa achar os pontos no seu programa que geram picos de consumo de memória, porque eles que vão pressionar o tamanho do heap e desperdiçar memória do sistema operacional.</p>
<p>Em última instância, você vai lembrar que todo collector tem dezenas configurações pra fazer tuning. Tamanho inicial do heap, fator de crescimento dos heaps, frequência pra rodar os collectors, quantidade de arenas, etc. Normalmente a configuração que já vem é o suficiente pra 99% das aplicações. Não seja ingênuo de achar que seu programa vai ficar muito melhor fazendo tuning dos alocadores ou garbage collectors, praticamente em todos os casos é seu código que desperdiça memória e tuning nenhum vai resolver isso. Mas no 1% dos casos que você já mediu exaustivamente o comportamento da aplicação em produção com dados reais, já corrigiu todo buraco de código possível, então talvez exista alguma chance de um tuning ajudar um pouco. Foi assim que na comunidade Ruby por exemplo descobrimos sobre o tuning de arenas do ptmalloc2 ou trocar pra jemalloc.</p>
<p>Em ambientes móveis, smartphones, é pior ainda, porque você não tem disco pra fazer swap. Significa que diferente de desktops e servidores, se seus programas foram mal feitos e alocarem memória de pico demais sem desalocar, o OOM ou Out of Memory Killer vai entrar em ação e matar seu programa com muita frequência, tornando a experiência do usuário uma droga. Por isso eu particularmente não gosto das soluções antigas de abrir aplicações que são só casca pra um navegador web e dentro carregar uma aplicação Javascript. No Javascript você não tem controle sobre o garbage collector e não tem acesso ao pool de memória como no num app nativo de iOS onde você pode mexer nos NSAutoreleasePool, significa que você está à mercê de um generational garbage collector, que usa mais memória que um app nativo, vai ter pausas de mark and sweep, e um Just in time compiler que também está alocando memória e usando mais CPU o tempo todo, porque otimizar código não sai de graça. Tudo tem um preço. Quanto mais alguma coisa é conveniente, menos ele tende a ser eficiente.</p>
<p>Quando você está programando pra desktop, que hoje em dia é tudo 64-bits, com muitos endereços virtuais sobrando, mesmo que falte RAM, seu sistema operacional vai jogar tudo pra arquivos de swap em disco. Fica lento, mas nada crasheia, por isso hoje em dia ficou comum fazer todo aplicativo desktop ser uma casca de navegador também e rodar apps web dentro. É o que Spotify, ou Slack ou os editores de texto Atom fazem. Consome memória pra caramba e quanto mais tempo eles ficam rodando mais memória vão consumindo. Por outro lado são super convenientes de fazer e você não precisa controlar memória manualmente. Eu ainda tenho preferência por programas nativos bem feitos que alocam memória de formas eficientes sem a necessidade de usar um garbage collector custoso. Um garbage collector é conveniente, mas não é necessário, especialmente em programas desktop.</p>
<p>Como eu já disse antes, tudo é uma questão de trade-off, você troca conveniência por mais lentidão e mais memória. Quanto menos conveniente for, mais você ganha performance e usa menos memória. Não existe um vencedor óbvio, cada caso é um caso. É um aplicativo pra servidor? Pra desktop? Pra smartphone? É grande? É pequeno? Precisa trabalhar quantidades grandes de dados com muita frequência? Precisa coordenar o processamento desses dados paralelamente? Ou de forma distribuída? Tudo isso afeta o que você vai escolher.</p>
<p>Com isso você também deve entender porque eu disse no episódio passado que linguagens com máquinas virtuais como Java ou Erlang preferem tomar conta de todos os recursos da máquina, porque o ideal é deixar eles alocarem toda a memória disponível pra gerenciarem os recursos internamente com seus garbage collectors. Percebam que eles levam pra user-land todas as atribuições do sistema operacional no kernel space. Eles fazem poucas syscalls porque conseguem gerenciar coisas como corotinas e gerenciar memória tudo em user-land.</p>
<p>O Go usa as funções de alocação como um malloc faz e prefere ficar mais baixo nível, mas ao mesmo tempo evitando depender demais do sistema operacional. Ele evita um pouco da fragmentação porque divide a memória em slabs dependendo do tamanho dos chunks como o ptmalloc e outros mallocs fazem. Mas diferente de Java e Erlang ele não tem e vai ter trabalho pra implementar um copy collector porque expõe ponteiros. É o mesmo motivo de porque Python ou Ruby também Não tem copy collectors. Generalizando um pouco, linguagens interpretadas ou com máquinas virtuais tendem a usar mais memória. Linguagens que não expõe ponteiros podem implementar compact copying collectors e evitar mais fragmentação ao custo de usar mais processamento.</p>
<p>E finalmente, chegamos ao último episódio do tema de Back-end! Gerenciamento de Memória era o último assunto pra vocês finalmente terem mais entendimento do funcionamento da máquina e sistema operacional. A discussão sobre linguagens só faz sentido se você entende essas coisas, senão a discussão fica mais parecendo fofoca de telenovela, sem argumentos e só repetindo frases de efeito que alguém disse num tweet ou blog post. Todo mundo só sabe até o ponto que a linguagem tem ou não garbage collector. E todo mundo só acredita que ter um generational garbage collector é bom. Mas ninguém sabe explicar porque é bom e o que você paga tem troca.</p>
<p>Mais do que isso, sem entender concorrência, paralelismo e gerenciamento de memória, você também não consegue entrar direito em devops e lá também vai ficar só seguindo superstições, mandingas, e repetindo o que ouviu os outros falar sem realmente saber o que está fazendo. Entender como a máquina e o sistema operacional realmente funcionam, pra mim, é o que começa a dividir as crianças dos adultos.</p>
<p>Se você tem dúvida mande nos comentários abaixo, se curtiu mande um joinha, compartilhe com seus amigos, não esqueça de assinar o canal e clicar no sininho pra não perder os próximos episódios. A gente se vê semana que vem, até mais.</p>
]]></content:encoded><category>performance</category><category>linguagens-de-programacao</category><category>ciencia-da-computacao</category><category>akitando</category></item><item><title>[Akitando] #45 - Gerenciamento de Memória (Parte 1) | Entendendo Back-end para Iniciantes (Parte 5)</title><link>https://akitaonrails.github.io/2019/03/27/akitando-45-gerenciamento-de-memoria-parte-1-entendendo-back-end-para-iniciantes-parte-5/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/03/27/akitando-45-gerenciamento-de-memoria-parte-1-entendendo-back-end-para-iniciantes-parte-5/</guid><pubDate>Wed, 27 Mar 2019 17:01:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/9AK_1gqEfkQ"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Eu disse no episódio anterior que não sabia se ia falar sobre Gerenciamento de Memória, mas acho que não dá pra terminar a série e não falar disso. Então hoje vamos escovar bits um pouco. Como a série é pra iniciantes, vale revisitar um pouco sobre representações binárias e hexadecimal e entender mais sobre como o computador e seus programas enxergam essa tal de &amp;ldquo;memória&amp;rdquo;.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/9AK_1gqEfkQ"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Eu disse no episódio anterior que não sabia se ia falar sobre Gerenciamento de Memória, mas acho que não dá pra terminar a série e não falar disso. Então hoje vamos escovar bits um pouco. Como a série é pra iniciantes, vale revisitar um pouco sobre representações binárias e hexadecimal e entender mais sobre como o computador e seus programas enxergam essa tal de &ldquo;memória&rdquo;.</p>
<p>Hoje quero explicar como os principais alocadores de memória do Linux funcionam e ensinar o que são os principais desafios que envolvem o gerenciamento de memória. Tudo isso vai ser importante pra semana que vem que vou explicar sobre garbage collectors.</p>
<p>Erratas:</p>
<ul>
<li>
<p>aos 11:39 min o áudio fica ruim por 1 minuto. Eu gravo o áudio num gravador externo e mantenho o áudio da câmera pra sincronizar. Mas meu gravador parou nesse minuto e fiquei só com o áudio da câmera, por isso que a qualidade cai, mas é só 1 minuto.</p>
</li>
<li>
<p>a 26:50 min aparece um typo &ldquo;Stacjk&rdquo; que na verdade é &ldquo;Stack&rdquo;</p>
</li>
</ul>
<p>Links</p>
<ul>
<li>The 640K memory limit of MS-DOS (<a href="https://www.xtof.info/blog/?p=985"target="_blank" rel="noopener">https://www.xtof.info/blog/?p=985</a>)</li>
<li>The difference between booting MBR and GPT with GRUB (<a href="https://www.anchor.com.au/blog/2012/10/the-difference-between-booting-mbr-and-gpt-with-grub/"target="_blank" rel="noopener">https://www.anchor.com.au/blog/2012/10/the-difference-between-booting-mbr-and-gpt-with-grub/</a>)</li>
<li>How Linux system boots up? (<a href="https://medium.com/@garimajdamani/how-linux-system-boots-up-f15c9e0f7a96"target="_blank" rel="noopener">https://medium.com/@garimajdamani/how-linux-system-boots-up-f15c9e0f7a96</a>)</li>
<li>Rebasing Win32 DLLs (<a href="http://www.drdobbs.com/rebasing-win32-dlls/184416272"target="_blank" rel="noopener">http://www.drdobbs.com/rebasing-win32-dlls/184416272</a>)</li>
<li>Commodore 64 memory map (<a href="http://www.awsm.de/mem64/"target="_blank" rel="noopener">http://www.awsm.de/mem64/</a>)</li>
<li>Physical Address Extension - PAE Memory and Windows (<a href="https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn613975%28v=vs.85%29"target="_blank" rel="noopener">https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn613975(v=vs.85)</a></li>
<li>A visual guide to Go Memory Allocator from scratch (Golang) (<a href="https://blog.learngoprogramming.com/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed"target="_blank" rel="noopener">https://blog.learngoprogramming.com/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed</a>)</li>
<li>Scalable memory allocation using jemalloc (<a href="https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919"target="_blank" rel="noopener">https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919</a>)</li>
<li>Understanding glibc malloc (<a href="https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/"target="_blank" rel="noopener">https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/</a>)</li>
</ul>
<p>Olá pessoal, Fabio Akita</p>
<p>Finalmente, estamos pertos de concluir o tema de Back-end dessa série Começando aos 40. Já estamos no décimo episódio pra falar sobre Gerenciamento de Memória. Mas diferente dos episódios passados que estavam dando quase 1 hora de duração, este eu resolvi quebrar em duas partes. Tá bem trabalho editar esse tanto de vídeo toda semana, e mesmo assim vai ser comprido hoje, então prestem atenção!</p>
<p>Durante os últimos episódios eu meio que dei uma introdução a coisas que vocês aprenderiam em  matérias como de sistemas operacionais numa faculdade de Ciências da Computação, então espero que aproveitem os tópicos que eu mencionei pra se aprofundar mais nos estudos desses temas.</p>
<p>Vou dizer que fiquei em dúvidas se deveria ou não me estender no tema de hoje, ele é ainda mais escovação de bits do que os temas anteriores. Mas eu sinto que é outro assunto que a grande maioria dos programadores, não só os iniciantes, até hoje não entendem direito. E o assunto é gerenciamento de memória. Todo mundo acha que porque suas linguagens costumam ter coisas como garbage collector, então é um assunto irrelevante. Mas isso não é verdade. Um programador amador realmente não vai prestar atenção nisso e em várias outras coisas, mas se você realmente tem a intenção de ser um verdadeiro profissional então sim, você precisa dominar esse assunto.</p>
<p>(&hellip;)</p>
<p>Recapitulando, eu já expliquei várias vezes como seu código fonte compila num binário e esse binário é carregado em memória. Também já rapidamente expliquei como CPUs e sistemas operacionais modernos alocam espaços de memória isolados pra cada processo, com endereços virtuais de tal forma que um processo não tem como acessar a memória de outro processo. Como eu já disse antes, se você realmente pretende levar a sério a profissão, precisa entender como seu computador realmente funciona.</p>
<p>Uma coisa que eu nunca mencionei por exemplo é o processo de boot em si, que é bem fascinante. Em hiper resumo, tem diversas etapas, do momento que você aperta o botão de ligar, segue o processo de Power on self test ou POST que você vai ouvir falar bastante se acompanha canais de montar seu computador, tem o teste de memória e outros dispositivos, até chegar no Master Boot Record que é o antigo MBR ou o atual GUID Partition Table ou GPT pra carregar o boot loader, que no caso do Linux seria um GRUB2 por exemplo, e finalmente carregar o binário do Kernel propriamente dito que, por sua vez, vai carregar um systemd, que por sua vez finalmente vai carregar todos os daemons pra terminar de tornar disponível os recursos da máquina pros programas, como montar suas partições do disco.  Se você está estudando Linux, veja os links que deixei na descrição abaixo que detalham esse processo em mais detalhes.</p>
<p>Eu estava pensando em quanto de detalhes quero mostrar hoje, mas se eu for realmente minucioso o episódio vai ficar com horas e horas de duração e vai ser extremamente tedioso. Em vez disso vou delinear os pontos principais e explicar resumidamente, você precisa pesquisar mais detalhes depois nos seus estudos.</p>
<p>Em termos de memória, entenda que você tem os pentes de RAM, o que você normalmente vê como pente de 16GB DDR4, DDR que significa Double Data Rate. De curiosidade eu já falei rapidamente antes que tudo no computador funciona segundo o clock da CPU. Sabe quando a gente fala em CPU de 4 Ghz? Sendo hertz um ciclo, 4 Ghz significa 4 bilhões de ciclos por segundo, pense em clock literalmente como um ciclo ou uma ida e volta de um pendulo, uma frequencia. Double Data Rate significa que é possível puxar dados da memória no topo do ciclo e na parte de baixo do ciclo, em vez de só conseguir puxar dados uma vez por ciclo como era antigamente, então ele tem a chance de puxar duas vezes, por isso é mais rápido, mas hoje em dia a gente só usa DDR de qualquer forma.  Uma diferença pra memórias de servidor são as memórias ECC que significa Error Correcting Code. É uma memória bem mais cara mas que garante que a memória nunca vai ser corrompida, pentes de RAM normais como as que usamos tem chances de devolver 1 bit errado de tempos em tempos, mas é raro, memória ECC de servidor tem uma proteção extra e menos chances.</p>
<p>O que o seu simples programa vê não é só RAM, é mais um “espaço de memória”, a CPU vai cuidar do que vai aonde. Você tem caches L1, L2 e L3 que são memórias pequenas e hiper rápidas que ficam no mesmo chip, pra fora dele você tem um barramento, que vai conectar com dispositivos como os pentes de RAM e com seu HD mecânico ou SSD que também pode servir de memória se conter um arquivo de SWAP. Se você não sabia disso memória é dividida normalmente entre acesso rápido e devagar. L1, L2 e L3 são memórias ultra rápidas. Quanto mais rápido mais caro, lógico, por isso eles são muito pequenos da ordem de até 64 quilobytes de L1, até meio megabyte de L2 e até 8 megabytes de L3. Eles não são memórias pra você usar, o CPU vai usar principalmente como cache pra manter seus pipelines cheios. Depois disso vem a RAM propriamente dita, seus pentes de DDR4, ordens de grandeza mais lentos que os caches, mas de uso geral. E depois vem coisas como o SWAP do seu SSD que é ordens de grandeza mais lento que a RAM.</p>
<p>Eu gostaria muito de explicar em detalhes o que são portas lógicas, como se chega num flip-flop, e como flip-flops podem ser usados pra armazenar bits. Pra ter um modelo na cabeça, pense em memória como um livro, com um espaço reservado pra um índice, e cada linha apontando pra uma página desse livro. Essas linhas são as linhas de endereço. Vamos entender essas linhas de endereço e como elas são organizadas.</p>
<p>Quando falamos que uma CPU é de 64-bits significa que os tais registradores da CPU acomodam números de até 64-bits de tamanho, e além disso temos barramentos, ou seja as rodovias de comunicação entre a CPU e a memória e dispositivos de I/O que também tem um tamanho máximo. 64-bits significa que a CPU consegue lidar com words ou palavras de 64-bits inteiros, que equivale a 2 elevado a 64 que seria o absurdo número de 16 Exabytes.  Pense assim, você tem bytes, quilobytes, megabytes, gigabytes, terabytes, petabytes e exabytes. 16 Exabytes é este numerazão aqui embaixo se for em bytes (18.446.744.073.709.551.616).</p>
<p>Agora, esse numerozão está representado no sistema decimal, que é o que estamos mais acostumados a lidar, mas quando falamos em computador, ele representa tudo em 1 ou 0. Vamos escovar bits um pouco. O número 1 é só 1, o número 2 é 1-0. O número 11 é 1-0-1-1. Pense em binário como a tabuada do 2, o número mais pra direita é 1, o segundo da direita pra esquerda é 2, na sequência vem 4 e depois 8 então 1011 é 8 + 0 + 2 + 1 que é 11, entenderam? Portanto o menor número de 64 bits é basicamente 64 zeros em sequência e o maior número seria 64 1s em sequência.</p>
<p>Obviamente ficar representando tudo em binário no papel é muito longo. Como lidamos com bytes que são conjuntos de 8 bits, é mais fácil lidar com uma representação que é múltipla de 8 ou, melhor ainda, múltipla de 16, que é o que chamamos de hexadecimal. É um sistema que em vez do nosso decimal que vai só até 10 ele vai até 16, mas como números arábicos só tem caracteres de 0 a 9, precisamos usar as letras do alfabeto de A até F pra ir até 16. O mesmo número máximo de 64 bits que eu falei que é 64 vezes o caracter “1”, em hexa seria quatro grupos de quatro Fs.</p>
<p>Tão vendo? Aquele numerozão horroroso em decimal que você jamais vai decorar é muito mais simples em hexadecimal e você consegue literalmente saber de cabeça que vai de 0 até 16 Fs. É muito mais curto. E em binário você tem propriedades matemáticas interessantes, por exemplo, multiplicar um número binário por 2, é simplesmente um shift left, tipo mover tudo uma casa pra esquerda e colocar um zero. Por exemplo o número 11 que eu falei que é 1-0-1-1 vira 1-0-1-1-0 que é 16 + 0 + 4 + 2 + 0 que é 22. Algumas coisas ficam mais simples de calcular.</p>
<p>Você precisa entender que usamos o sistema decimal que vai de 1 até 10 por motivos históricos. Vale pesquisar a história da matemática, você vai encontrar outros sistemas. Um exemplo, se você usar o relógio como parâmetro, é mais fácil contar de 1 até 60. Computadores são 1 ou 0 porque é o sistema mecânico mais simples, pense o interruptor de luz da sua casa, em todo instante ele armazena uma de duas informações: ligado ou desligado. 10 interruptores guardam 10 bits de informação. E os computadores mais rudimentares do passado usavam lâmpadas a vácuo, como o que ilumina seu quarto neste instante. Luz ligada ou desligada. Coloque 10 lâmpadas e você pode visualizar 10 bits de informação.</p>
<p>Continue estudando mais sobre representação binária e hexadecimal, é importante você ter essas ordens de grandeza na cabeça de forma natural, mesmo que seu trabalho não seja fazer aritmética direto nesses sistemas. De qualquer forma, estávamos falando sobre computadores 64-bits. Este número abaixo (16 Fs) é o maior número inteiro que é possível armazenar em uma palavra de 64-bits e voltando às linhas do índice do nosso livro, significa que cada linha de endereço pode apontar números só até esse tamanho e, por consequência, esse tamanhão máximo de RAM ou páginas do nosso livro. Só que isso é teórico.</p>
<p>Nenhuma máquina que eu saiba tem capacidade pra realmente comportar esse tanto de memória real. Um dos motivos é porque embora o processador tenha registradores de 64-bits, o barramento entre a CPU e a memória em processadores Intel, se não me engano, é de 42-bits, e na AMD é de 48-bits, portanto o máximo endereçável é 2 elevado a 48 que seria 256 Terabytes. 2-bits fazem muita diferença, porque é o quadrado do quadrado, então na Intel você conseguiria endereçar no máximo 4 Terabytes.</p>
<p>Isso é ordens de grandeza maior do que em computadores 32-bits da geração passada. E não, 64 não é o dobro de 32-bits, é o quadrado. Lembrando que 32-bits significa 2 elevado a 32, que limita o endereçamento a no máximo 4GB de RAM. Se você tinha computador nos anos 2000 vai lembrar que não dava pra colocar mais que isso de RAM. Se você chegou a ter acesso a servidores com Windows Server 2003 Service Pack 2 Datacenter Edition dava pra ter até 64 GB.</p>
<p>Isso porque havia um truque da Intel em processadores 32-bits chamado PAE ou Physical Address Extension, e dependendo da configuração de hardware e sistema operacional, dava pra ir acho que até um máximo de 128 Gigas mesmo num computador 32-bits. Na prática, os processos num sistema desses continuavam só enxergando 4GB mas era possível ter mais processos ativos porque com PAE você podia swapar alguns processos pra essas áreas extendidas de memória e evitar usar swap de disco por exemplo.</p>
<p>Tudo isso dito, vamos tentar explicar mais sobre como o sistema operacional enxerga essa memória. Até agora sabemos que a memória é como um livro, com linhas de endereço no índice, cada endereço representando uma página do livro, com o endereço máximo dependendo se o computador tem barramento de 48-bits, 42-bits, 36-bits, ou 32-bits e assim por diante. O sistema operacional pode ler e escrever em qualquer endereço de memória. Num sistema operacional muito simples, tipo um Basic num Commodore 64, dava pra acessar diretamente esses endereços pra escrever ou ler. Na verdade existe um porém, você poderia achar que seu programa tem acesso a tudo do endereço 0000 até o endereço FFFF por exemplo, num computador de 16-bits. Mas quando você boota um Commodore, apesar dele poder endereçar 64KB, porque tinha barramento de 16-bits, você vai ver que ele diz que só tem 38.911 bytes livres. Isso porque como em qualquer máquina, nem todos os endereços estão livres pro usuário.</p>
<p>Um pedaço dos endereços é reservado pra kernel, outro pedaço são funcionalidades como endereços de I/O, outro pedaço no caso do Commodore é o próprio interpretador Basic, e o que sobra é onde você pode carregar seus programas. O programa Basic só pode carregar a partir do endereço $0801 até o $A000, se você fizer a conta, A000 é o decimal 40,960 e 0801 é o decimal 2049. Então 40,960 menos 2049 são os 38911 bytes que o sistema declara como disponível.</p>
<p>Isso era na época em que tudo usava endereços reais, até o fim dos anos 80, antes dos processadores Intel 80286. Os computadores modernos a partir dos anos 90 tem o tal modo protegido e memória virtual. Quando o sistema operacional carrega seu programa ele dá pra ele um índice virtual, que vai do endereço virtual 0000 0000 até o FFFF FFFF. Só que o endereço virtual 0000 0000 desse processo, na memória real pode ser um endereço nada a ver, faz de conta, DCBA 9876, mas seu processo não tem idéia disso, só o sistema operacional sabe mapear entre os dois.</p>
<p>Daí você carrega outro programa, ele também ganha outro índice virtual que vai de 0000 0000 até FFFF FFFF só que o 0000 0000 desse programa mapeia pro endereço real BA98 7654 por exemplo. Ambos os programas enxergam o 0000 0000 mas cada um aponta pra um lugar diferente. E por isso os dois programas são ditos isolados, porque não importa que endereço eles tentem apontar, nunca vai ser em cima do endereço do outro processo porque o sistema operacional garante que um endereço real usado não é apontado por mais do que um índice virtual.</p>
<p>A grande vantagem disso é que você pode ter 10 programas, cada um “enxergando” 4 GB de memória inteiro só pra ele, ou seja endereços virtuais de 0000 0000 a FFFF FFFF mas seu computador real pode ter bem menos memória que isso. Na prática a maioria dos programas não vai usar toda a memória disponível, então o sistema operacional compartilha a memória real entre os diversos programas até acabar a memória real. Então seu processo tem só a ilusão de que tem toda essa memória. E o que acontece quando acaba 100% da memória real do sistema, ou seja, toda a RAM e todo o swap no HD? Antigamente seu computador ou ia ficar lento ao ponto de ser inusável, ou programas iam começar a crashear por falta de memória, ou alguma combinação disso.</p>
<p>Num sistema operacional de smartphones você já sabe o que acontece. Num iPhone ou qualquer Android moderno, você vai abrindo programas e não precisa fechar. Quando acaba a memória, os programas menos usados ou os abertos à mais tempo e sem uso, fecham sozinhos, dando espaço pra abrir mais programas. Num Android, que é um derivado de Linux, e mesmo iOS que é derivado de BSD UNIX, existe o OOM Killer ou Out of Memory Killer. Todo Linux tem isso, e é isso que evita que seu celular fique sem memória. É por isso que falamos que em celulares modernos você não precisa ficar fechando manualmente os programas, o OOM Killer vai fazer isso por você.</p>
<p>Voltando aos seus processos, o sistema operacional começa dando zero bytes de memória real pro seu processo. À medida que o processo vai pedindo memória, ele vai alocando memória real, sob-demanda, e mapeando os endereços reais pra endereços virtuais que o processo enxerga. E quando o processo vai devolvendo memória ou quando você fecha o programa, esses endereços reais voltam a ficar disponíveis pra outros processos poderem usar.  Se você simplesmente abre programas de Linux como ps, top ou htop ou o Task Manager no Windows, vai quanto cada programa está alocando. Pra simplificar digamos que você tem 2 programas, cada programa você vê ocupando 1.5 GB. Ou seja, cabe tudo em 3 GB de RAM. Daí na sua cabeça você acha que porque tem 4 GB de RAM, tem memória de sobra, certo? Errado. Um desses programas pode estar muito perto de crashear.</p>
<p>Pra simplificar, vamos considerar ainda um computador de 32-bits nesse exemplo. Eu disse que seu programa enxerga do endereço virtual 0000 0000 até FFFF FFFF. Mas ele Não vai poder usar tudo isso. Num Windows 32-bits de mais de 10 anos atrás, você começa tendo só 2 GB disponível, do começo até a metade, ou seja de 0000 0000 até 7FFF FFFF. Daí pra cima é reservado ao sistema operacional. Lembra como eu falei no Commodore 64 que partes dos endereços são reservados pro Kernel, Basic, I/O e outras coisas? Mesmo coisa em sistemas operacionais modernos. Mesmo num Linux, ele vai reservar de 512MB a 1 GB de endereços. Num Windows Server você podia bootar com um flag especial e compilar os programas com a flag /largeaddressspace pra permitir usar um pouco mais além da metade dos endereços. Não quer dizer que o Windows está realmente usando 2 GB, mas sim que os endereços estão reservados pra ele usar.</p>
<p>Ou seja, num computador 32-bits, apesar de poder mapear até 4GB e mesmo se você tivesse 4GB reais de RAM, nenhum processo seu iria conseguir usar mais que 2GB. Hoje em dia você tem problemas de memória em computadores 64-bits? Imagina quando a gente tinha que programar servidores em 32-bits. Pior ainda, imagina como era na época dos 16-bits. Saber usar memória é até hoje o que diferencia um amador de um profissional de verdade. Pra dar um exemplo, lembram do episódio que eu falei de SAP? Nessa época, acho que era 2003, eles tinham um servidor de aplicações Java, o In-Q-My, ainda em beta, pra carregar aplicações J2EE pesadas como o Enterprise Portals. No boot do Java com os programas SAP por cima, ele carregava tranquilamente um giga e meio.</p>
<p>Se você fosse amador, pensaria, “ah, sussa, máquinas 32-bits carregam até 4GB então tem sobrando”. Se você fosse intermediário pensaria “puta que pariu, só mais 500 Mb e vai bater no limite de 2 GB”. Porém, eu não conseguia fazer esse servidor subir, ele crasheava por falta de memória. Mas eu descobri porque. Lembram que eu falei que o sistema operacional usa parte da memória do processo? Seus programas reusam muitas bibliotecas que o sistema operacional compartilha com os processos, então você tem endereços virtuais que mapeiam pros endereços reais dessas bibliotecas do sistema, incluindo muitas DLLs que todos os processos usam como a LibC da Microsoft ou o Foundation Classes. Pra você entender, o sistema não está duplicando essas bibliotecas em todos os processos, mas os endereços virtuais dos processoa mapeiam pras mesmas bibliotecas. Os processos são isolados, então pra eles enxergarem alguma coisa de fora, os endereços virtuais precisam mapear pra esses recursos compartilhados.</p>
<p>Ainda pensando de forma ingênua, você imaginaria que essa bibliotecas carregam bonitinhas sequencialmente uma atrás da outra, sem desperdiçar nenhum endereço. Lógico que não, não necessariamente. E de fato, algumas dessas bibliotecas apareciam com endereços na primeira metade dos endereços virtuais do processo, ou seja, em cima dos 2 GB reservado pro seu programa. Isso se chama fragmentação de memória. A JVM precisava alocar memória contígua no boot, ou seja com endereços sequenciais, sem buracos no meio. Aliás, alocação de memória sempre aloca pedaços inteiros, sequenciais. Como a memória estava fragmentada, não havia um trecho sequencial limpo de 1.5 GB. E agora?</p>
<p>Felizmente, no Windows você tem como fazer rebase das DLLs, pense mais ou menos, como defragmentar a memória. Exatamente isso, você pode mudar o endereço de carregamento dessas DLLs. Como são bibliotecas dinâmicas, o binário não mapeia um endereço fixo, então você pode mudar elas de lugar. E foi assim que eu consegui subir os programas Java que eu precisava. Quem já trabalhou com servidores 32-bits e programas realmente pesados de Java, com certeza esbarrou com isso, é um problema sabido desde o ano 2000 pelo menos. E pense que em 2003 o site StackOverflow ainda não existia.</p>
<p>Aceite este fato: a memória total do seu sistema não está à disposição do seu processo. Felizmente em máquinas 64-bits de hoje, você tem muito mais que 2 GB de endereços virtuais disponíveis, então pelo menos esse problema já não afeta mais. Tanto que mesmo na época uma das soluções que a SAP falava era: migre pra máquinas 64-bits. Espero que em 2019 ninguém mais esteja usando máquinas 32-bits. O segundo fato que você precisa aceitar: memória fragmenta. A razão é simples. Digamos que sua memória total seja de 10 kb, dividida em chunks ou pedaços de 1 kb. Você começa alocando 3 variáveis de 1 kb cada. Digamos que o alocador aloque os primeiros 3 chunks. Agora digamos que você resolva liberar a segunda variável. Você ficou com 1 kb ocupado, 1 kb livre, e o terceiro kb ocupado.</p>
<p>Agora seu programa resolve que precisa de uma variável de 2 kb. Como o espaço vago do meio não cabe os 2 kb, e repetindo, grave essa regra: o alocador sempre precisa alocar pedaços contíguos, com endereços sequenciais. Então ele só vai ter espaço depois do terceiro chunk. Agora você ficou com um espaço fragmentado. Pense numa escala maior, com milhares de alocacões e desalocações, sua memória vai ficando com buracos no meio do caminho e toda vez que você precisa alocar mais memória do que cabe nesses buracos ele precisa ir lá pra frente. Então um programa que internamente ele enxerga que está alocando só 4 KB na verdade está potencialmente bloqueando 5 KB nesse momento. Fragmentação é uma das coisas que alocador de memória quer evitar.</p>
<p>Quem faz esse trabalho é o alocador de memória. No caso do Linux, mais especificamente da biblioteca glibc, você tem o famoso “malloc”. Eu expliquei nos episódios anteriores que no Linux você pode escolher entre diferentes schedulers de threads como o CFS que hoje é o padrão ou outros como o BFS dependendo de pra que vai usar a máquina. Alocador de memória também, você pode escolher.</p>
<p>O malloc do glibc na verdade é o ptmalloc2 que por sua vez é baseado no dlmalloc, DL porque quem criou essa versão foi o professor Doug Lea. Um alocador de memória é um conjunto de algoritmos e estruturas para manter metadados dessa memória. Um alocador moderno tem vários de problemas importantes pra resolver. Primeiro de tudo, alocar memória precisa ser rápido, estamos falando de frações de milissegundos, ou seja, nanossegundos, porque é uma operação que acontece o tempo todo. Um ptmalloc2 leva uns 300 nanossegundos por operação. Mas não adianta ser rápido se ele “vaza” memória ou seja, usa mais memória do que o necessário, em parte porque não foi inteligente e deixou a memória fragmentar demais. Existem várias syscalls mas em C você normalmente chama pelas funções malloc, passando um tamanho que precisa alocar, e a função free que sinaliza ao sistema operacional que esse endereço pode ser liberado quando puder.</p>
<p>Quando você chama a função malloc, o único parâmetro que passa é quanto de memória quer alocar, e ele devolve como retorno o endereço que ele achou onde cabe um pedaço de dados do tamanho que você pediu. Se você tiver pouca RAM e vários processos fragmentando memória, você vai notar que teoricamente era pra caber mais programas na RAM mas por alguma razão falta memória. Um programa mal escrito pode estar alocando mais memória do que deveria, e não devolvendo. E mesmo devolvendo o sistema ainda pode não ter colocado essa memória à disposição.</p>
<p>Pra piorar, eu já expliquei sobre concorrência e paralelismo e todos os problemas que você enfrenta ao lidar com threads reais, lembra? Existe um outro problema: e se várias threads pedem ao alocador pra reservar memória ao mesmo tempo? Acabamos de criar uma contenção, ou seja, um gargalo. Se o alocador não suportar threads corretamente, o que acontece é um lock do alocador e cada thread tendo que esperar o anterior receber seu pedaço de memória. Na verdade o tal ptmalloc que eu falei é um fork do antigo dlmalloc justamente pra suportar threads.</p>
<p>Só de entender o problema de fragmentação e agora o problema de threads, você deveria começar a entender que fazer uma chamada em malloc não é um troço simples. Pra começar a forma de suportar threads é dividir o índice de endereços disponíveis no que o malloc chama de Arenas. Num Linux 32-bits o padrão é 2 vezes o número de cores. Então num computador quad-core com hyper threading você pode ter até 8 threads e por isso até 16 arenas. De forma simplista se todas as threads pudessem ter qualquer endereço do espaço de 32-bits, o alocador ia precisar criar um lock global e as threads perderiam totalmente sua performance e ficaram mais tempo em fila esperando o alocador do que realmente trabalhando em paralelo. Mas se você tem 4 cores e disser que do endereço 0000 0000 a 3FFF FFFF é da thread 1, do endereço 4000 0000 até 7FFF FFFF é da thread 2 e assim por diante, agora elas não compartilham endereços e você não precisa de um lock. Lembra que eu dei a dica já que a forma de você não ter que lidar com mutexes e locks em concorrência é não compartilhar nada entre as threads?</p>
<p>Agora as Arenas ainda são sub-divididas em Heaps ou montes. Seu programa quando executa pode armazenar dados na stack ou pilha ou no heap. A pilha é literalmente a pilha de execução, seu programa carrega as instruções na stack e pequenas quantidades de dados cabem no stack, se usar demais ou de forma errada vai ter o famoso Stack overflow. Se a memória fosse uma página de papel, pense na stack crescendo de cima pra baixo e o Heap começando de baixo pra cima. Não é exato mas é uma forma de visualizar. Na prática seu programa vai armazenar a maior parte dos dados que precisa pra trabalhar no Heap e na stack podem ficar coisas como valores constantes ou coisas assim.</p>
<p>Uma heap é ainda sub-dividido em múltiplos chunks. Podemos ter chunks muito pequenos de até 80 bytes, chunks de até 512 bytes e chunks grandes de mais de 512 bytes, que é como o ptmalloc organiza. Podemos agrupar os chunks menores em caixas ou bins, no caso os de até 80 bytes no que se chama Fast Bin, os de até 512 bytes num Small bin e os maiores que 512 bytes num Large bin. Por que isso? Pra minimizar fragmentação. Você fragmenta a memória mais rápido se tentar misturar pedaços grandes no meio de pedaços pequenos. É melhor agrupar pedaços de tamanhos similares mais próximos, porque se você desaloca um chunk de 16 bytes no Fast Bin, quando precisar alocar 16 bytes de novo, é mais fácil procurar direto no Fast bin. E se tentar alocar alguma coisa de 1 MB é melhor ir no Large Bin. E no começo você aloca poucos bins, e vai alocando mais à medida que precisar.</p>
<p>Lembre que toda essa organização ainda não está na prática “usando” a memória ainda. Está só criando estruturas que representam caixas de endereços. Daí a alocador vai pegar desses bins, dependendo de que thread está pedindo e que tamanho de memória pediu. Se você tem boa intuição está entendendo que seu processo sempre vai usar um pouco mais de memória do que ele realmente precisa por causa dessa organização e estruturas de suporte que o alocador precisa. Quanto mais tempo um processo fica vivo trabalhando, mais essas atividades de alocar e desalocar memória podem is expandindo, crescendo o heap e causando leaks se o programa for mal escrito, causando fragmentação demais.</p>
<p>Mas o pior não é isso, eu disse que Arenas são divisões para evitar contenção de lock de threads. E se sua aplicação inicialmente carregar 300 MB de estruturas na primeira arena. Mas agora numa segunda fase outra thread precisa trabalhar essas estruturas mas ela vai usar outra arena? Os 300 Mb vão ser copiados de uma arena pra outra porque o ptmalloc não suporta mover memória entre arenas. De repente seu programa inicia usando o dobro do que ele realmente precisa. Esse é um problema real relatado por engenheiros do Google e por causa disso eles criaram um outro alocador de memória, chamado de tcmalloc, que justamente permite cachear dados entre threads e por isso se chama Thread-Caching malloc ou tcmalloc.</p>
<p>Se algum dia esbarrar num tutorial que fala de tcmalloc, lembre que ele é do Google. Ele é mais rápido que o ptmalloc2 do glibc, chegando a ser até 6 vezes mais rápido e usando estruturas de dados mais eficientes, então também usando bem menos memória no geral. E como o tcmalloc implementa as mesmas funções POSIX como o malloc(), free(), calloc(), realloc() e outros. O Google mesmo diz que ele não é necessariamente 100% compatível e alguns programas podem ter problemas, mas na grande maioria, você consegue carregar o tcmalloc e seus programas devem funcionar mais rápido, usando menos memória e, se for um programa que fica de pé por dias seguidos como um servidor de aplicação, ele também deve tender a alocar menos memória no geral.</p>
<p>Tcmalloc foi criado mais de 10 anos atrás, e na mesma época, com o Firefox 3 o alocador de memória do Windows da época fragmentava demais, ou seja, o navegador tendia a crescer no uso de memória como eu já expliquei que acontece, numa ordem de pelo menos 20% ou mais de desperdício. Se o processo usar 1 GB ele vai desperdiçar 200 Mb só em fragmentação. Nessa época um engenheiro chamado Jason Evans queria resolver o problema de gerenciamento de memória de uma linguagem experimental que ele estava criando e pra isso fez um novo alocador. Esse alocador passou a ser usado no Firefox e foi um dos motivos de porque Firefox no Windows conseguia desperdiçar menos memória. Com o tempo esse alocador foi adotado pelo Facebook porque seus servidores também estavam desperdiçando muita memória. Esse alocador foi devidamente nomeado de jemalloc por causa de Jason Evans. Com o tempo o jemalloc se tornou mais rápido, mais robusto e confiável e desperdiçando ainda menos memória que o tcmalloc do Google. Inclusive acho que ele é mais compatível com o ptmalloc2 e você poderia trocar o alocador do seu Linux por jemalloc e tudo vai funcionar melhor.</p>
<p>De curiosidade, a linguagem Rust, assim como o Firefox, são da mesma fundação Mozilla e por isso o Rust desde o começo sempre usou o jemalloc, que é um dos motivos de porque ele é eficiente com memória. A grande maioria das outras linguagens ainda usam o ptmalloc2 do glibc. Linguagens como Python, Ruby também se beneficiam se você fizer o interpretador usar o jemalloc. Vou deixar nas descrições abaixo como fazer isso no Ruby.</p>
<p>Agora, isso vale pra linguagens que ativamente usam o alocador do sistema operacional. C, ou C++, ou Rust que já falei, todos que compilam pra binários nativos vão usar o ptmalloc2 ou o jemalloc hoje em dia. Pra variar, o Go não usa nenhum que já existia. Um ptmalloc2 usa funções de mais baixo nível ainda do sistema operacional como mmap, madvise, munmap, sbrk. O Go reinventa a roda e implementa seu próprio alocador meio inspirado, claro, no tcmalloc do Google. O Go também divide a memória em várias arenas. Esse conjunto de arenas é o que ele chama de Heap. Ele organiza as arenas em páginas de 8kb. Ele também divide esses blocos em bins pra tamanhos diferentes de chunks mas no caso do Go os nomes são diferentes ele tem um Tiny pra objetos menores que 16 bytes, um Small pra objetos menores que 32 kb e um Large pra coisa maiores que 32 kbytes. Esse valores dependem da versão do Go, então não decorem isso, só lembrem que o alocador agrupa blocos de tamanhos similares, como o ptmalloc2 já fazia também. E que é uma das estratégias pra diminuir a fragmentação.</p>
<p>Escovamos bits demais por hoje, vou ter que dividir esse episódio em duas partes de novo. Neste episódio tivemos mais noção de como o sistema operacional realmente organiza a memória, as diferenças de arquiteturas 32-bits ou 64-bits, e os diferentes tipos de alocadores. E como um bônus de termos já discutido sobre threads nos últimos episódios, vimos agora como eles afetam o gerenciamento de memória também. Na semana que vem vamos falar sobre os famosos garbage collectors, que muita gente ainda acha que é mágica, mas na prática não tem idéia de como funcionam.</p>
]]></content:encoded><category>performance</category><category>ciencia-da-computacao</category><category>programacao</category><category>akitando</category></item><item><title>[Akitando] #43 - Concorrência e Paralelismo (Parte 1) | Entendendo Back-End para Iniciantes (Parte 3)</title><link>https://akitaonrails.github.io/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3/</guid><pubDate>Wed, 13 Mar 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/cx1ULv4wYxM"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;De volta à série Começando aos 40, estamos já no oitavo episódio! E ela é a Parte 3 do tema de Back-end, mas desta vez vou precisar me alongar mais explicando conceitos antes de retornar às ferramentas.&lt;/p&gt;
&lt;p&gt;Concorrência e Paralelismo é algo que todo iniciante hoje em dia já esbarra logo cedo. Nós vivemos num mundo que é naturalmente paralelo e concorrente. Já estamos no ponto onde nos definimos como &amp;ldquo;multi-tarefas&amp;rdquo;.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/cx1ULv4wYxM"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>De volta à série Começando aos 40, estamos já no oitavo episódio! E ela é a Parte 3 do tema de Back-end, mas desta vez vou precisar me alongar mais explicando conceitos antes de retornar às ferramentas.</p>
<p>Concorrência e Paralelismo é algo que todo iniciante hoje em dia já esbarra logo cedo. Nós vivemos num mundo que é naturalmente paralelo e concorrente. Já estamos no ponto onde nos definimos como &ldquo;multi-tarefas&rdquo;.</p>
<p>Mesmo assim, ainda existe mais superstição e &ldquo;misticismo&rdquo; do que real noção do que esse conceito realmente significa. E ao contrário do que possa parecer, na realidade o básico não é tão complicado assim.</p>
<p>Preste bastante atenção na explicação de hoje, porque isso vai ser base pra todo o resto que vou explicar até o fim desta série.</p>
<p>Se você já conhece os detalhes do que estou explicando, obviamente pra caber num vídeo para iniciantes, estou simplificando BASTANTE muitos conceitos pra ilustrar. Infelizmente não cabe tudo num episódio só, mas não deixem de comentar se achar que faltou alguma coisa muito importante!</p>
<p>Links:</p>
<ul>
<li>The Linux Scheduler: a Decade of Wasted Cores (<a href="https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/"target="_blank" rel="noopener">https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/</a>)</li>
<li>The C10K problem (<a href="http://www.kegel.com/c10k.html"target="_blank" rel="noopener">http://www.kegel.com/c10k.html</a>)</li>
</ul>
<p>=== Script</p>
<p>Olá pessoal, Fabio Akita</p>
<p>Vamos voltar à Parte 3 do tema de Back-end. Recapitulando já expliquei os conceitos básicos do computador e do sistema operacional, sobre processos e threads. Expliquei sobre compilação e bibliotecas estáticas e dinâmicas. Já expliquei sobre máquinas virtuais e interpretadores. Demos uma rápida passada em características de algumas linguagens, a história do Java, um pouco do lado .NET, até dei uma tangente na história inicial de desenvolvimento mobile e mais um pouco sobre licenças de software.</p>
<p>Estamos já no oitavo episódio da série Começando aos 40. E hoje acho que é hora de começar a tratar de um tema que é muito discutido mas eu acho que os iniciantes tem dificuldade pra visualizar na cabeça. Concorrência e Paralelismo. Mas antes entrarmos nos assuntos mais óbvios como Go ou Elixir ou Node.js precisamos voltar na história como sempre pra que todos finalmente comecem a entender esse assunto de verdade!</p>
<p>Infelizmente explicar concorrência e paralelismo só falando é muito difícil. Vocês ainda vão precisar praticar programando centenas de vezes coisas diferentes pra começar a entender de verdade como as coisas funcionam. Eu espero que a explicação que eu dê agora pelo menos ajude um pouco vocês terem uma imagem na cabeça do que esperar quando realmente colocarem as mãos no metal. Vamos lá!</p>
<p>(&hellip;)</p>
<p>Hoje em dia todo mundo tem um computador potente. Mesmo o mais fraco Intel i3 é dual core com 3 Ghz ou mais. E se você tiver a bufunfa pra comprar os novos Intel i9 ou AMD Threadripper com 8 cores e mais de 4 Ghz, você tem mais poder de computação do que os mais poderosos computadores dos anos 60, combinados. E se falarmos de processadores de servidor, como os Intel Xeon E7 temos até 24 cores. E vocês sabem que a Intel tem a tal tecnologia hyperthreading que faz você enxergar 2 threads simultâneas num mesmo core, então esse Xeon E7 de 24 cores pode ter até 48 threads de execução. Isso é bastante paralelismo. Até mesmo seu celular Android, com o chipset Qualcomm 845, é nada menos que octa-core com 2.8 Ghz cada.</p>
<p>E isso porque não estamos falando de GPUs que tem propósitos diferentes de um CPU, mas possuem cores que processam em paralelo também, no caso da NVIDIA o que chamamos de CUDA cores. E na nova RTX 2080 temos nada mais, nada menos, que 2.944 CUDA cores. Sendo que em placas mães melhores podemos colocar 2 placas dessas rodando em paralelo e poder ter acesso a quase 6 mil CUDA cores ao mesmo tempo. Isso sim é processamento paralelo massivo.</p>
<p>Então, do seu celular, a desktop, servidores, estamos claramente num mundo massivamente paralelo. Mas não foi sempre assim. Considere que acesso a esse mundo paralelo só começou de verdade pra nós programadores a coisa de pouco mais de uma década. Pra entender como tudo mudou, vou resumir como foi do fim dos anos 50 até agora.</p>
<p>Nos anos 50 talvez vocês já tenham ouvido falar do meme que a IBM dizia que o mundo não precisa de mais do que cinco computadores. Eles estavam falando do IBM 701. E se você não sabia disso, essa frase foi tirada de contexto e é fake news, pra variar. A frase em contexto foi numa reunião de acionistas que dizia que a IBM tinha planos de vender talvez cinco IBM 701 mas voltaram com pedidos pra 19. Estamos falando de uma máquina que, se fosse alugar na época, não custaria menos que 15 mil dólares, em dólares dos anos 50. E a potência? Era uma máquina de 18-bits. Lembrando que seu celular hoje é 64-bits. E essa máquina vinha com nada menos que 1 quilobyte de memória e podia suportar um máximo de 9 quilobytes. E ainda foi a primeira que vinha com … gravador de fita, que podia gravar nada menos que 1.2 megabytes a uma velocidade estrondosa de 5.6 kilobytes por segundo. Lembrando que sua placa de rede ethernet porcaria trafega dados a pelo menos 1 gigabits por segundo, e isso é considerado lento hoje em dia dado que existem placas de 10 gigabits.</p>
<p>Então você imagina que numa máquina cara dessas, basicamente uma calculadora glorificada, você gostaria de usá-la da forma mais eficiente possível. Era uma época onde a linguagem mais avançada era Fortran pra fazer cálculos. E onde você carregava programas com cartões perfurados, numa máquina que conseguia ler no máximo 150 cartões por minuto e perfurar no máximo 100 cartões por minuto. Então a forma mais eficiente de usar essa máquina era os programadores serem extremamente cuidadosos pra escrever programas de cálculo muito precisos e sem erros, que chamamos de jobs, e organizar esses jobs em batches ou grupos de programas similares, e deixar numa fila pra ir processando dia e noite. Foi a época dos jobs em batch. Se tivesse um erro no seu programa, você só ia saber no dia seguinte e ter que repetir todo o processo.</p>
<p>Então dos anos 50 pros anos 60, alguns grandes nomes que já falei antes como John Backus, criador do Fortran, ou John McCarthy que é considerado o pai da inteligência artificial e o criador do garbage collector do Lisp, começaram a trabalhar um conceito para aumentar a eficiência dos programadores. Em vez de terem que colocar seus jobs numa fila e ficar só esperando de braços cruzados, e se fosse possível usar as ineficiências do computador, como o tempo que ele fica lendo ou escrevendo nas fitas e outras pausas, pra permitir que múltiplos programadores pudessem trabalhar concorrentemente no mesmo computador?</p>
<p>O conceito básico era que fosse possível salvar o contexto do ambiente de um programador e trocar pelo contexto de outro programador. Entenda contexto como o que define o ambiente de trabalho do programador, o que está na memória basicamente. Se você trocar muito rápido entre um contexto e outro, ou seja fazer switch, para os programadores “parece” que ele está sozinho na máquina, mas na verdade a máquina está gerenciando entre múltiplos contextos. A máquina oferece um pedaço de tempo, ou time-slice, para cada ambiente e vai dando a vez pra outro ambiente, sucessivamente. Esse é o conceito de time-sharing baseado em context switching.</p>
<p>Esse conceito nasceu na época dos IBM 704 e IBM 709 e foi incorporado aos mainframes dos anos 60. O conceito de time-sharing existiu mais no mundo corporativo, em mainframes e mini-computadores dos anos 60 até 70. Nos anos 80 principalmente começou a aparecer nos UNIX usados em workstations. E só nos anos 90 começou a de fato a aparecer no mundo dos desktops. Se você usou micro-computadores nos anos 70 e 80, lidou com micros muito simples, com CPUs como o Z80 ou Motorola 68000 ou mesmo Intel 8088. Todos CPUs de um único core, que só sabiam executar uma instrução de cada vez a cada giro do clock.</p>
<p>Se você já teve um Sinclair, MSX, Commodore 64, Apple II ou outras máquinas 8 bits ou 16 bits da década de 80, você se viu na mesma situação dos computadores dos anos 50: só conseguir executar uma tarefa ou um job de cada vez. Num MS-DOS você podia executar um programa, mas pra executar outro precisava sair do primeiro e só depois carregar o segundo. Ou usar um truque de interrupção, a famosa INT 21H que era o recurso de Terminate and Stay Resident ou TSR. De uma forma rudimentar você podia carregar um segundo programa, chamar essa interrupção pra devolver controle pro DOS mas deixar residente em memória de forma que podia ser chamado de volta. Era como utilitários pequenos com o Borland Sidekick funcionavam na época. E isso meio que simulava um tipo de multi-tarefa.</p>
<p>Foi só com o Windows 3.1 que a maioria das pessoas realmente experimentou mais esse conceito de multi-tarefa, mas no caso era multi-tarefa cooperativa. Quando falamos em multi-tarefa num computador com CPU de um único core, pensa numa mesa de escritório sendo dividida entre mais de uma pessoa. A primeira pessoa chega, coloca as coisas dela na mesa e começa a trabalhar. Pra outra pessoa usar, a primeira precisa tirar todas as coisas dela da mesa e deixar a outra colocar as coisas dela. As coisas da primeira pessoa é o tal contexto e a troca é o switching. É o mesmo conceito de time-sharing que falei antes. Mas se a segunda pessoa se recusar a sair da mesa, ninguém mais consegue usar a mesa, então ela precisa cooperar pra todo mundo poder usar. Esse é o conceito de multi-tarefa cooperativa, que é o que tinha no Windows 3.1. Se um programa fizesse alguma coisa que bloqueasse o sistema como imprimir, todos os outros programas ficavam impedidos de ter sua vez pra rodar.</p>
<p>Isso veio a mudar em sistemas operacionais 32-bits como OS/2 ou Windows 95, Windows NT e também nas distribuições Linux que começaram a aparecer do começo pro meio da década de 90. Nesse caso temos o que já expliquei antes: cada processo era iniciado pelo sistema operacional num espaço de memória protegido, ou seja, onde um programa não consegue escrever em cima da memória do outro. Além disso agora não é o programa que tem que cooperar pros outros poderem rodar. Na metáfora da mesa, imagine que agora tenha um supervisor, que tira a pessoa da mesa pra deixar a outra usar mesmo se a pessoa não quiser sair naquele momento. Esse supervisor é que chamamos de scheduler do sistema operacional. Ele aloca um pedaço de tempo, ou time-slice pra cada programa e interrompe pra deixar outro rodar. A forma de fazer isso é pegar o que está na tal mesa, separar do lado e colocar as coisas da outra pessoa na mesa. E quando der o tempo, ele tira as coisas da mesa e devolve o que tava antes e vai fazendo esse switching de contexto. Isso é multi-tarefa preemptiva.</p>
<p>Isso é feito no nível do processo, que na metáfora é o que está executando na tal mesa naquele determinado momento. Mas digamos que quiséssemos ter tarefas em paralelo na mesma mesa ao mesmo tempo. Por definição, cada processo tem uma única thread de execução, pense numa thread como sendo uma tarefa sendo executada na mesa. Mas podemos ter multi-thread que seria o equivalente a ter 2 pessoas usando a mesma mesa e os mesmos materiais na mesa ao mesmo tempo. Pra isso acontecer o tal supervisor faz a mesma coisa: ele força uma das pessoas a parar de usar o material da mesa pra deixar a outra pessoa sentada do lado usar o mesmo material.</p>
<p>Multi-thread é mais ou menos isso: duas pessoas começando com o mesmo material ao mesmo tempo. Cada pessoa pode até usar seu próprio material, mas ela tem acesso ao material todo que está na mesa, que na metáfora é o espaço de memória do processo. Um processo pode ter uma ou mais threads. Num processador com um único core, não é possível que as duas pessoas trabalhem exatamente ao mesmo tempo, porque só tem um único espaço na mesa, então a única forma de haver concorrência é o tal supervisor ficar fazendo uma das pessoas pausar o trabalho pra dar um tempo pra outra pessoa continuar o trabalho dela.</p>
<p>Quando falei de um processador moderno como o Intel i3, ela tem 2 cores. Pense em 2 cores como duas mesas. Nesse caso cada thread ou cada pessoa realmente pode trabalhar verdadeiramente em paralelo. Mas nos anos 90 o comum era um processador 486 ou Pentium de um único core. Só servidores tinham processadores com dois ou mais cores. Então você tinha multi-tarefa preemptiva que fica pausando threads pra outra poder rodar, e agora você tem multi-processamento paralelo onde cada thread roda separadamente em um processador separado.</p>
<p>Quando são dois processos independentes, eles podem rodar 100% em paralelo. Se não estiverem usando nada que é compartilhado pelos dois como um arquivo ou outro recurso de hardware como impressora ou rede. Se um deles estiver usando a impressora, obviamente o segundo processo é obrigado a esperar ou fazer outra coisa enquanto espera. Mesma coisa se forem 2 threads dentro do mesmo processo. Seria o equivalente às duas pessoas uma em cada mesa mas onde ambos dependem do mesmo material pra trabalhar. E é nessa situação que multi-threading começa a ficar complicado.</p>
<p>Se ambas as pessoas quiserem escrever no mesmo pedaço compartilhado de papel ao mesmo tempo, imagine a zona que ia ser. Cada linha ou cada palavra de uma pessoa ia ficar misturado com o que a outra pessoa tá querendo escrever por cima. Ou seja, seja lá o que eles gostariam de escrever no papel ia virar um monte de dados misturados. Na realidade você talvez gostaria que a primeira pessoa usasse o papel, e só quando terminasse a segunda pessoa usasse na sequência. Mas como eles estão disputando ao mesmo tempo, pode ser que o inverso aconteça, e como não sabemos quem vai ganhar a disputa de quem começa a usar o papel na frente do outro literalmente temos uma condição de corrida, ou o que se chama de race condition, que pode levar a resultados inesperados ou não-determinísticos.</p>
<p>Então como fazer nesses casos? Pra isso precisamos de um sinalizador. Quando a primeira pessoa quiser escrever, ele coloca uma placa dizendo “ocupado”. Quando a segunda pessoa for escrever também ele vê a placa e espera. Quando a primeira pessoa termina o que queria, ele tira a placa de “ocupado”. Agora a segunda pessoa pode colocar a placa dela e escrever sua parte, e depois tirar a placa. Se ela vai embora e esquece de tirar a placa, aquele pedaço de papel fica bloqueado e ninguém mais consegue escrever nele. Agora pense que a primeira pessoa bloqueia o pedaço de papel, então a segunda pessoa resolve bloquear outro pedaço de papel que a primeira vai precisar na sequência. Mas ambos esquecem de tirar as placas de ocupado de seus respectivos papéis e cada um quer usar o papel do outro. Temos um impasse, onde nenhum sinaliza pro outro que já terminou e ambos param de trabalhar esperando. Eles estão literalmente mortos por causa do bloqueio, ou seja, o que chamamos de dead-lock.</p>
<p>Essa placa de “ocupado” é o que em programação multi-thread chamamos de lock ou mutex, que significa mutuamente exclusivo. É um sinalizador de exclusividade. E como no exemplo, quem bloqueia o recurso também tem a responsabilidade de desbloquear quando acabar. E com isso você consegue ver como duas threads, mesmo se tiver dois cores na CPU onde eles teoricamente poderiam trabalhar independentes em paralelo, se tiver um recurso compartilhado como o tal pedaço de papel, um é obrigado a esperar o outro terminar.</p>
<p>Eu tentei usar metáforas aqui mas você só vai entender realmente o que tudo isso significa o dia que realmente começar a programar usando esses recursos. Entenda alguns fatos primeiro: toda CPU moderna é feita pra rodar threads, é como se fosse a menor unidade de execução. Cada thread executa uma sequência de instruções e tem um contexto, que é seu material de trabalho, que fica nos registradores da CPU. Quando o CPU faz o tal context switching, ele guarda o que está nos registradores naquele momento e deixa outra thread executar. Fazer essa troca de contexto não sai de graça, porque tem o trabalho de tirar o material da mesa e colocar o material do outro na mesa. Então quanto mais threads você tenta executar num core, mais devagar o trabalho geral vai ficar. Então não adianta ficar criando threads como um idiota achando que tudo vai rodar em paralelo e ficar mais rápido, lógico que não. O ideal é ter o mesmo número de threads que número de cores no seu CPU. Por outro lado não é porque você tem 2 cores e inicia 2 threads que ambos vão rodar da forma mais eficiente, porque eles podem estar compartilhando o mesmo recurso e esperando liberar o mutex que o outro colocou. Nesse caso uma terceira thread poderia se aproveitar desse período de espera e executar enquanto isso, caso não precise usar o mesmo recurso. Então você vê como também ter exatamente o mesmo número de threads que cores não é um regra. Em muitos casos determinar qual o número ideal de threads que você pode subir naquela determinada máquina é uma questão de tentativa e erro.</p>
<p>Criar processos é um procedimento que custa pro sistema operacional, não é de graça. Ele precisa carregar o binário do programa, alocar memória do sistema, checar permissões de segurança, etc. Esse procedimento inclusive é diferente entre sistemas operacionais. Em particular, sistemas Linux são mais rápidos pra criar processos do que um MacOS e uma ordem de grandeza mais rápido do que um Windows. Já falei em outro episódio que no Linux muitos programas como o Apache tomam vantagem de fazer o tal FORK que cria uma cópia rápida do processo atual pra gerar outro processo que vai rodar concorrentemente. E fazer forks em Linux é uma operação comparativamente mais barata do que no Windows. Ou seja, só porque ambos fazem a mesma coisa não quer dizer que fazem do mesmo jeito. Qualquer sistema que dependa de forks vai rodar ordens de grandeza pior num Windows.</p>
<p>Por isso mesmo desenvolvimento em Windows privilegia e recomenda criar um único processo por aplicação mas usar mais threads dentro do mesmo processo. Como uma thread compartilha a memória interna do processo com outras threads, a ação de criar threads é ordens de grandeza mais barato do que criar novos processos. Por outro lado isso também é uma troca. Processos são isolados de outros processos e um não vai pisar no calo do outro. Você não tem race conditions, você não precisa manualmente gerenciar mutexes e nem se preocupar com dead-locks, mas processos em Windows são mais caros pra criar. Por outro lado threads são bem mais baratas, só que agora temos o trabalho de nos preocuparmos em ter que manualmente codificar mutexes e tudo mais.</p>
<p>Programação é sempre o ato de se comprometer, você aceita uma vantagem em troca de uma desvantagem. Tudo sempre depende e nunca tem uma resposta certa pra tudo. Alguém poderia dizer que porque threads são mais rápidas de criar e usam menos recursos, eles são obviamente melhores do que fazer forks de processos. Por outro lado alguém pode argumentar que como temos que gerenciar mutexes e coisas assim manualmente, você tem muito mais bugs e situações não-determinísticas com threads e, portanto, processos são infinitamente mais fáceis de programar com menos bugs. Ambos estão certos.</p>
<p>Na prática você vai encontrar mais soluções baseadas em fork de processos em sistemas Linux ou UNIX como no MacOS e mais soluções multi-thread no Windows. Mas isso não é uma regra e em alguns casos, a melhor opção não necessariamente é a mais óbvia. Por exemplo, um navegador web como Firefox ou Chrome. Acho que se você é desenvolvedor já sabe que eles usam recursos de multi-processos. Antigamente um navegador só abrir uma única página de cada vez. Mas aí inventaram a idéia de abas e abrir múltiplos sites diferentes em múltiplas abas. Como um navegador é razoavelmente pesado e consome muita memória pra guardar tantas imagens e javascript que cada página carrega, você quer tentar usar a menor quantidade de memória quanto possível. Então o primeiro instinto de um programador é fazer cada aba ser uma thread.</p>
<p>De fato você vai usar menos recursos, mas à medida que os navegadores vão ficando mais e mais complexos, a quantidade de bugs por causa de threads aumentam exponencialmente. Agora um bug numa thread pode desestabilizar o navegador inteiro porque dentro do processo do navegador, uma thread tem acesso a tudo que as outras threads tem acesso. Então era muito comum você abrir um site qualquer e de repente seu navegador inteiro com 20 abas dava pau e você perdia tudo.</p>
<p>Pra piorar, alguns desses bugs eram bugs de segurança. Imagine uma aba que abre um site conseguir vasculhar a memória da outra aba que está com um e-commerce aberto e com seus dados de cartão de crédito na memória global. Cada vez que você tenta consertar um bug, pode acabar acidentalmente largando um bug novo no lugar. Então é como tentar tampar o sol com a peneira. Some a isso a possibilidade de instalar plugins que ganham acesso à memória do navegador como os plugins de Java ou os antigos de Flash e você tem um desastre esperando pra acontecer.</p>
<p>Por isso muitos navegadores começaram a separar certas partes em processos independentes como o Safari que iniciou separando coisas como Flash e Java em processos mas ainda manteve muita coisa em threads pra usar menos memória. Mas no caso do Google Chrome ele resolveu separar completamente cada aba em seu próprio processo. Por um lado isso trouxe muito mais estabilidade. E você já deve ter visto muitas vezes uma aba inteira crashear, mas o navegador continuar rodando sem nenhum problema. E você pode ver cada processo separado num Task Manager do Windows por exemplo, e inclusive pode matar um processo por lá mesmo, na mão. É a grande vantagem de se separar tudo em processos como eu já falei antes. Por outro lado todo mundo sabe como o Chrome hoje é um dos aplicativos que mais come RAM do seu sistema e essa separação é uma das razões já que código comum entre as diversas abas precisa ser copiada em outro processo a cada nova aba.</p>
<p>Conceitualmente falando, um fork de um processo é como se você pausasse esse processo e fizesse uma cópia de tudo que ele tem na memória pra outro espaço isolado e resumisse os dois, e a partir daí cada um tem vida independente. Se um processo está consumindo 1 MB de RAM, com um fork você deveria estar agora usando 2 MB. Mas no Linux temos um recurso chamado copy-on-write ou CoW, onde o segundo processo não usa praticamente nenhuma memória adicional e fica apontando pra memória do processo original assim que é criado. Se a memória não for modificada, ambos podem reaproveitar o mesmo dado em vez de fazer cópias desnecessárias e só coisas que o segundo processo começa a gravar começa a usar mais memória e fica invisível ao processo original e vice-versa. No Windows o equivalente ao fork realmente duplica a memória, que é outro motivo de porque criar processos no Windows é considerado mais caro.</p>
<p>Um fork com copy-on-write no Linux cada processo tem acesso a uma cópia da memória do processo pai, mas diferente de threads dentro do processo que compartilha tudo, não existe a necessidade de mutexes ou outro tipo de controle de acesso porque a partir do ponto do fork, cada processo tem sua cópia da memória pai e um não escreve em cima da memória do outro. Isso faz as coisas serem muito mais simples de programar. Claro, threads ainda são mais leves e usam muito menos memória no total.</p>
<p>Você pensa no Linux como um sistema estático que é sempre igual, mas ele mudou radicalmente dos anos 90 pros anos 2000 e até agora. A interface que você interage com o sistema, que seria os shells de linha de comando, ou as APIs, são parecidas, muito porque ele segue o que chamamos de padrão POSIX que é sigla de Portable Operating System Interface onde o X no final denota sua herança de UNIX. Teoricamente os UNIX e seus clones como Linux deveriam seguir esse padrão pros programas serem portáveis entre eles, no nível do código fonte, porque os binários não são compatíveis.</p>
<p>No mundo POSIX você tem a abstração de Posix threads ou pthreads implementado como LinuxThreads anos atrás. Threads em Linux eram bugados e nunca foram grande coisa até que finalmente alguns projetos começaram a surgir pra reimplementar o suporte de threads no Linux, como o Next Generation Posix Threads ou NGPT da IBM que eu me lembro que uma característica marcante era ser um mapeamento M pra N, ou seja uma thread em Linux não seria equivalente um pra um com uma thread de verdade.</p>
<p>Mas o projeto que ganhou foi o Native Posix Thread Library ou NPTL de Ulrich Drapper que era mantenedor do glibc e Ingo Molnár que vou citar de novo a seguir. O NPTL é uma implementação um pra um em vez de ser M pra N como o NGPT, portanto era bem mais simples de ser implementado. Esse projeto corrige as falhas do LinuxThreads e só a partir desse ponto dá pra se dizer que threads em Linux ficaram decentes. E isso só é um fato a partir da kernel 2.6 mais ou menos por volta de 2007. Pra ver como as coisas são diferentes entre diferentes Unix, o Solaris usava uma estratégia M pra N até o Solaris 9. O FreeBSD e NetBSD eram M pra N também mas acho que o FreeBSD também ia ou foi pra uma pra um. Parece que um pra um começou a ganhar terreno mas vamos voltar nisso em breve, guarde esse ponto.</p>
<p>Eu já expliquei sobre o tal scheduler na metáfora das mesas sendo o supervisor que força uma pessoa a sair da mesa pra dar lugar pra outra. Existem diferentes estratégias que um scheduler pode tomar. Quando você só tem uma mesa, você é obrigado a deixar uma pessoa trabalhando e mandar as outras esperarem a vez, e o supervisor manda trocar de lugar com o próximo pra ir dando chance das outras trabalharem também. Mas imagine se o supervisor resolve deixar uma pessoa que ainda não está pronta sentar na mesa e ela fica sem fazer nada, enquanto as outras estão esperando.</p>
<p>E se você adicionar uma segunda mesa, parece que fica mais fácil, mas na verdade fica mais difícil. Imagina se você tem três pessoas pra usar as duas mesas. Mesma coisa, você vai rotacionando uma pessoa em cada mesa. Mas imagina o trampo de uma pessoa ficar transportando seu trabalho de uma mesa pra outra se o supervisor não for muito legal e não deixar ficar na mesma mesa. Quando você tem duas mesas, não dá pra deixar seu trabalho no canto da mesa pra voltar pra ela rápido na próxima rodada necessariamente, você pode ter que ficar transportando suas coisas de um lado pro outro, e obviamente isso é mais lento.</p>
<p>E se você não tomar cuidado, e seu scheduler for meio burrinho, ele vai dando vez pra threads que não precisam, ou deixa threads com pouco trabalho ocupando tempo demais na sua vez, ou pior, cortando no meio o trabalho de quem realmente precisa rodar. E por isso no começo dos anos 2000 um Linux era ruim pra coisas como tocar um vídeo. O vídeo ficava engasgando, mesma coisa com áudio. Foi só em 2007 com a Kernel 2.6 que ganhamos o Completely Fair Scheduler ou CFS que implementa um algoritmo melhor, de autoria do Ingo Molnár que já mencionei antes, que se inspirou nas idéias de Con Kolivas. Em resumo esse CFS maximiza o uso da CPU e tenta dar preferência pra programas interativos, como aplicativos desktop, que diferente de processamento intensivo, tende a ficar ocioso por períodos de tempo e de repente precisar de prioridade pra coisas como tocar vídeo.</p>
<p>Cada sistema operacional implementa estratégias diferentes de scheduler, MacOS, Windows, BSDs, Linux e outros priorizam coisas diferentes e usam algoritmos diferentes. O próprio Windows demorou anos pra chegar no nível de granularidade do MacOS que sempre foi considerado melhor principalmente em criação multimídia porque ele não engasgava no gerenciamento de threads. Por isso músicos e DJs sempre preferiram Macs, especialmente em apresentações ao vivo: porque eles sabiam que o áudio não ia engasgar do nada porque o scheduler resolveu do nada dar a vez pra outra thread à toa. Isso acontecia com Windows e com Linux por isso eles eram bem inúteis em situações assim.</p>
<p>De novo, entenda que threads também tem comportamentos diferentes em diferentes sistemas operacionais. Quem saiu na frente na estratégia de threads foram os Macs no começo dos anos 2000 com o OS X. Windows era o segundo melhor, mais ou menos, e foi passado pelos Linux com o CFS só depois de 2007. Ou seja, o Linux moderno que conhecemos hoje só tomou forma de verdade a partir de 2007. Em termos de engasgos você vai notar que o Windows ainda é menos polido que MacOS e também desktop Linux. E esse fator do scheduler antigo foi mais um motivo de porque no mundo Linux processos continuaram sendo importantes.</p>
<p>De 2007, agora vamos voltar quase uma década, em 1999 surgiu um paper que começou a ajudar a mudar a forma como pensamos sobre concorrência no mundo de redes e web. Estamos falando do lendário paper sobre o problema dos C10K de Dan Kegel do antigo site cdrom.com discutindo quando os servidores de FTP da Simtel conseguiam servir até 10 mil conexões simultâneas sobre uma ethernet de 1 gigabit. Começamos a usar esse termo pra discutir como servir grandes quantidades de conexões simultaneamente. 10 mil parece muita coisa, mas um só servidor do Whatsapp hoje serve mais de 2 milhões de conexões simultâneas. O problema é que existem diversas estratégias pra fazer isso.</p>
<p>Eu já mencionei I/O mas agora vou definir um pouco melhor. I/O é qualquer entrada e saída do sistema, arquivos, rede, USB, teclado, monitor. Agora pense nos mainframes antigos que usavam dispositivos de fita, como os do IBM 701 que eu falei que gravavam dados a fantásticos 5.6 quilobytes por segundo. Se ele tivesse que ficar esperando a operação toda acabar o computador não ia fazer mais nada a não ser esperar. Por isso mainframes lendariamente tem milhares de canais paralelos de I/O. Mas o mesmo não acontece no barramento de computadores comuns como o que usamos.</p>
<p>Ou seja, mesmo que você consiga ter paralelismo disponível na sua CPU e sistema operacional, se você só tem um HD, não tem como milhares de threads escreverem ao mesmo tempo. O barramento de I/O vira gargalo. É como se você dividir o trabalho de escrever um livro entre 500 pessoas, onde cada uma escreve uma página. Então você vai acabar o livro no tempo que leva pra escrever uma única página. Porém, se todo mundo tiver que escrever no mesmo livro, não adiantou nada, só dá pra escrever uma página de cada vez.</p>
<p>Lembra quando falei de locks em memória global do processo compartilhado entre múltiplas threads? Algo semelhante acontece quando threads tentam acessar recursos restritos do sistema. É o que chamamos de operações que bloqueiam ou bloqueantes. Então se uma thread resolve escrever alguns gigabytes num arquivo, essa thread fica bloqueada até terminar, nesse caso outra thread pode executar outra coisa que não depende do HD enquanto a primeira fica bloqueada.</p>
<p>Conexões de rede também são bloqueantes, imagine uma thread fazendo download de alguma coisa ou algum cliente de rede pendurado numa thread. Essas operações são bloqueantes e a sequência de instruções da thread vai esperar até a operação terminar e desbloquear o processamento. Com o tempo os sistemas operacionais passaram a implementar algo chamado I/O assíncrono. Ou seja, em vez de bloquear o processamento, a thread pode pedir pra escrever alguma coisa num arquivo, mas o sistema operacional não vai bloquear a thread. Em vez disso a thread pede pra escrever e indica na função do sistema pra quando ele terminar, enviar um evento de término. Daí a thread pode configurar pra quando receber esse evento daí executar alguma outra sequência de instruções.</p>
<p>Ou seja, a programação fica dependente do evento de término da operação de I/O, seja operação de leitura ou de escrita, e é isso que muitos chamam de orientação a eventos. Isso não é nada novo, qualquer linguagem de programação dos anos 90 usada pra fazer aplicativos gráficos sempre usou programação orientada a eventos. Por exemplo, o código que é executado quando você tem o evento de clicar num botão, ou o evento de arrastar uma janela. Num Linux podemos enviar sinais a um processo, que é uma forma rudimentar de comunicação inter processos, como os sinais de kill como SIGTERM ou SIGINT. Aqui estamos estendendo para eventos de I/O do sistema operacional.</p>
<p>Mesmo sem I/O assíncrono podemos atender múltiplas conexões como no problema do C10K fazendo cada conexão ao servidor ser servida por forks do processo servidor. É como a primeira versão de um Apache resolvia isso. Cada nova requisição era gatilho pra um fork do processo principal que gerava um processo filho. Já expliquei que isso é pesado, especialmente em Windows.</p>
<p>A segunda alternativa é cada nova conexão recebida no processo servidor ser servida por uma nova thread dentro desse processo. Também já expliquei como threads são bem mais leves que um processo, mas temos o problema de gerenciar o compartilhamento da memória interna do processo entre múltiplas threads e os bugs que isso pode causar como race conditions e dead locks. Além disso quando estamos falando de 10 mil conexões ou mais, seria necessário mais de 10 mil threads, e eu já expliquei como context switching entre threads não é algo tão simples assim nem barato, você precisa de um scheduler muito bom e mesmo assim cada thread também consome recursos do sistema operacional. Versões subsequentes do Apache e outros servidores web como o IIS no Windows se valeram de threads pra servir milhares de conexões, mas não era o bastante.</p>
<p>A terceira opção como já imaginaram é usar esse tal recurso de I/O assíncrono, que permite que uma única thread ordene múltiplas operações de I/O ao sistema operacional sem que ele fique bloqueado. E o sistema operacional se vira pra realizar essas operações e vai notificando à thread a medida que cada operação vai terminando.</p>
<p>Cada sistema operacional implementa esse sistema de filas de eventos para I/O de formas diferentes. Sistemas derivados de BSD como o FreeBSD ou mesmo o MacOS tem uma syscall chamada kqueue. O Windows usa uma coisa chamada I/O Completion Port ou IOCP. E os Linux tinham syscalls chamada select, depois poll e finalmente a mais usada agora que é a epoll. Assim como gerenciamento de threads e schedulers, cada uma dessas implementações se comportam de formas diferentes. Estão vendo como os sistemas operacionais são mais diferentes do que só uma interface gráfica diferente?</p>
<p>Como resultado de toda a discussão no fim dos anos 90 e começo dos 2000 em cima do paper do C10K de Dan Kegel surgiu um projeto que nasceu com o objetivo de superar o Apache em conseguir servir dezenas de milhares de conexões simultâneas usando o mínimo possível de recursos do sistema, ou seja, sem usar a estratégia de uma conexão por processo ou thread e sim usando uma combinação de processos e chamadas de I/O assíncrono. É assim que nasce o projeto do servidor web mais rápido e escalável que temos até hoje, o NGINX de Igor Sysoev que começou a ser desenvolvido a partir de 2002 com lançamento da versão estável em 2004.</p>
<p>O NGINX tem um processo master que carrega outros processos que ele chama de “workers” e você vai ver que a palavra “worker” é um termo muito usado em arquiteturas com concorrência. Cada worker consegue gerenciar milhares de sockets de conexões de navegadores requisitando páginas web, por exemplo. Em vez de gerar um fork ou uma thread por conexão, ele registra a requisição via epoll num Linux e implementa um loop de eventos. Pense que o programa do worker é um loop que fica fazendo syscalls não bloqueantes de I/O e cada vez que o loop repete ele checa se veio o evento de término e aí executa o que precisa e devolve a página de volta pro requisitante. É uma forma de numa única thread conseguir servir milhares ou dezenas de milhares de conexões simultaneamente.</p>
<p>Não sei se fica claro e isso é importante. Se fosse como era antigamente, e eu fizesse um loop com chamadas de I/O, digamos que ler o arquivo HTML do HD levasse 1 segundo e enviar o HTML pela rede pro navegador levasse 1 segundo - é bem menos que isso na realidade, claro. E agora digamos que esse processo recebesse 10 requisições da mesma página. Levaria pelo menos 20 segundos pra servir todo mundo. Com I/O assíncrono poderia levar pouco mais do que os 2 segundos pra servir as 10 páginas.</p>
<p>Numa metáfora, do jeito antigo é como se no restaurante todo cliente pedisse direto pro chef o prato que quer, ele só consegue atender e fazer um prato de cada vez e cada cliente na fila precisaria esperar o prato do cliente anterior ficar pronto pra pedir o seu. Ou seja, o último cliente da fila precisa esperar todos os clientes anteriores ter o prato pronto antes de poder pedir o seu. Agora adicione um garçom, todos os clientes fazem o pedido pro garçom um atrás do outro e podem se sentar. Agora o último cliente da fila é atendido bem mais rápido. E o garçom vai passando os pedidos pro chef, que daí vai devolvendo os pratos à medida que forem ficando prontos, é como o evento de término que eu falei antes, mas os clientes já todos foram atendidos, fizeram os pedidos e podem ficar sentados fazendo outra coisa. É mais ou menos a diferença entre só ter I/O bloqueantes e ter I/O não-bloqueante. Não fica todo mundo bloqueado esperando em sequência.</p>
<p>Voltando ao NGINX, como cada worker é uma única thread, o master sobe na média um worker por core disponível na CPU então numa CPU quad-core subiríamos 4 workers. Cada worker com um loop de eventos que fica esperando os eventos de término de cada requisição de I/O que ele recebe, com capacidade de servir dezenas de milhares de conexões simultaneamente. E como ele não precisa criar uma thread por conexão, ele não perde tempo nem recurso fazendo context switching. Então um NGINX num bom servidor consegue servir até milhões de conexões simultaneamente, especialmente se for servir coisas simples como ler uma página HTML estática e só devolver sem processar lógica nenhuma. Se você vem de Javascript talvez já tenha ouvido falar desse pattern de arquitetura, isso se chama um Reactor, porque dentro do loop ele reage aos eventos de I/O que vão terminando.</p>
<p>Na mesma época, por volta de 2002 outro framework também implementou o mesmo pattern de Reactor com um event loop e usando I/O assíncrono, Node.js? Nope, ainda não. Esse foi o framework Twisted implementado em Python em 2002. Assim além de servir páginas HTML muito rápido, a cada loop do Reactor você podia executar processamento escrito em Python. Twisted introduziu ainda outro conceito: os Deferreds ou Futures. Mas vou explicar isso no próximo episódio.</p>
<p>Nesta primeira parte eu queria deixar bem explícito conceitos que acho que a maioria dos programadores tem problemas em entender logo no começo: concorrência e paralelismo e como os sistemas foram evoluindo pra resolver esse problema. Aliás, eu não expliquei isso antes mas vamos definir agora: concorrência e paralelismo. Simplificando, lembram dos computadores até os anos 90? Você podia ter tarefas que pausam e deixam outras executarem no seu lugar e ficam trocando de lugar, fazendo o tal context switching. Elas são concorrentes mas não são paralelas. Quando você tem dois ou mais cores na CPU aí sim, você realmente tem paralelismo, porque duas tarefas concorrentes, por exemplo threads, podem ser executadas verdadeiramente em paralelo. Seja em uma CPU com capacidade pra somente uma thread ou uma CPU com capacidade pra múltiplas threads, ainda assim você precisa de um scheduler, porque seu programa pode modelar e gerenciar mais threads concorrentes do que existem cores físicas pra executá-las.</p>
<p>É importante também que todos entendam como os sistemas operacionais são muito diferentes especialmente nesses aspectos de concorrência, como implementam threads de maneiras diferentes, como existem diferentes schedulers e no Linux você pode inclusive escolher schedulers diferentes pra casos diferentes. E como I/O assíncrono é outro recurso implementado de formas diferentes. Sabendo desses conceitos no próximo episódio vamos falar de coisas mais abstratas como corotinas, fibers e o conceito de deferreds ou futures.</p>
<p>Esse assunto é bem cabeludo, concorrência e paralelismo não tem uma resposta única que resolve todos os problemas. Cada tipo de problema vai precisar de uma solução diferente. Forks de processos funcionam. Multi-threads funcionam. I/O assíncrono é só mais uma opção nessa caixa de ferramentas e ainda vamos explorar outras opções. Além disso no mundo Linux o suporte a threads só ficou realmente bom a partir de 2007. Por isso você vê tanta coisa interessante no mundo de concorrência dando as caras principalmente na década de 2010. Muitos acham que event loops, o pattern de Reactor nasce com Node.js a partir de 2009, mas não, NGINX e Twisted pelo menos, já implementavam isso em 2002, derivados diretamente do objetivo de resolver o problema C10K de Dan Kegel de 1999 e esses conceitos mesmo já vinham pesquisas de anos antes.</p>
<p>Se tiverem dúvidas não deixem de mandar nos comentários abaixo, se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, assinem o canal e não deixem de clicar no sininho pra não perderem os próximos episódios. A gente se vê semana que vem, até mais.</p>
<p>Links:</p>
<ul>
<li>The Linux Scheduler: a Decade of Wasted Cores (<a href="https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/"target="_blank" rel="noopener">https://blog.acolyer.org/2016/04/26/the-linux-scheduler-a-decade-of-wasted-cores/</a>)</li>
<li>The C10K problem (<a href="http://www.kegel.com/c10k.html"target="_blank" rel="noopener">http://www.kegel.com/c10k.html</a>)</li>
</ul>
]]></content:encoded><category>sistemas-distribuidos</category><category>backend</category><category>performance</category><category>akitando</category></item><item><title>[Akitando] #40 - Entendendo Back-End para Iniciantes em Programação (Parte 1) | Série "Começando aos 40"</title><link>https://akitaonrails.github.io/2019/02/20/akitando-40-entendendo-back-end-para-iniciantes-em-programacao-parte-1-serie-comecando-aos-40/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/02/20/akitando-40-entendendo-back-end-para-iniciantes-em-programacao-parte-1-serie-comecando-aos-40/</guid><pubDate>Wed, 20 Feb 2019 17:00:00 -0300</pubDate><description>&lt;p&gt;Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;Akitando&lt;/a&gt;. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Qjk-cSW-jk4"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3&gt;Descrição no YouTube&lt;span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Este é o 5o episódio da série &amp;ldquo;Começando aos 40&amp;rdquo;. Você deve assistir os episódios anteriores da série pra entender onde estamos e recomendo assistir os 2 vídeos da série &amp;ldquo;Sua Linguagem Não É Especial&amp;rdquo;. No episódio de hoje vou começar a introduzir os conceitos básicos para o que chamamos de &amp;ldquo;back-end&amp;rdquo;, que na prática é a própria introdução à programação.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Disclaimer: esta série de posts são transcripts diretos dos scripts usados em cada video do canal <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">Akitando</a>. O texto tem erros de português mas é porque estou apenas publicando exatamente como foi usado pra gravar o video, perdoem os errinhos.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Qjk-cSW-jk4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h3>Descrição no YouTube<span class="hx:absolute hx:-mt-20" id="descrição-no-youtube"></span>
    <a href="#descri%c3%a7%c3%a3o-no-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Este é o 5o episódio da série &ldquo;Começando aos 40&rdquo;. Você deve assistir os episódios anteriores da série pra entender onde estamos e recomendo assistir os 2 vídeos da série &ldquo;Sua Linguagem Não É Especial&rdquo;. No episódio de hoje vou começar a introduzir os conceitos básicos para o que chamamos de &ldquo;back-end&rdquo;, que na prática é a própria introdução à programação.</p>
<p>A intenção do vídeo não é serem aulas nem tutoriais, mas conectar os pontos de diversos assuntos que vocês vão encontrar de forma desconexa se sair pesquisando pelo Google. Se você fez faculdade, já deveriam saber tudo que vou dizer aqui, então veja o que faltou nos estudos e acelere!</p>
<p>Em resumo, vou falar rapidamente sobre os conceitos mais simples, linguagem Assembly, compiladores, interpretadores, máquinas virtuais, bytecode, processos, forks, threads, tudo que qualquer programador tem obrigação de saber na ponta da língua e em paralelo vou passar rapidamente de novo pelos anos 90.</p>
<p>Como o episódio ficou longo, eu dividi em 2 partes. Na de hoje vou só até o crash das ponto coms em 2001, e vamos continuar dali na Parte 2 da semana que vem.</p>
<p>Links:</p>
<ul>
<li>
<p>Playlist &ldquo;Começando aos 40&rdquo; (<a href="https://www.youtube.com/watch?v=O76ZfAIEukE&amp;list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki"target="_blank" rel="noopener">https://www.youtube.com/watch?v=O76ZfAIEukE&list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki</a>)</p>
</li>
<li>
<p>Sua Linguagem Não É Especial Parte 1 (<a href="https://www.youtube.com/watch?v=p9-WuJbVHHc"target="_blank" rel="noopener">https://www.youtube.com/watch?v=p9-WuJbVHHc</a>)</p>
</li>
<li>
<p>Sua Linguagem Não É Especial Parte 2 (<a href="https://www.youtube.com/watch?v=XcTTajFENHI"target="_blank" rel="noopener">https://www.youtube.com/watch?v=XcTTajFENHI</a>)</p>
</li>
<li>
<p>Conheça a história (brasileira) da informática (<a href="https://keepgeek.wordpress.com/2010/08/01/conheca-a-historia-brasileira-da-informatica/"target="_blank" rel="noopener">https://keepgeek.wordpress.com/2010/08/01/conheca-a-historia-brasileira-da-informatica/</a>)</p>
</li>
<li>
<p>Meet the Unitron Mac 512 – the World’s First Macintosh Clone (<a href="https://www.cultofmac.com/266710/meet-unitron-mac-512-worlds-first-macintosh-clone/"target="_blank" rel="noopener">https://www.cultofmac.com/266710/meet-unitron-mac-512-worlds-first-macintosh-clone/</a>)</p>
</li>
</ul>
<p>Canais Retro:</p>
<ul>
<li>The 8-bit Guy (<a href="https://www.youtube.com/channel/UC8uT9cgJorJPWu7ITLGo9Ww"target="_blank" rel="noopener">https://www.youtube.com/channel/UC8uT9cgJorJPWu7ITLGo9Ww</a>)</li>
<li>Modern Vintage Computer (<a href="https://www.youtube.com/channel/UCjFaPUcJU1vwk193mnW_w1w"target="_blank" rel="noopener">https://www.youtube.com/channel/UCjFaPUcJU1vwk193mnW_w1w</a>)</li>
<li>LGR (<a href="https://www.youtube.com/user/phreakindee"target="_blank" rel="noopener">https://www.youtube.com/user/phreakindee</a>)</li>
</ul>
<p>Me sigam também em outras redes sociais:</p>
<ul>
<li>Twitter (<a href="https://twitter.com/akitaonrails"target="_blank" rel="noopener">https://twitter.com/akitaonrails</a>)</li>
<li>Instagram (<a href="https://instagram.com/akitaonrails"target="_blank" rel="noopener">https://instagram.com/akitaonrails</a>)</li>
<li>Facebook (<a href="https://facebook.com/akitaonrails"target="_blank" rel="noopener">https://facebook.com/akitaonrails</a>)</li>
<li>Quora (<a href="https://www.quora.com/profile/Fabio-Akita"target="_blank" rel="noopener">https://www.quora.com/profile/Fabio-Akita</a>)</li>
<li>Blog (<a href="https://www.akitaonrails.com"target="_blank" rel="noopener">https://www.akitaonrails.com</a>)</li>
</ul>
<h2>Script<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Espero que vocês tenham sobrevivido ao meu último episódio onde gastei um longo tempo tentando dar contexto sobre as tecnologias de Front-End. Também espero que tenham entendido a receita: na dúvida, encontrem o contexto de porque determinada tecnologia foi criada. Tecnologia são ferramentas, ferramentas foram criadas com um propósito. Por isso não existe uma única tecnologia que resolve todos os problemas. Já diria o sábio que pra quem acha que só existe martelo, todos os problemas são pregos.</p>
<p>E seguindo nessa mesma linha, no episódio de HOJE, vou tentar dar contexto para as tecnologias que chamamos de Back-end. Porém, back-end é muito, mas muito maior do que front-end e mesmo se eu fizer um episódio de uma hora, não vai dar pra explicar tudo que eu quero. Eu comecei a escrever o script deste episódio, aí cheguei na página 10, fui escrevendo e cheguei na página 20 e mesmo vocês tendo gostado do episódio anterior que deu quase 50 minutos se eu tentar encaixar tudo agora vai facilmente dar mais de 2 horas. E se eu fizer isso vou correr demais e talvez não explicar direito, então vou ser obrigado a cortar em vários episódios e mesmo assim vai ficar grande! Então vamos lá porque temos bastante coisa pra cobrir.</p>
<p>Pra começar, a pergunta mais comum quando se fala em programação back-end pra Web é qual linguagem aprender? Javascript? Java? C#? PHP? Python? Ruby? Elixir? Go? Scala? Clojure? Rust?  Nos episódios sobre Sua Linguagem não é especial que eu espero que tenham assistido, eu dei uma rápida introdução nos primeiros 40 anos de história das linguagens e como uma linguagem veio influenciando as outras. Assistam essa história antes e, mesmo que não tenha intenção de se tornar um desenvolvedor front-end, assista o episódio anterior antes pra ter o contexto necessário. Se não assistiu vai lá, eu espero ….</p>
<p>Pronto, já foi? Então vamos lá!</p>
<p>(&hellip;)</p>
<p>No Brasil infelizmente nós praticamente pulamos a história da computação até os anos 80 e só começamos mais ou menos nos anos 90. Isso porque antes dos anos 80 vivíamos numa ditadura, e perdemos o timing da evolução dos transistores e nunca tivemos um mercado decente de microprocessadores. Nosso mercado era fechado a importar coisas de fora. Maldita reserva de mercado, toda reserva de mercado é um puta erro. A gente precisava ir até o Paraguai pra conseguir comprar máquina decente. Havia também computadores nacionais como os da Itautec e Microsiga que faziam clones de IBM PC e mesmo de outros como os famosos Sinclairs, quem não lembra da série TK como o TK-90? Tinha a Hotbit e Gradiente fazendo MSX que eu gostava bastante. E tinha a Unitron que fazia clones de Apple II e chegou até mesmo a fazer um clone excepcional do Mac 512, que era quase igual e rolou até uma controvérsia direto com a Apple onde eles quase pediram sanções dos EUA contra exportações do Brasil por causa disso.</p>
<p>Essa história é interessante e explica porque por muitos anos Steve Jobs odiou o Brasil, vou deixar linkado na descrição abaixo sobre isso. E tivemos até sistemas operacionais nacionais como o Sisne que era um clone de MS-DOS, só que em português. Eu brincava de Basic com computadores como Apple II e MSX quando ia na casa de amigos, meu primeiro mesmo veio do paraguai no fim dos anos 80. Foi um PC XT de talvez 10 Mhz ou algo assim, poderosos 1 MB de RAM e 10 MB de HD, com monitor CGA verde, de incríveis 4 tons de verde. Fiz muitos programas em dBase e Clipper nessa máquina, fiz muitos trabalhos de escola com ele. E joguei muito também. Você não precisa da máquina mais potente pra aprender a programar, você precisa aprender a controlar a máquina que tem e tirar o absoluto máximo que ela pode dar. Ninguém nunca vai dirigir bem uma Ferrari se sequer tem coordenação pra trocar marcha num Gol.</p>
<p>Vocês vão notar que mesmo hoje em dia, em pleno 2019, é muito raro encontrar programadores de cabelo branco. Eu mesmo só comecei a ganhar os meus nos últimos poucos anos. E esse é um dos motivos: havia muito pouco programador antes dos anos 80. Mesmo durante os anos 80 era difícil ser programador. Então os que estão na ativa são ainda mais raros. Nos Estados Unidos você vê vários, incluindo famosos como os Uncle Bob da vida. Por isso temos muito pouca gente realmente com décadas de experiência. Se programador no Brasil só se tornou uma carreira realmente mainstream depois do advento da Web, antes era bem mais nicho. E por isso você vê muito pouca gente que pode dizer coisas como: trabalhei em projetos com Lisp, ou Smalltalk, ou PROLOG, sem ser do meio acadêmico.</p>
<p>Bom, antes de falarmos sobre as linguagens novas, precisamos começar do começo. Todo hardware, mais especificamente a CPU vem de fábrica entendendo um certo conjunto de instruções ou funções. Eles tem registradores que é como se fosse um tipo de memória. Você coloca valores nesses registradores e chama uma instrução que é mais ou menos a mesma coisa que chamar uma função passando argumentos, se você começou a aprender alguma linguagem. Daí a CPU executa alguma coisa e grava o resultado em outros registradores e você pode ler a resposta. A linguagem que usamos pra falar diretamente com a máquina é Assembly, e seu montador, o assembler, traduz o código escrito em texto com mnemônicos como JMP ou ADD ou MOV diretamente nas instruções binárias da máquina.</p>
<p>Além do hardware temos instruções específicas do sistema operacional, o que chamamos de syscalls ou chamadas de sistema. Então o tal binário nativo que falamos são instruções pra máquina e pro sistema operacional que é uma abstração da máquina.  Sempre que falamos em abstrações pense que é uma forma de pegar uma sequência de instruções que, por si só, podem ser difíceis de entender o que vai fazer e encapsular numa chamada só com um nome mais descritivo. É o que você chamaria de funções. Um sistema operacional tem várias funções que abstraem instruções mais simples que a máquina oferece, como ler arquivos, ou ou alocar espaço na memória pra um programa.</p>
<p>É extremamente chato e trabalhoso escrever Assembly diretamente, além de ser bem ineficiente hoje em dia, tanto porque precisamos escrever muito código pra fazer uma aplicação inteira e porque a performance não vai ser melhor e vou explicar porque a seguir. Mas pior, o código que escrevemos é totalmente dependente da CPU que estamos usando. Programar Assembly de Intel x86 ou PowerPC ou ARM exigem instruções diferentes. Por isso temos linguagens como C. Um “compiladores” traduz o código como escrito em C e realiza diversas otimizações antes de terminar de traduzir em instruções binárias. Mas mais importante, podemos usar o mesmo código C com diferentes compiladores pra gerar as instruções de um Intel x86 ou ARM sem mudar o código C.</p>
<p>Uma linguagem como C cria um programa binário nativo. Ou seja, ele depende de chamar diretamente instruções do sistema operacional e da máquina. Um compilador faz muito mais que só traduzir código um pra um pra instruções de máquina, ele vai tentar o possível pra melhorar seu código primeiro. Vai tirar instruções que não servem pra nada. Vai tentar executar coisas fora de ordem se o resultado for o mesmo. Vai trocar pedaços inteiros por instruções mais simples. Vai tentar prever o que é o próximo resultado e já pré-calcular e assim por diante. Ele literalmente faz mágica, portanto parta do princípio que seu código deve ser escrito primariamente pra que outro ser humano consiga entender e é trabalho do compilador melhorar pra ficar mais eficiente na máquina. Você eventualmente pode otimizar seu código pra ficar mais eficiente quando ficar complexo, mas se sua linguagem exige que você faça isso o tempo todo, sua linguagem é defeituosa.</p>
<p>No processo de transformar um código texto em um binário que executa na máquina você precisa primeiro de um compilador. Só que ninguém escreve um programa 100% do zero. Normalmente você nem fala diretamente com o hardware ou o sistema operacional. Existem dezenas de bibliotecas que você deve reusar e que vão facilitar seu trabalho. No C você vai precisar de coisas como LIBC ou no C++ você talvez vai usar coisas como Boost. São grandes bibliotecas reusáveis pra você não ter que ficar reinventando a roda toda hora. No Windows seriam as DLLs que você já deve ter visto, como win32. No Linux a extensão dessas bibliotecas binárias é ponto SO. Agora você precisa de um linker que vai ligar seu programa compilado com essas bibliotecas. E você tem duas opções, linkar estaticamente ou linkar dinamicamente.</p>
<p>Pra linkar estaticamente você precisa do binários antes dele virarem um SO ou DLL, ou seja do que chamamos de arquivo de objetos, normalmente com extensão ponto O que é transformado em ponto A ou ponto LIB em Windows. Se seu programa é um binário e a biblioteca é outro binário, ao linkar estaticamente você vai ter as duas coisas mescladas num único bináriozão. Porém se todo programa que você fizer tiver que mesclar o binário das bibliotecas, vai ter um binários gigantes. Por outro lado você pode linkar dinamicamente, pra isso você precisa do arquivo de cabeçalho da biblioteca que é um código que declara quais funções estão expostas no binário dessa biblioteca. Dessa forma todos os programas vão ter tamanho menor e vão reusar o mesmo binário da biblioteca que está instalado no seu sistema.</p>
<p>Porém ao copiar esse binário pra outra máquina você precisa garantir que a biblioteca dinâmica está lá também. É por isso que muitos programas no Windows antigamente mandavam você instalar antes o runtime do Visual Basic 6 ou hoje em dia o runtime do .NET ou outros componentes como DirectX pra games. E mais, você precisa instalar a versão certa de cada biblioteca ou vai ter problemas. Se você já está instalando e configurando seu Linux do zero na mão, já deve ter aprendido sobre variáveis de ambiente. Não basta só baixar as bibliotecas e copiar em algum diretório qualquer. Pra que seus executáveis encontrem as bibliotecas, se você instalar na mão, vai precisar mexer em variáveis de ambiente como LD_LIBRARY_PATH que indicam onde procurar por elas.</p>
<p>Essa discussão se linkar dinamicamente ou estaticamente é melhor é uma das coisas que programadores gostam de discutir. Ambas tem vantagens e desvantagens. Se você tem todo o código-fonte de tudo disponível o tempo todo e quer um binário que você possa copiar pra outra máquina e não se preocupar com dependências, pode compilar tudo estático, ficar com um binário gigante e quando eu digo gigante estou falando de binários de 200Mb ou mais ainda. É o que linguagens novas como o Go do Google fazem: ele prefere baixar o código fonte das dependências e compilar e linkar tudo estaticamente. Tem algumas exceções que vou falar depois mas na maior parte do tempo todo binário de Go você pode gerar na sua máquina, copiar num servidor e na maior parte do tempo tudo funciona sem se preocupar em precisar instalar dependências dinâmicas.</p>
<p>Mas se é tão simples assim e ficar se preocupando com versões de bibliotecas dinâmicas ou se elas estão instaladas é tão chato, porque não compilar tudo estaticamente? Primeiro porque você não precisa ter o código fonte das bibliotecas, só o arquivo de cabeçalho que lista suas funções. Segundo, porque digamos que você tenha 10 programas, todas elas linkam estaticamente uma biblioteca de criptografia. Digamos que descobrimos que tem um bug de segurança crítico nessa biblioteca. Se todos os programas estiverem linkados dinamicamente, basta recompilar só a biblioteca de criptografia e substituir na máquina em cima da antiga e todos os 10 programas automaticamente vão carregar a versão corrigida. Caso contrário, você agora precisa recompilar todos esses 10 programas pra que eles gerem novos binários com as correções e substituir na máquina um a um. Tudo sempre depende de pra que serve seu programa. Aliás, programação é muito isso: aprender a fazer escolhas entre opções que não estão erradas, que você precisa saber o contexto. Nem sempre as escolhas são simples, vá se acostumando com isso. É como em arte, qual cor é melhor? Vermelho? Azul? Amarelo? Não existe cor certa. Existe saber usar a cor certa no lugar certo.</p>
<p>Na maioria das distribuições Linux existe o conceito de um gerenciador de pacotes. No Redhat tempos o RPM e a ferramenta yum, em distribuições derivadas de Debian como o Ubuntu tempos DEB e a ferramenta apt.
Em Arch Linux e derivados como Manjaro temos o pacman. E assim por diante. Mas quando não existe o pacote de algum programa que queremos você normalmente baixa um tarball que é tipo um zip, e vai precisar descomprimir, compilar e instalar a partir desse código fonte. E você normalmente vai ver a seguinte sequência de 3 comandos que precisa executar na linha de comando. Primeiro tem o ./configure. Configure é responsável justamente por checar se você tem todas as dependências que se precisa pra compilar já instalados na sua máquina, se não tiver ele vai dar erro e você precisa instalar as dependências e rodar configure de novo. Daí você roda make. Make lê um arquivo chamado Makefile que vem no projeto e que declara tarefas que a ferramenta make pode executar, e o default ou padrão é compilar.
Ele vai chamar compiladores como o GCC ou CLANG e transformar o código C em binários.
E por fim você tem o make install que é o make chamando a tarefa install do Makefile que vai pegar esse binário que acabou de compilar, dar permissão de executar, e mover pra um diretório que o PATH consiga encontrar, como o /usr/local/bin ou pra outro lugar arbitrário tipo /opt/blabla e colocar esse diretório no PATH se precisar. Toda vez que você faz essa sequência vê como demora.</p>
<p>Programas modernos são dependentes de dezenas de bibliotecas e o compilador tem bastante trabalho pra transformar centenas de arquivos ponto C em ponto O e depois no binário final linkado estaticamente ou dinamicamente com as bibliotecas que precisa, e só aí finalmente você pode executar seu programa. Em Windows você quase nunca instala programas assim, você normalmente já baixa o binário que vem embutido num instalador. O instalador normalmente instala as dependências que precisa. Em Windows, ao contrário de Linux, preferimos manter todas as versões de uma determinada biblioteca no sistema. Por isso você tende a ter um sistema operacional mais gordo que o Linux e com bibliotecas bem antigas ainda disponíveis, mas a vantagem é que conseguimos pegar um binário feito pra Windows 98 e ele vai provavelmente funcionar no Windows 10. MacOS é um híbrido dos dois conceitos, ele mantém muitas bibliotecas antigas até um certo ponto, mas não tão longe quanto o Windows, e ao contrário do Windows ele trás já os compiladores e dá a opção de compilar direto do código-fonte mantendo os cabeçalhos de bibliotecas que precisam pra compilar. Por isso existem adaptações de programas de Linux que funcionam perfeitamente em MacOS.</p>
<p>Você vai ver mais sobre esse tipo de escolha quando estiver usando linguagens que compilam em binários nativos, como C, C++, Pascal ou Object Pascal do Delphi, Objective-C ou Swift pra iOS e até em linguagens novas como o Google Go que eu falei a pouco e o Rust da Mozilla. Mas do outro lado espectro existe o conceito de um interpretador. Um interpretador é em si só um programa mas a função dele é ler código que você escreveu e traduzir em instruções pra máquina sem necessariamente precisar compilar esse código em binário nativo. Ou seja, ele depende de ter o código fonte do seu programa e traduzir, ou interpretar, o código toda vez que carrega.</p>
<p>Ele contém partes de um compilador, como o parser. Pra interpretar o código que você escreveu, precisamos de um programa que leia esse código e interprete de acordo com uma gramática da linguagem e traduza isso em instruções de máquina ou uma representação similar, pra isso serve o parser. Aqui que a coisa começa a ficar confuso, mas vamos focar pra tentar entender. Originalmente um interpretador é só um programa como eu já disse, você executa na linha de comando e passa como argumento o caminho pra um arquivo de texto que contém o código que você escreveu na linguagem que esse interpretador entende. Ele vai carregar esse arquivo, vai usar o parser pra checar que você escreveu certo, vai transformar numa representação interna que ele entende, normalmente numa árvore - lembra que eu falei no episódio anterior da importância de você entender estruturas de árvores? E finalmente vai começar a interpretar e executar as instruções pra fazer o que você pediu. Quando terminar seu programa, o interpretador também termina.</p>
<p>É assim que funcionam originalmente linguagens como Perl, PHP, Python, Ruby, até Javascript. Elas variam em algumas funcionalidades, por exemplo, Perl e Python permitem pré-compilar seu código fonte na representação intermediária que eu falei. O termo “compilar” pode confundir porque você pode achar que ele está compilando num binário nativo como o compilador de C, mas é diferente. Ele apenas vai economizar o tempo de ler o arquivo texto e parsear na tal estrutura de árvore que num Ruby chamamos de AST ou Abstract Syntax Tree. Por isso em Perl você tem a extensão ponto PL pro código fonte e ponto PLX pro pré-compilado ou em Python você tem ponto PY pro código fonte e ponto PYC pro pré-compilado. Ruby e Javascript não tem essa funcionalidade ainda. Na prática tanto faz, hoje em dia os HDs são rápidos o suficiente pra não importar tanto isso.</p>
<p>Antigamente fazia sentido porque a principal função de um interpretador era permitir que você pudesse criar programas de linha de comando que executam e terminam muito rápido. Você podia chamar de duas formas: digitando perl e passando como argumento o arquivo do código ou ligando o flag de execução direto do arquivo texto de código e tendo como primeira linha o que chamamos de “shebang” que é a cerquilha com exclamação e o caminho absoluto pro executável do interpretador. E se você já fuçou pela Web por scripts deve ter visto isso. Existia ainda outra opção que era criar um pacote que embute o interpretador junto com a representação do seu código fonte num único executável. O Perl permitia isso e é o que o Visual Basic antes da versão 6 também fazia. No final parecia que era um binário nativo como de um C, mas na verdade era um pacote que embutia as duas coisas. De curiosidade eu disse Visual Basic até o 6 porque no 6 você tinha a opção de compilar o código fonte em binário nativo quando ele passou a adaptar e usar o compilador do Visual C++. Visual Basic 6 é um produto que me impressionou bastante na época.</p>
<p>Como eu disse antes, um interpretador originalmente foi feito pra iniciar rápido e terminar rápido. No episódio anterior eu comecei a falar sobre servidores web. Todo sistema operacional lida com processos. Toda vez que você executa uma aplicação, seja o executável nativo compilado em C ou os interpretadores que citei antes que também são feitos em C ou C++, o trabalho do sistema operacional é carregar, alocar memória e criar um processo em execução. Em ambientes UNIX ou principalmente Linux criar processos é uma coisa relativamente barata, ordens de grandeza se comparar com MacOS ou Windows. Em Windows criar processos envolve mais peso ou o que chamamos de overhead, não é uma coisa barata e nem é porque ele é pior mas porque tem requerimentos diferentes. Pensando antigamente num UNIX, um programador naturalmente  faria um programa que é executado e, se for um servidor TCP, ele só consegue servir um cliente TCP de cada vez, como já expliquei no episódio anterior.</p>
<p>Pra conseguir servir múltiplos clientes TCP ao mesmo tempo, a coisa mais fácil de se fazer é um FORK do processo, ou seja, criar uma cópia do programa em execução na memória. Cada nova conexão o servidor faz um novo FORK. E esse fork é razoavelmente rápido de criar e usa pouca memória porque ele reusa os mesmos bits do programa pai original que também está na memória, é um truque do sistema operacional e esse recurso é chamado de COW ou Copy on Write que evita fazer uma cópia inteira do executável da memória pra outro espaço de memória. Então se um processo precisa de 200 kbytes em memória, no instante que fizer fork ele não vai dobrar a memória e usar 400, vai usar muito menos que isso.</p>
<p>Um servidor web como o Apache antigo que eu falei, no começo só usava FORK. Por isso ele era simples. E mais do que isso, ganhou a capacidade de executar programas dependendo da URL que era pedido, através do padrão que falei que ficou conhecido como CGI. No começo esses executáveis eram só programas nativos compilados de C. Mas falei que rapidamente fizeram Perl funcionar no Apache também e começamos a era da web dinâmica. Pois bem, interpretadores com programas curtos iniciam e terminam rápido. Mas se o programa vai ficando mais e mais complicado, incluindo coisas custosas como conectar num banco de dados ou manipular arquivos, ou carregar muitos dados em memória, é muito custoso ficar iniciando e terminando esses programas toda vez. Foi quando surgiu a primeira gambiarra na forma de coisas como o mod_perl e depois o mod_php que são módulos compilados dentro do Apache e que dá inclusive acesso às estruturas internas do Apache. Toda vez que um novo fork do Apache é feito ele já pré-carrega com a capacidade de executar Perl ou PHP sem precisar carregar o interpretador separadamente. Isso é mais rápido do que CGI puro, porém é um terrível buraco de segurança já que bugs no Perl ou no PHP dão acesso ao Apache inteiro e a tudo que ele tem acesso na máquina.</p>
<p>A segunda gambiarra pra evitar ter que carregar os interpretadores a cada nova requisição foi o FastCGI que é basicamente um segundo servidor que carrega paralelo ao Apache ou outro servidor web, ele mantém o interpretador e seu programa ativos em memória e pode servir múltiplas requisições sem precisar terminar e reiniciar toda vez. Daí o servidor web e o servidor de FastCGI se comunicam via TCP ou outras formas como Unix Sockets que é recurso em Unix para que dois processos consigam se comunicar. Quando uma requisição chega ao Apache ele passa essa informação e outros metadados pro servidor de FastCGI executar o programa Perl ou PHP e devolve a resposta resultante de volta pro Apache devolver pro navegador. Uma grande vantagem disso era poder isolar os dois servidores de forma que um não invada demais o espaço do outro em caso de bugs de segurança.</p>
<p>Mas em Windows, você não tem o conceito de FORK na API de alto nível do sistema, o CreateProcess. Internamente o kernel do Windows permite algo parecido com FORK incluindo o recurso de COW ou Copy on Write, mas não é tão usado e mesmo assim ainda é uma ordem de grandeza mais lento. Isso explica porque os primeiros Apache eram uma porcaria em Windows, nem perto da performance num Linux e por isso dávamos preferência a usar o próprio IIS da Microsoft. Só porque dois programas compilam pra dois sistemas operacionais diferentes e rodam, não significa que rodam igual. Coisas feitas pra Windows funcionam melhor em Windows e coisas feitas pra Linux só rodam bem em Linux. Lembre-se disso. Dada essa limitação você acaba sendo obrigado a usar mais Threads que são linhas de execução em paralelo dentro de um mesmo processo.</p>
<p>Já falamos da discussão de compilação estática vs compilação dinâmica. É hora de falar de outro dilema da programação: programação baseada em processos com fork versus um processo com múltiplas threads. Hoje em dia usamos muito mais Threads mas processos com fork não estão obsoletos, por exemplo, o conector do banco de dados PostgreSQL funciona via fork assim como muitos outros softwares. Como eu disse antes, toda vez que você executa um programa o sistema operacional carrega os bits em memória e inicia uma entidade chamada processo. O sistema operacional tem o poder de matar esse processo ou pausar e reiniciar sua execução. Dentro do processo ele não tem consciência de todos os outros processos ao seu redor a menos que tenha privilégios de perguntar ao sistema operacional. Pra todos os efeitos, ele se sente como se fosse a única coisa rodando na máquina. Quando o processo precisa escrever na memória ele não escreve diretamente.</p>
<p>Pense na memória como um livro com páginas numeradas sequencialmente de 1 até um número grande. Num computador de 32-bits temos a capacidade de mapear de 1 até 2 elevado a 32 ou seja mais de 4 bilhões ou 4GB. Num computador de 64-bits temos a capacidade de mapear até 2 elevado a 64 ou seja mais de 18  quintilhões de bytes ou 18 exabytes, que é bastante coisa. Se tem vários programas na memória você imaginaria que o certo seria cada programa começar a partir de uma determinada página dessa memória, tipo o Word começando na página 1, o Excel na página 100, o Chrome na página 200 e assim por diante, mas isso seria extremamente complicado de controlar. Em vez disso todo mundo começa na página 1, mas de uma memória virtual e o sistema operacional que se encarrega de traduzir a página 1 da memória virtual do Excel pra página 100 da memória real. Então o Excel nunca teria como acessar a memória do Chrome. E assim começamos a criar um mínimo de segurança entre os programas. No MS-DOS antigo que rodava no que chamamos de modo real, cada programa tem acesso a todos os endereços reais da máquina, mas não tinha problema porque no MS-DOS só dava pra rodar um programa de cada vez. Pra trocar do Word pro Excel, tinha que primeiro sair do Word e daí carregar o Excel. Mas hoje em dia onde vários programas.</p>
<p>Isso facilita muito pra nós programadores porque não precisamos nos preocupar com a memória de terceiros. E se precisamos fazer algo como um servidor Web onde cada conexão de um cliente TCP precisa rodar em paralelo a outras conexões, podemos fazer só um FORK do processo pra cada um. Cada FORK é um processo isolado e daí um não afeta a memória do outro. Tudo funciona perfeitamente. Mas se é caro,  ou não é possível fazer FORKS, a única outra solução é dentro de um único processo iniciarmos múltiplas Threads, que são bem mais baratas que forks. Cada Thread daí pode servir um cliente TCP conectado ao processo. Mas agora começamos a ter problemas. Porque não existe memória isolada pra cada thread, dentro do processo cada thread tem acesso a toda a memória virtual do processo. Por isso você vai ouvir falar, caso ainda não tenha passado por isso, que escrever programas que são multi-thread é uma puta dor de cabeça, porque você precisa garantir o que chamamos de thread-safety ou seja, código seguro pra rodar em threads. Nesse caso, você como programador tem a responsabilidade de escrever código que, quando executado numa thread, não pise sem querer na memória de outra thread executando ao mesmo tempo. A forma de fazer isso normalmente envolve toda vez que você precisa escrever algum dado em memória, primeiro você notifica o sistema que vai escrever, pega o que chamamos de um lock, escreve e depois libera esse lock. Se falhar em pegar o lock, se arrisca a corromper a memória de outra thread. Se esquecer de soltar o lock vai causar um deadlock e bloquear outras threads de pegar o lock. Pense essa rotina em milhares de linhas de código. Ou seja, dor de cabeça.</p>
<p>Recapitulando, um programa binário é executado pelo sistema operacional num espaço de memória isolado, chamado processo. Cada processo pode ser clonado via fork num Linux. E cada processo pode rodar uma ou mais threads dentro dele. Um servidor web é um processo e pra servir múltiplas requisições ele faz um fork pra cada uma. Daí nasce CGI ou FastCGI. Mas em Windows como forks são ordens de grandeza mais caros, o servidor web IIS precisa de outro truque e isso veio na forma do padrão ISAPI que é como se fosse um programa escrito pra CGI mas com modificações pra ser thread-safe e compilado dentro de uma biblioteca DLL que é carregado pelo IIS. O servidor web da Netscape, que não existe mais, tinha outro padrão similar, o NSAPI. Muitos programadores de Delphi já devem ter feito programas pra Web e compilado em DLLs ISAPI ou NSAPI.</p>
<p>Com o advento de coisas como CGI, FastCGI, ISAPI, NSAPI e outras arquiteturas e o fato de termos interpretadores que possibilitam escrever código que tem pouca dificuldade de rodar porque não precisamos nos preocupar com compilação e gerenciamento de dependências, é quando linguagens interpretadas como Perl, PHP e ASP Clássico ganham notoriedade no mercado, especialmente com o timing do início da bolha das ponto coms a partir do meio dos anos 90.</p>
<p>Voltando a threads mais um pouco, como faz pra várias threads rodarem em paralelo? Pense assim, um processo tem pelo menos 1 thread. Pra um processo executar múltiplas threads ao mesmo tempo, o CPU tem uma coisa chamada agendador ou scheduler, mas só pode rodar um número limitado de threads ao mesmo tempo. Então ele pausa uma thread em execução de acordo com vários critérios e acorda outra thread que tava pausada pra dar chance dela rodar e vai fazendo isso com os vários threads de tal maneira que você tem a impressão que estão todos rodando ao mesmo tempo. E quando você pede o tal lock é esse scheduler que vai evitar que outras threads pisem no seu calo por exemplo. Programação multi-thread vai ser uma longa jornada pra vocês que estão iniciando e é até hoje considerada uma das coisas mais difíceis pra um programador realmente entender, só competindo com gerenciamento de memória e segurança.</p>
<p>Entendendo esses conceitos básicos que eu expliquei muito rapidamente, podemos começar a entender coisas como o surgimento do Java. Você precisaria ter tido experiência com C++, Smalltalk e Lisp pra entender realmente como um Java nasce mas vamos começar por aqui. Em 1991 surge uma nova linguagem na Sun com o codenome Oak, e o objetivo inicial era ser um sistema pra set-top-boxes. Naquela época se tinha o conceito que as pessoas teriam “caixas” multimidia nas salas integrando a tv a cabo e outros conteúdos. Esse conceito anos depois viria a se tornar um Tivo ou Roku ou mesmo Google Chromecast e Apple TV de hoje, que inclusive seriam substituídos pelas Smart TVs e consoles de videogame também. Lembrem-se que Linux ainda estava só no começo, ainda era rudimentar, e coisas como Android estão muitos anos à frente ainda.</p>
<p>Java embute alguns conceitos de compiladores e interpretadores. Mais especificamente de máquinas virtuais, por isso você tem a JVM ou Java Virtual Machine. Uma máquina virtual é uma evolução de um interpretador. Em vez de ser só um programa que executa código escrito numa determinada linguagem, como Java, a JVM tem uma ambição maior, ele quer ser o próprio sistema operacional e abstrair a máquina real por baixo. Pro programa em Java não existe um sistema operacional Linux ou Windows rodando num processador Intel ou ARM. Só existe a JVM.</p>
<p>O conjunto todo que a Sun inventou tem várias ferramentas. Diferente de um interpretador e mais similar a uma linguagem compilada como C++, ele separa a fase de compilação da fase de execução. O compilador JAVAC pega seu código fonte escrito em Java (eles usaram o mesmo nome pra tudo e isso dificulta explicar, eu sei) e compila numa representação intermediária que chamamos de Java Byte Code. Na prática, lembra que eu falei que o compilador de C traduz seu código C em instruções nativas do sistema operacional e do processador? E se seu sistema operacional e processador forem a JVM? Também falei que cada processador hardware vem com um conjunto particular de instruções que só roda nesse processador. No caso da JVM é a mesma coisa, é isso que chamamos de bytecode. Então quando o JAVAC compila, ele gera um binário nativo só que específico pra máquina virtual Java. Na época se pesquisou inclusive fazer um processador hardware mesmo que entendia essas instruções mas nunca foi mesmo pra frente. Entenderam? A JVM é como se simulasse um computador.</p>
<p>Assim como um interpretador, você carrega a JVM e aí ele carrega o seu programa, abstraindo o sistema real por baixo. Mas diferente de um interpretador ele faz coisas mais pesadas e tem premissas mais caras. Java é péssimo pra ser algo como substituto de Perl na linha de comando porque a inicialização dele é extremamente lenta até hoje. Isso porque ele tem um gerenciador de memória muito mais sofisticado do que a maioria dos interpretadores e carrega muito mais coisas na inicialização. Por causa disso também ele não é bom pra fazer FORK pra rodar em múltiplos processos, porque o custo desse gerenciador de memória é extremamente alto. Como simulador de um sistema operacional, nesse sentido ele se parece mais com o Windows do que um Linux. Então o caso de uso ideal do Java é rodar um único processo, sozinho na máquina, e seus programas rodam em paralelo dentro da JVM e a JVM substitui o sistema operacional pra gerenciar seus programas rodando em paralelo. E cada programa seu precisa usar Threads pra executar coisas em paralelo dentro dele. Viu porque eu disse que a JVM praticamente substitui o sistema operacional? Você tira o máximo dele quando não roda mais nada além da JVM e dá todos os recursos da máquina pra ele gerenciar.</p>
<p>E eu disse que compiladores como C fazem dezenas de otimizações pra reescrever seu código da maneira que rode mais rápido possível. A JVM vai um passo além, ele fica medindo como seu programa roda e otimiza em tempo real pra binário nativo da máquina por baixo pra ficar mais rápido. É a técnica que chamamos de JIT ou compilador Just In Time. Javascript faz algo semelhante em navegadores modernos como Safari ou Chrome. Um otimizador em tempo de compilação demora Muito pra compilar, se você já tentou compilar um programa manualmente, dependendo do tamanho já viu que pode levar de segundos a minutos. Uma página web não pode demorar tanto pra carregar. Em vez de tentar otimizar tudo, ele só lê o código fonte em Javascript e faz o mínimo pra interpretar e executar, mas à medida que ele executa um compilador JIT vai otimizando seu código em tempo de execução e só o código que está executando. Se você por acaso carregou uma biblioteca que nunca é usada, o compilador JIT nem vai perder tempo tentando otimizar esse pedaço. Então é tipo um meio termo entre um compilador AOT ou Ahead of Time como em C e um interpretador.</p>
<p>A vantagem do Java compilar num binário intermediário de bytecode e rodar numa JVM é que eliminamos o problema de ter que ter compiladores pra cada arquitetura de computador como Intel ou ARM. Basta que cada tipo de computador e sistema operacional tenha uma JVM. Você já deve ter visto isso com programas Java que rodam em Mac ou Linux ou Windows, mas em cada um você precisa baixar um Java específico uma vez só. Além disso o Java cresceu rapidamente e temos quase tudo escrito em Java. Assim como uma das maiores idéia do C era sua portabilidade para diferente arquiteturas, contanto que você recompilasse o código C, no caso do Java eles também queriam portabilidade escrevendo o máximo possível de tudo somente em Java.</p>
<p>Aliás, essa é outra diferença do Java com interpretadores. Em Java, você tem praticamente tudo que um sistema operacional como Windows ou Linux oferecem mas escritos todos em Java e embutidos dentro da JVM. Em interpretadores como Perl ou Python ou PHP ou Ruby eles dependem bastante do sistema operacional por baixo. Interpretadores de código aberto como eles nunca tiveram a ambição de substituir o sistema operacional, mas de se integrar a eles. Pra fazer isso usamos o conceito de binding, que é uma forma do interpretador expor a uma biblioteca de código nativo o acesso a suas estruturas internas e sua memória. Então, em vez de escrever uma biblioteca de criptografia toda em Python, ele simplesmente mapeia para o que o OpenSSL já instalado no sistema operacional oferece. Perl, PHP e Ruby fazem a mesma coisa. Isso inclusive cria um problema: se a biblioteca nativa que você está fazendo binding não for thread-safe, você bloqueia o interpretador de rodar threads em paralelo. Isso acontece o tempo todo em todos os interpretadores.</p>
<p>Por isso também é mais difícil de fazer interpretadores rodarem em todos os sistemas operacionais. Muitas vezes eles rodam, mas algumas funcionalidades são diferentes ou tem qualidade inferior porque as mesmas dependências não existem em todos os sistemas operacionais. Por isso mesmo, nunca faça programas complexos que rodam num interpretador no Windows esperando que vá funcionar igualzinho em Linux ou Mac. Entre Mac e Linux existe mais compatibilidade porque ambos compartilham muitas das mesmas bibliotecas open source. No Windows elas precisam ser remendadas pra se ligar a bibliotecas proprietárias que só existem no Windows. Portanto, se vai codar em linguagens interpretadas como essas, prefira sempre Linux ou Mac. Não é uma questão de ser anti-Microsoft ou algo assim, os interpretadores que foram criados em Linux funcionam bem só em Linux, mesmo que exista uma versão pra Windows.</p>
<p>Macs passaram a ser construídos sobre um UNIX baseado na Kernel do BSD UNIX quando o OS X saiu em 1999, sistema esse que foi uma evolução do NextStep criado no fim dos anos 80 depois que Steve Jobs foi removido da Apple. Ele é um UNIX de verdade, assim como o Solaris da Sun ou o HP-UX. Eles se parecem com distribuições Linux porque o Linux tentou ser um clone de UNIX. E eles são razoavelmente compatíveis porque os UNIX modernos e distribuições Linux usam mais ou menos as mesmas ferramentas gerais, incluindo o mesmo compilador GCC e suas dependências como o GLIBC. Só o Windows que é completamente diferente dentre os principais sistemas operacionais. E o Java nasceu com a promessa de permitir que você pudesse escrever programas que rodavam em qualquer lugar, porque havia JVM para Linux, para Solaris, pra Mac, pra Windows. Por isso ele se popularizou tanto tão rápido. E também coincidiu com o começo da bolha da internet que acelerou ainda mais a adoção de Java.</p>
<p>Vendo isso, a Microsoft não quis perder o bonde. Primeiro, aproveitando a experiência com Visual Basic e Visual C++ que compilavam binários nativos compatíveis com Windows, eles contrataram o Anders Hejlsberg, criador do Turbo Pascal e do Object Pascal do Delphi da Borland. Seu primeiro produto foi o Visual J++ que era uma versão de Java da Microsoft que compilava pra uma JVM proprietária e com várias funcionalidades incompatíveis. Por exemplo, notável era que o Java também tinha um jeito de fazer bindings pra binários nativos chamado JNI mas a no J++ a Microsoft fez uma forma diferente e discutivelmente melhor ou mais performática chamada RMI e o J++ fazia vários atalhos pra se ligar ao Windows de forma mais eficiente. A linguagem tinha a mesma cara do Java mas não era compatível com o Java da Sun. Por conta disso os programas compilados em J++ rodavam de forma mais eficiente só que o bytecode era incompatível com a JVM da Sun e como pra se chamar Java precisava ser compatível a Sun processou a Microsoft. O J++ acabou sendo descontinuado por conta disso mas as suas sementes é que deram origem ao que você conhece como o CLR o Common Language Runtime, ao framework .NET e à linguagem C#. Por isso as primeiras versões de C#, pra mim, tinham cara de Java misturado com Delphi.</p>
<p>Veja, como a JVM executa bytecodes binários, qualquer linguagem pode ser transformada nesses bytecodes. A sintaxe que conhecemos como Java é só um deles. Por isso hoje em dia você tem linguagens como Scala, Clojure e Kotlin que compilam para o mesmo bytecode e rodam na JVM. Mas a Microsoft usou essa característica antes e além de C# já fez também Visual Basic.NET e ambas compilavam pro bytecode do CLR. O CLR ao contrário da JVM fazia muitos bindinds direto pro sistema operacional pra conseguir ter a melhor performance. Portabilidade não era importante porque bastava rodar onde o Windows rodava, e nessa época a Microsoft ainda considera o mundo open source como inimigo, coisa que só veio a mudar nos anos recentes. Essa é a grande diferença entre Java e .NET. Embora ambas sejam máquinas virtuais e os códigos compilem em bytecodes, o Java tinha como meta rodar no maior número de dispositivos quanto possível e o .NET só em Windows.</p>
<p>Mas as coisas foram mudando. Essa ambição da Sun se mostrou muito a frente do seu tempo. A Sun teve muitas ingerências, perdeu valor e foi comprada pela Oracle e desde antes disso com a depressão depois do crash da bolha da internet em 2001 que ela evolui a passos de tartaruga. Em paralelo no mundo open source, surgiu uma iniciativa ousada. A Microsoft abriu a linguagem C# como um padrão aberto Ecma, então Miguel de Icaza, do projeto GNOME decidiu tentar implementar o CLR no Linux, esse projeto recebeu o nome de Mono. Levou quase 10 anos pra conseguir de fato criar uma CLR e todo o framewortk quase totalmente compatível com o da Microsoft. Porque não era só uma questão de fazer a máquina virtual e o compilador, era necessário também reescrever do zero todas as bibliotecas que compõe o pacote .NET framework.
E muitas delas dependem de bindings para bibliotecas nativas do Windows como já falei. Isso foi um dos maiores problemas pra conseguir compatibilidade no Linux. Por exemplo, as bibliotecas para fazer aplicativos desktop, tipo um Word da vida, usam bibliotecas nativas do Windows pra gerar janelas nativas e não janelas diferentes como o Java fazia com seu toolkit Swing. Do jeito do Java o peso era maior e a performance era menor. Mas usando bibliotecas nativas a velocidade do C# e do .NET pra desenhar as mesmas janelas era muito maior e mais comparável com o que o Visual Basic ou Visual C++ anterior faziam. O Mono no Linux fazia bindings com o GNOME pra também conseguir ter performance similar.</p>
<p>Depois de muitos anos e com a troca de CEO com a saída de Steve Balmer e a entrada de Satya Nadella e sua política de boa vizinhança, a Microsoft se tornou finalmente mais amigável ao mundo open source e acabou comprando a empresa de Miguel De Icaza, a Xamarin. E o Mono foi renomeado como .NET Core e hoje está em caminho de substituir o antigo .NET framework. E apesar do CLR suportar múltiplas linguagens na prática todo mundo programa ou em C# ou em Visual Basic.NET mesmo.</p>
<p>E pro episódio de hoje acho que já cobrimos bastante terreno. Eu tentei explicar um pouco sobre como as coisas funcionam por baixo dos panos. A idéia de CPUs, sistemas operacionais, gerenciamento de processos e de memória. As diferenças entre linguagens compiladas de forma estática e dinâmica. O que são interpretadores e o conceito de máquinas virtuais e bytecodes. E também as diferenças de rodar coisas em paralelo com forks e com threads. Os anos 90 foram o terreno para o crescimento de Java e .NET no mundo mais proprietário e em paralelo pra evolução do mundo open source como um todo, do Linux, e linguagens interpretadas como Perl e PHP. Estamos ainda só começando o século XXI. No próximo episódio vamos precisar do que expliquei aqui pra você finalmente começarem a entender as diferenças entre todas as linguagens modernas.</p>
<p>Como eu já disse no episódio anterior, se eu fiz meu trabalho direito espero que vocês tenham ficado com mais dúvidas do que quando começaram a assistir. Mandem suas dúvidas nos comentários, se curtiram o vídeo mandem um joinha, compartilhem com seus amigos, não deixem de assinar o canal e clicar no sininho. Aguardem a parte 2 sobre tecnologias back end na semana que vem, a gente se vê, até mais!</p>
]]></content:encoded><category>backend</category><category>linguagens-de-programacao</category><category>performance</category><category>akitando</category></item><item><title>Windows Subsystem for Linux é Bom, Mas Ainda Não Suficiente</title><link>https://akitaonrails.github.io/2017/09/20/windows-subsystem-for-linux-e-bom-mas-ainda-nao-suficiente/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/09/20/windows-subsystem-for-linux-e-bom-mas-ainda-nao-suficiente/</guid><pubDate>Wed, 20 Sep 2017 14:43:00 -0300</pubDate><description>&lt;p&gt;Em julho de 2016 escrevi um post precipitado intitulado &lt;a href="http://www.akitaonrails.com/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener"&gt;&amp;ldquo;The Year of Linux on the Desktop - It&amp;rsquo;s Usable!&amp;rdquo;&lt;/a&gt; e, de fato, ele era (mal e mal) utilizável.&lt;/p&gt;
&lt;p&gt;O verdadeiro &amp;ldquo;Ano do Linux no Desktop&amp;rdquo; ainda não chegou. Mas está chegando, como vou explicar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; este não vai ser &amp;lsquo;mais-um-post-de-instalação-do-WSL&amp;rsquo;, já existem vários por aí, por exemplo &lt;a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener"&gt;este&lt;/a&gt; ou &lt;a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener"&gt;este outro&lt;/a&gt;. E você sempre pode &lt;a href="http://lmgtfy.com/?q=windows&amp;#43;10&amp;#43;creator&amp;#43;update&amp;#43;install&amp;#43;WSL"target="_blank" rel="noopener"&gt;pesquisar no Google&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No início de 2016 a Microsoft anunciou e lançou uma versão bem alpha do que viria a ser o Windows Subsystem for Linux (WSL). Ele inicializava e rodava alguns programas, mas era cheio de bugs. Justo, afinal era ainda apenas um preview para Insiders (aliás, evite builds do Insider Preview, eles travam com tela azul o tempo todo, é desenvolvimento ativo).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Em julho de 2016 escrevi um post precipitado intitulado <a href="http://www.akitaonrails.com/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener">&ldquo;The Year of Linux on the Desktop - It&rsquo;s Usable!&rdquo;</a> e, de fato, ele era (mal e mal) utilizável.</p>
<p>O verdadeiro &ldquo;Ano do Linux no Desktop&rdquo; ainda não chegou. Mas está chegando, como vou explicar.</p>
<p><strong>TL;DR</strong> este não vai ser &lsquo;mais-um-post-de-instalação-do-WSL&rsquo;, já existem vários por aí, por exemplo <a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener">este</a> ou <a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener">este outro</a>. E você sempre pode <a href="http://lmgtfy.com/?q=windows&#43;10&#43;creator&#43;update&#43;install&#43;WSL"target="_blank" rel="noopener">pesquisar no Google</a>.</p>
<p>No início de 2016 a Microsoft anunciou e lançou uma versão bem alpha do que viria a ser o Windows Subsystem for Linux (WSL). Ele inicializava e rodava alguns programas, mas era cheio de bugs. Justo, afinal era ainda apenas um preview para Insiders (aliás, evite builds do Insider Preview, eles travam com tela azul o tempo todo, é desenvolvimento ativo).</p>
<p>Um ano se passou, chegamos ao Windows 10 Anniversary Update e, finalmente, ao tão esperado <strong>Creators Update</strong>. A evolução foi significativa.</p>
<p>Se você não sabe o que é o WSL, recomendo muito que leia os <a href="https://blogs.msdn.microsoft.com/wsl/"target="_blank" rel="noopener">artigos no blog de desenvolvedores da Microsoft</a> e o excelente post da Jessie Frazelle, <a href="https://blog.jessfraz.com/post/windows-for-linux-nerds/"target="_blank" rel="noopener">Windows for Linux Nerds</a>. Em resumo: o WSL intercepta as system calls do kernel do Windows e as mapeia como system calls do kernel Linux, enganando binários ELF64 não modificados para que rodem como se estivessem num sistema Linux de verdade. O detalhe crucial é que não há nenhum kernel Linux rodando! Ele também encapsula serviços do Windows como sistema de arquivos e rede em dispositivos que o Linux consegue acessar.</p>
<p>Portanto, não é virtualização. Não existe Hyper-V envolvido. É o Windows executando binários ELF64 diretamente, sem recompilação, sem modificação.</p>
<p>Diferentemente do OS X, onde é preciso ajustar o código-fonte e recompilar para rodar no Darwin (a camada Mach/BSD do sistema), aqui dá para baixar binários Ubuntu (ou de qualquer outra distro — Fedora e SUSE estão chegando em breve à Windows App Store) e executá-los <strong>diretamente</strong>.</p>
<p>Melhor ainda: dá para rodar daemons de sistema (sim, PostgreSQL, Memcached, Redis, etc. todos carregam e rodam em background) e até programas com interface gráfica via X!</p>
<p>Não me entendam mal, tenho o maior respeito por quem trabalha duro na Microsoft para fazer isso acontecer. É um feito técnico notável por si só.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/657/2017-09-14.png" alt="Windows e X lado a lado"  loading="lazy" /></p>
<p>Olhem bem para esse screenshot. É algo digno de admiração: GNOME-VIM nativo rodando lado a lado com o Microsoft Edge carregando um site servido por uma aplicação Rails dentro do WSL, acessando o PostgreSQL.</p>
<p>Mas será que funciona bem para o dia a dia?</p>
<h3>O WSL é bom de verdade?<span class="hx:absolute hx:-mt-20" id="o-wsl-é-bom-de-verdade"></span>
    <a href="#o-wsl-%c3%a9-bom-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>TL;DR</strong> não chega nem perto de ser tão fluido quanto rodar uma distro Linux pura, e a integração está longe de ser tão boa quanto o OS X em termos de usabilidade — o Windows simplesmente não foi redesenhado de forma significativa para ser um sistema UNIX como o OS X.</p>
<p>Num mundo ideal, o Windows substituiria toda a base NT e construiria sua própria infraestrutura inspirada em BSD (como o Darwin) ou criaria uma distro Linux própria usando o kernel Linux pronto (provavelmente inviável por causa da GPL).</p>
<p>Mas isso é irreal. A Apple conseguiu fazer isso numa época em que a base instalada de Macs era pequena, então alienar alguns usuários não custava tanto. O Windows é grande demais para falhar. É e continuará sendo um pesadelo de manutenção de proporções épicas.</p>
<p>Dito isso, o WSL em si é um milagre, então não tenho como reclamar. A Microsoft teve uma década realmente difícil na virada do século. O Windows 10 é uma volta por cima, e depois de duas grandes iterações está finalmente se tornando razoável de novo. Talvez estejamos a mais algumas atualizações de um WSL realmente utilizável. Só o tempo dirá.</p>
<p>A Apple teve seu milagre na virada do século, e o OS X e o iOS são sistemas operacionais excelentes. Os melhores da categoria. E conseguir reiniciar um ecossistema inteiro do zero não é algo que qualquer empresa consegue fazer.</p>
<p>As distros Linux tiveram seus altos e baixos. Nenhum ecossistema evolui de forma saudável e coesa sendo tão fragmentado. Mas cada distro principal conseguiu forçar seu próprio ecossistema na marra. A Canonical sobreviveu onde muitas falharam, mas ainda não é a vencedora definitiva, e dá a impressão de que está <a href="https://arstechnica.com/information-technology/2017/04/ubuntu-unity-is-dead-desktop-will-switch-back-to-gnome-next-year/"target="_blank" rel="noopener">perdendo o fôlego</a>. A RedHat aproveitou a febre do dot-com e conseguiu voar alto no mercado corporativo. Não existe distro &ldquo;melhor&rdquo;. É, de novo, uma questão de gosto e ego, na maior parte do tempo para a maior parte das pessoas.</p>
<p>E a comunidade OSS ainda desperdiça uma quantidade absurda de tempo em disputas sem sentido, como a <a href="https://www.reddit.com/r/linuxmasterrace/comments/616wxo/what_is_with_all_the_controversy_with_systemd/"target="_blank" rel="noopener">controvérsia do Systemd</a> ou o tempo que está levando para abandonar o X e finalmente migrar para o Wayland. OSS tem prós e contras, como tudo o mais.</p>
<p>Mas voltando ao WSL, antes que eu me perca. Sim, como prometido, quase tudo que se faz num Ubuntu 16.04 nativo pode ser feito nesse novo ambiente. Até o ridículo <code>cmd.exe</code> foi bastante atualizado para o século XXI. Ainda está longe de um Terminal padrão do Linux ou OS X, mas está chegando lá. Na prática, eu recomendaria instalar o <a href="https://github.com/goreliu/wsl-terminal"target="_blank" rel="noopener">WSL Terminal</a> como substituto, e apesar do nome terrível &ldquo;Bash on Ubuntu on Windows&rdquo;, eu recomendaria instalar o ZSH em vez do Bash para ter mais recursos de usabilidade.</p>
<p>Dá para pesquisar tutoriais sem fim sobre como personalizar o ambiente, Vim, ajustar o PostgreSQL, etc. Então não vou repetir isso aqui. Um aviso importante: tenho que rodar <code>sudo /etc/init.d/PostgreSQL start</code> para iniciar os daemons. Eles não sobem automaticamente depois de reiniciar o sistema — guarde isso.</p>
<p>Também dá para instalar um servidor X no Windows, como o <a href="https://sourceforge.net/projects/xming/"target="_blank" rel="noopener">xming</a>, fazer <code>export DISPLAY=:0</code> e então <code>sudo apt install gvim</code> para carregar a versão gráfica do Gvim, por exemplo. Ou um desktop GNOME ou XFCE completo, se for o seu negócio. Dá para rodar aplicações X lado a lado com apps Windows, e isso é muito legal. Gosto de instalar o <a href="https://github.com/skwp/dotfiles"target="_blank" rel="noopener">YADR</a>, aquela coleção de dotfiles supercustomizada, e ele funciona bem.</p>
<p>Estou usando o <a href="https://github.com/asdf-vm/asdf"target="_blank" rel="noopener">ASDF</a> para instalar e gerenciar múltiplas versões de Ruby, Node.js, Elixir, etc. E também funciona.</p>
<p>Consegui mover minhas chaves SSH da instalação anterior do <a href="http://www.akitaonrails.com/2017/01/10/arch-linux-a-melhor-distro-de-todas"target="_blank" rel="noopener">Arch Linux</a>. E aí consigo clonar os repositórios Git normalmente. E aqui vem o <strong>grande problema</strong>. Clonei e configurei um projeto Rails. Este é o melhor resultado que consegui rodando toda a suíte RSpec com testes de aceitação usando PhantomJS:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 5 minutes 35 seconds (files took 48.53 seconds to load)
888 examples, 0 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso rodando no meu <a href="http://www.akitaonrails.com/2017/01/31/do-macbook-pro-para-o-dell-xps-arch-linux-para-usuarios-criativos"target="_blank" rel="noopener">Dell XPS 9550</a> com Core i7 Skylake (4 cores, 8 threads), 16 GB de RAM e SSD NVME2. Pedi a um colega para rodar no Mac Mini dele, de 2012, com Core i5 (provavelmente Haswell), 10 GB de RAM e SSD normal. Este foi o resultado dele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 37.6 seconds (files took 9.73 seconds to load)
888 examples, 0 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sim, chocante. A emulação do sistema de arquivos é <a href="https://meta.discourse.org/t/installation-notes-for-discourse-on-bash-for-windows/48141/3?u=sam"target="_blank" rel="noopener">notoriamente lenta</a> no WSL (pelo menos 4 vezes mais lenta, como se vê nos números acima).</p>
<p>Este outro teste rodou num hardware um pouco mais novo (ainda bem inferior ao meu Dell XPS) com Linux Mint:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 2.4 seconds (files took 22.69 seconds to load)
888 examples, 1 failure</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Rodei a mesma suíte algumas vezes para confirmar, mas não consegui passar da marca de 5 minutos, e o carregamento de arquivos fica sempre em torno de 1 minuto, enquanto em outros sistemas operacionais é de 2 a 4 vezes mais rápido.</p>
<p>Se o seu fluxo de desenvolvimento é intensivo em I/O, você vai sofrer. A compilação do Webpack, que já é lenta, fica ainda mais lenta. Npm/Yarn demora uma eternidade. Instalar coisas a partir do código-fonte é muito devagar.</p>
<p>Não chega a ser inutilizável, mas essa lentidão vai cansar rápido.</p>
<h3>E a Virtualização?<span class="hx:absolute hx:-mt-20" id="e-a-virtualização"></span>
    <a href="#e-a-virtualiza%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois de ver a performance de sistema de arquivos deplorável descrita acima, pensei: <em>&ldquo;e quanto a rodar tudo no bom e velho Virtualbox?&rdquo;</em></p>
<p>Em teoria, o WSL &ldquo;deveria&rdquo; ser uma camada mais fina que o Virtualbox, mas o grande vilão em termos de overhead parece ser a camada de abstração do sistema de arquivos, e não necessariamente o acesso a CPU ou RAM. Meu instinto inicial diria que um Virtual Drive — um único arquivo montado como HD virtual — teria um desempenho bem melhor.</p>
<p>Então lá fui eu instalar o Virtualbox com o mesmo conjunto de ferramentas (asdf, etc.).</p>
<p>Montar uma pasta compartilhada diretamente para o equivalente no WSL não funciona direito; parece que o protocolo de montagem não entende dot files, então eles simplesmente não aparecem. É preciso compactar a pasta com tar e descompactar dentro da máquina para preservar arquivos ocultos e permissões.</p>
<p>Rodando a mesma suíte RSpec no Virtualbox com 4 Cores (dos 8 que minha máquina tem) e 4GB de RAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.4 seconds (files took 6.33 seconds to load)
888 examples, 2 failure</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então fomos de 5:35 min para 2:03 min e o carregamento de arquivos de 49 segundos para 6 segundos! Cerca de 2,5 vezes mais rápido na execução e nada menos que 8 vezes mais rápido no carregamento de arquivos! É absurdo, mas faz todo sentido. Minha máquina de 2016, dentro do Virtualbox, ainda supera hardware bare-metal de 2011/2012. E o WSL fica na última posição.</p>
<p>Esqueça benchmarks sintéticos — este é um caso real misturando execuções bound em CPU e I/O. Só para comparar, tentei ver se conseguia mais desempenho rodando no VMWare Workstation. O resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.3 seconds (files took 6.21 seconds to load)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Praticamente idêntico. Para mim, isso mostra que o Virtualbox percorreu um longo caminho para se igualar a soluções comerciais historicamente mais robustas como VMware e Parallels, então fico com o Virtualbox por enquanto.</p>
<p>Fiz outro teste: instalação do Ruby 2.4.2 a partir do código-fonte usando <code>asdf</code>. Primeiro no WSL (rodando com <code>time</code>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>229.38s user 244.59s system 113% CPU 6:57.51 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E a mesma instalação no Virtualbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>295,39s user 22,43s system 135% CPU 3:53,87 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mais uma vez, cenário misto de CPU e I/O. No Virtualbox sou pelo menos duas vezes mais rápido.</p>
<p>Se quiser ver cenários mais controlados com benchmarks sintéticos, recomendo ler <a href="http://www.phoronix.com/scan.php?page=article&amp;item=intel-7900x-wsl&amp;num=1"target="_blank" rel="noopener">o artigo do Phoronix sobre WSL vs Virtualbox</a>.</p>
<h3>O Dilema do &ldquo;Ano do Linux no Desktop&rdquo;<span class="hx:absolute hx:-mt-20" id="o-dilema-do-ano-do-linux-no-desktop"></span>
    <a href="#o-dilema-do-ano-do-linux-no-desktop" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A falta de software comercial é um problema, e uma das causas é a ausência de compatibilidade binária retroativa.</p>
<p>Apesar de todos os seus problemas, a Microsoft manteve compromisso com um conceito muito difícil: dá para executar binários não modificados do Windows 95 no Windows 10 atual. Na maioria das vezes eles &ldquo;simplesmente funcionam&rdquo;. Nas distros Linux, todo binário é compilado contra cabeçalhos de biblioteca muito específicos — o próprio kernel, Glibc e muitos outros. Sempre que um desses muda, todos os binários precisam ser recompilados. É por isso que existe o conceito de &ldquo;distros&rdquo;, onde se recompila e testa um conjunto completo de softwares. O Long-Term Support (LTS) é basicamente travar as versões dessas dependências por alguns anos para que o software rode sem precisar recompilar tanto. Mas em distros de ponta, é inevitável recompilar muita coisa várias vezes.</p>
<p>O Windows carrega os binários de dependências antigos consigo. O OS X faz o mesmo em certa medida. E o OS X vai além, com o conceito de &ldquo;fat binaries&rdquo; (ou &ldquo;Universal Binaries&rdquo;), onde um pacote contém múltiplos binários voltados para diferentes arquiteturas de hardware (32-bit vs 64 bit, Intel vs ARM vs PowerPC).</p>
<p>Principalmente por causa desse suporte, aplicações comerciais complexas como o Office ou o Creative Suite da Adobe conseguem rodar em muitas máquinas diferentes, em versões diferentes do sistema operacional, por muitos anos sem precisar atualizar. Nas distros Linux, binários são uma dor de cabeça. É por isso que ter o código-fonte disponível é tão importante — e esse é um debate filosófico sobre o conceito do &ldquo;Software Livre&rdquo; como concebido por Richard Stallman.</p>
<p>É um dilema sem solução. Distros Linux não têm compatibilidade binária retroativa nativa. Desenvolvedores precisam contar com ferramentas como o <a href="https://lvc.github.io/abi-compliance-checker/"target="_blank" rel="noopener">ABI Compliance Checker</a> e tentar usar <a href="http://www.airs.com/blog/archives/220"target="_blank" rel="noopener">symbol versioning</a> do glibc, entre outros recursos. Mas não é realista esperar que voluntários contribuindo com seu tempo livre para o código aberto mantenham versões antigas de software por muitos anos. É um compromisso justo. Quer o melhor, o mais rápido, o mais seguro? Atualize frequentemente, mas aceite que software antigo e não mantido vai quebrar.</p>
<p>Portanto, sem liberar o código-fonte, não dá para ter binários compatíveis e bons em cada grande distribuição, em versões diferentes, ao longo de muitos anos.</p>
<h3>Qual é o certo para você?<span class="hx:absolute hx:-mt-20" id="qual-é-o-certo-para-você"></span>
    <a href="#qual-%c3%a9-o-certo-para-voc%c3%aa" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Não adianta tentar fazer benchmark de sistemas operacionais. Todos são geralmente bons o suficiente.</p>
<p>Macs são mais caros (às vezes por uma diferença absurda de mais de um milhão de reais). Não tem como negar. O compromisso é que o hardware da Apple, o OS X e o ecossistema são geralmente os melhores no geral, e o &ldquo;acabamento&rdquo; dos produtos costuma ser de fato superior do ponto de vista de experiência de uso.</p>
<p>Para a melhor combinação de software mainstream com software open source, não existe alternativa melhor. Se puder comprar um Mac, compre um Mac.</p>
<p>Quem é gamer precisa ficar com o Windows. Sem discussão. Os melhores jogos estão no Windows, ponto. A melhor combinação de hardware suporta Windows. Nenhum ajuste no Linux vai te dar o desempenho bruto necessário para extrair 4K a 60fps das novas GPUs AMD e NVIDIA via DirectX. O hardware Mac tem limitações e não aceita as GPUs mais novas. Se quer jogar os últimos jogos AAA do jeito que foram projetados para rodar, monte um hardware personalizado com Windows 10.</p>
<p>Desenvolvedores web têm mais sorte. Não tem como errar — é literalmente questão de gosto qual sistema operacional escolher. Dá para entregar os mesmos resultados usando as mesmas ferramentas. E a não ser que você precise de infraestrutura específica Microsoft (SQL Server, Active Directory, etc.), instala qualquer distro Linux e fica feliz.</p>
<p>Desenvolvedores de apps desktop precisam pensar no público-alvo. Se quer construir apps desktop Windows, tem que estar no Windows. Mas dá para usar .NET/Xamarin ou Java para construir binários cross-platform. O problema é que, idealmente, precisam ter os três sistemas para garantir que o software se comporta como esperado.</p>
<p>Desenvolvedores mobile também têm azar. Android dá para fazer no Windows ou Linux, mas para desenvolver para a App Store da Apple é preciso ter Mac. Não tem como escapar disso, e boa sorte esperando a Apple liberar suas ferramentas para outras plataformas.</p>
<p>Profissionais criativos podem escolher entre Windows e OS X. Depende das ferramentas. Se você usa Adobe, qualquer um serve bem, mas provavelmente vai querer uma workstation personalizada. Até a Apple remodelar o ridículo Mac Pro, não encontrará o melhor hardware pelo preço na Apple. O iMac 5K atual é bom, mas acima disso não tem opção. Por exemplo, se precisar de 2 GPUs NVIDIA Titan ou Quadro rodando em paralelo, a Apple não vai ajudar.</p>
<p>Se o fluxo de trabalho exige Final Cut Pro, Logic Pro, Motion 5, a melhor opção disponível é o iMac 5K.</p>
<p>Eu não sou mais desenvolvedor em tempo integral. Divido meu tempo gerindo uma empresa, fazendo apresentações, experimentando tecnologia e pesquisando. Meu setup ideal seria um MacBook Pro. Mas estou me forçando a viver fora do ecossistema Apple, e dói. Meu lado desenvolvedor web fica feliz no Linux (Manjaro GNOME é meu favorito atualmente). Mas quando o lado criativo precisa de ferramentas, Gimp, Inkscape, Kdenlive simplesmente não chegam lá. Estou muito acostumado com Photoshop, Illustrator, Final Cut/Premiere, Motion 5/After Effects. O LibreOffice Impress é terrível para apresentações complexas, e os slides baseados em HTML/JS servem para coisas simples. Tente fazer gráficos e animações sérias lá e você vai sofrer demais.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/a59U6kRJHLg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Se você é um criativo, <strong>precisa</strong> de telas com calibração de cores (espaço Adobe RGB), precisa de poder de processamento bruto para renders rápidos e pós-processamento avançado. Editores de vídeo Adobe vão preferir Windows. Editores Final Cut são bem atendidos nos Macs, mesmo que o sistema de refrigeração dos novos MacBook Pros seja terrível:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/6TWbXV5xeYE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>E se você é editor de vídeo em tempo integral, pode pagar tanto por um PC quanto por um Mac de ponta, então a disputa fica pequena.</p>
<p>Se você é editor de vídeo com orçamento limitado, <strong>não vai</strong> ter um bom fluxo de edição 4K no Linux, ponto. O suporte para explorar o potencial total das GPUs modernas é fraco, não há bom suporte a Cuda ou OpenCL, e a maioria das ferramentas de edição não os utiliza. O Blender — que é primariamente uma ferramenta de modelagem 3D e animação — é considerado uma das melhores ferramentas de edição de vídeo disponíveis no Linux, o que já diz muito. O estado lamentável do suporte a GPU no Linux compromete o uso criativo. A única outra ferramenta de edição não-linear que consegue atingir fluxos de trabalho profissionais é o <a href="https://www.linux.com/learn/pro-level-video-editing-lightworks-linux"target="_blank" rel="noopener">LightWorks</a>, que não é open source, requer os binários proprietários da NVIDIA e <a href="https://www.lwks.com/index.php?option=com_shop&amp;view=shop&amp;Itemid=205"target="_blank" rel="noopener">custa dinheiro</a>.</p>
<p>Se for por esse caminho, provavelmente é melhor ficar com Windows ou OS X por enquanto.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Me perdi um pouco no meio do caminho, mas a conclusão é simples:</p>
<ul>
<li>O WSL ainda não chegou lá. Precisamos esperar mais algumas atualizações. Quando você ouvir que melhoraram a abstração do sistema de arquivos em algumas ordens de magnitude, aí sim vale tentar de novo. E lembre-se que existem toneladas de <a href="https://github.com/Microsoft/BashOnWindows/issues"target="_blank" rel="noopener">issues abertas</a>, então os resultados variam muito.</li>
<li>Se você é desenvolvedor web, está seguro em qualquer distro Linux.</li>
<li>Se você é um profissional misto, precisando tanto de ferramentas de desenvolvimento quanto de ferramentas criativas, use Mac ou Windows 10 com uma distro Linux totalmente virtualizada.</li>
<li>Se é gamer, Windows.</li>
<li>Se é profissional criativo em tempo integral, tanto Macs quanto PCs servem bem.</li>
</ul>
<p>Por enquanto vou ficar com meu Dell XPS rodando Windows 10 e Manjaro GNOME no Virtualbox. O Virtualbox é rápido o suficiente para não prejudicar meu fluxo de trabalho. E as ferramentas criativas conseguem aproveitar os núcleos CUDA da minha NVIDIA sem problema, então consigo renderizar no Blender e fazer o Premiere processar vídeo 4K em tempo razoável.</p>
]]></content:encoded><category>linux</category><category>microsoft</category><category>performance</category></item><item><title>Ex Manga Downloadr - Parte 7: Lidando Corretamente com Grandes Coleções</title><link>https://akitaonrails.github.io/2017/06/16/ex-manga-downloadr-parte-7-lidando-com-grandes-colecoes/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/06/16/ex-manga-downloadr-parte-7-lidando-com-grandes-colecoes/</guid><pubDate>Fri, 16 Jun 2017 15:10:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; consegui simplificar bastante o código original usando &lt;a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener"&gt;Flow&lt;/a&gt;. Só que o lado negativo foi que o tempo de execução aumentou consideravelmente.&lt;/p&gt;
&lt;p&gt;José Valim gentilmente apareceu e deixou um &lt;a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener"&gt;comentário valioso&lt;/a&gt;, que vou reproduzir aqui:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Você tentou reduzir o &lt;code&gt;@max_demand&lt;/code&gt;? O &lt;code&gt;@max_demand&lt;/code&gt; é o quanto de dados você troca entre os estágios. Se você colocar 60, isso significa que está mandando 60 itens para um estágio, 60 para outro e assim por diante. Isso gera um balanceamento ruim para coleções pequenas, porque há chance de todos os itens acabarem no mesmo estágio. Na verdade você quer reduzir o &lt;code&gt;max_demand&lt;/code&gt; para 1 ou 2, para que cada estágio receba lotes pequenos e precise pedir mais. Outro parâmetro que costuma ser ajustado é a opção &lt;code&gt;stages: ...&lt;/code&gt; — você deveria definir como a quantidade de conexões que tinha no poolboy antes.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener">post anterior</a> consegui simplificar bastante o código original usando <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a>. Só que o lado negativo foi que o tempo de execução aumentou consideravelmente.</p>
<p>José Valim gentilmente apareceu e deixou um <a href="http://www.akitaonrails.com/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener">comentário valioso</a>, que vou reproduzir aqui:</p>
<blockquote>
  <p>Você tentou reduzir o <code>@max_demand</code>? O <code>@max_demand</code> é o quanto de dados você troca entre os estágios. Se você colocar 60, isso significa que está mandando 60 itens para um estágio, 60 para outro e assim por diante. Isso gera um balanceamento ruim para coleções pequenas, porque há chance de todos os itens acabarem no mesmo estágio. Na verdade você quer reduzir o <code>max_demand</code> para 1 ou 2, para que cada estágio receba lotes pequenos e precise pedir mais. Outro parâmetro que costuma ser ajustado é a opção <code>stages: ...</code> — você deveria definir como a quantidade de conexões que tinha no poolboy antes.</p>

</blockquote>
<blockquote>
  <p>No entanto, acredito que você não precisa usar Flow de jeito nenhum. O Elixir v1.4 tem algo chamado <a href="https://hexdocs.pm/elixir/Task.html#async_stream/5"target="_blank" rel="noopener"><code>Task.async_stream</code></a>, que é uma mistura de poolboy + task async, e definitivamente é um substituto melhor para o que você tinha. Adicionamos isso ao Elixir depois de implementar o Flow, quando percebemos que dá pra extrair muito valor do <code>Task.async_stream</code> sem precisar partir para uma solução completa como o Flow. Usando <code>Task.async_stream</code>, a opção <code>max_concurrency</code> controla o tamanho do pool.</p>

</blockquote>
<p>E, obviamente, ele está certo. Eu tinha entendido errado como o Flow funciona. Ele foi feito para quando você tem uma quantidade absurda de itens para processar em paralelo — especialmente unidades de processamento que podem ter alta variância e, por isso, muita back-pressure, não só pela quantidade de itens, mas porque os tempos de execução podem variar dramaticamente. Então é um daqueles casos de ter um canhão na mão quando só tem uma mosca para matar.</p>
<p>O que eu não sabia era da existência do <code>Task.async_stream</code> e seu companheiro <a href="https://hexdocs.pm/elixir/Task.Supervisor.html#async_stream/4"target="_blank" rel="noopener"><code>Task.Supervisor.async_stream</code></a> para quando preciso de mais controle.</p>
<p>Vamos dar um passo atrás.</p>
<h3>Lidando com coleções em Elixir<span class="hx:absolute hx:-mt-20" id="lidando-com-coleções-em-elixir"></span>
    <a href="#lidando-com-cole%c3%a7%c3%b5es-em-elixir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Erlang é uma fera. Ele fornece todos os blocos de construção de um sistema operacional de tempo real e altamente concorrente! Sério, o que ele entrega out-of-the-box é muito mais do que qualquer outra linguagem/plataforma/máquina virtual, em qualquer época. Em Java, .NET ou qualquer outra coisa, você não tem isso de graça. Precisa montar as peças na mão, gastar centenas de horas ajustando e ainda torcer muito para que tudo funcione de forma integrada.</p>
<p>Então, tem sistemas distribuídos para construir? Não tem outra opção de verdade. Faça Erlang, ou sofra.</p>
<p>Aí o Elixir eleva isso um nível acima, criando uma biblioteca padrão bastante razoável e simples de usar, que torna a parte de escrever código genuinamente agradável. É uma combinação matadora. Precisa fazer o próximo WhatsApp? O próximo Waze? Reconstruir o Cassandra do zero? Criar algo como o Apache Spark? Faça Elixir.</p>
<p>Em Erlang, você resolve tudo usando GenServer. É uma abstração elegante do OTP. Você <a href="http://www.akitaonrails.com/en/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook/"target="_blank" rel="noopener">precisa entender OTP</a> de forma íntima. Não tem atalho. Não existe Erlang sem OTP.</p>
<p>Dito isso, dá para começar simples e escalar sem tanto esforço.</p>
<p>Geralmente tudo começa com Coleções, ou mais corretamente, com algum tipo de <a href="https://hexdocs.pm/elixir/Enum.html#content"target="_blank" rel="noopener">Enumeração</a>.</p>
<p>Assim como minha função simples <code>Workflow.pages/1</code>, que itera por uma lista de links de capítulos, busca cada link, faz o parse do HTML retornado e extrai a coleção de links de páginas dentro daquele capítulo, reduzindo as sublistas em uma lista completa de links de páginas.</p>
<p>Se eu sei que a coleção é pequena (menos de 100 itens, por exemplo), simplesmente faria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Simples assim. É linear. Vai processar um link por vez sequencialmente. Quanto mais links de capítulos, mais tempo demora. Normalmente eu quero processar isso em paralelo. Mas não posso disparar um processo paralelo para cada link de capítulo, porque se receber 1.000 links e disparar todos de uma vez, vira um ataque de negação de serviço e certamente recebo centenas de timeouts.</p>
<p>Dá para esbarrar em 2 problemas principais quando precisa iterar por uma coleção grande.</p>
<ul>
<li>
<p>Se a coleção for enorme (imagine um arquivo de texto de um gigabyte que você precisa iterar linha por linha). Para isso usa-se <a href="https://hexdocs.pm/elixir/Stream.html#content"target="_blank" rel="noopener"><code>Stream</code></a> em vez de <code>Enum</code>. Todas as funções parecem quase idênticas, mas você não precisará carregar a coleção inteira na memória nem ficará duplicando-a.</p>
</li>
<li>
<p>Se a unidade de processamento demora muito. Agora que você resolveu não explodir o uso de memória, e se você tiver jobs lentos enquanto itera por cada item da coleção? É exatamente o nosso caso, onde a coleção é bem pequena, mas o tempo de processamento é longo porque está buscando de uma fonte externa na internet. Pode levar milissegundos, pode levar alguns segundos.</p>
</li>
</ul>
<p>Uma forma de controlar isso é usando &ldquo;batches&rdquo;, algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">chunk</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pages_download</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">concat</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages_download</span><span class="p">(</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="n">results</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">end</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="n">results</span>	
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é só para exemplo, não compilei esse trecho para verificar se funciona, mas dá para entender a ideia de dividir a lista grande em chunks e processar cada chunk via <code>Task.async</code> e <code>Task.await</code>.</p>
<p>De novo, para listas pequenas isso está ok (milhares de itens) desde que cada item não demore muito para processar.</p>
<p>O problema é que isso não é lá muito bom. Porque cada chunk precisa terminar antes do próximo começar. É por isso que a solução ideal é manter uma quantidade constante de jobs rodando a qualquer momento. Para isso, precisamos de um Pool, como expliquei na <a href="http://www.akitaonrails.com/en/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/"target="_blank" rel="noopener">Parte 2: Poolboy ao resgate!</a>.</p>
<p>Mas implementar do jeito correto para manter o pool sempre cheio exige um malabarismo chato entre transações do Poolboy e <code>Task.Supervisor.async</code>. Foi por isso que fiquei interessado no novo uso do <code>Flow</code>.</p>
<p>O código até fica limpo, mas como já expliquei antes, esse não é o caso de uso adequado para o Flow. É melhor quando você precisa iterar por dezenas de milhares de itens ou até infinitos (quando você tem um fluxo de entrada de requisições que precisam de processamento paralelo, por exemplo).</p>
<p>Então, finalmente, existe um meio-termo. A solução entre o simples <code>Task.async</code> e o <code>Flow</code> é o <code>Task.async_stream</code>, que funciona como uma implementação de pool, mantendo uma quantidade <code>max_concurrency</code> de jobs rodando em stream. O código final fica muito mais elegante assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Task</span><span class="o">.</span><span class="n">async_stream</span><span class="p">(</span><span class="no">MangaWrapper</span><span class="p">,</span> <span class="ss">:chapter_page</span><span class="p">,</span> <span class="o">[</span><span class="n">source</span><span class="o">]</span><span class="p">,</span> <span class="ss">max_concurrency</span><span class="p">:</span> <span class="vi">@max_demand</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">to_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">}},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E esse é o <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/517183261e998ab40f6e5bc793b4db9adcf899e3"target="_blank" rel="noopener">commit final</a> com as mudanças mencionadas.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A implementação com <code>Task.async_stream</code> é super simples e os tempos finalmente voltaram ao que eram antes.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>84,16s user 20,80s system 138% cpu 1:15,94 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Muito melhor do que os mais de 3 minutos que estava demorando com Flow. E não é porque o Flow é lento — é porque eu não estava usando corretamente, provavelmente jogando um chunk grande em um único GenStage e criando um gargalo. De novo: só use Flow se tiver itens suficientes para colocar centenas deles em vários GenStages em paralelo. Estamos falando de coleções com dezenas de milhares de itens, não da minha mísera lista de páginas.</p>
<p>Tem um pequeno ajuste, porém. Para buscar todos os links de capítulos e páginas estou usando <code>max_concurrency</code> de 100. O timeout padrão é 5000 (5 segundos). Isso funciona porque o HTML retornado não é tão grande e dá para paralelizar tanto assim numa conexão de alta largura de banda.</p>
<p>Mas no procedimento de download de imagens em <code>Workflow.process_downloads</code> tive que cortar o <code>max_concurrency</code> pela metade e aumentar o <code>timeout</code> para 30 segundos para garantir que não travasse.</p>
<p>Como é uma implementação simples, não há recuperação de falhas nem rotina de retry. Teria que substituir essa implementação por <code>Task.Supervisor.async_stream</code> para retomar algum controle. Minha implementação original era mais trabalhosa, mas eu tinha onde adicionar mecanismos de retry. Então, de novo, é sempre uma troca entre facilidade de uso e controle. Quanto mais controle, pior fica o código.</p>
<p>Esse é um exercício simples, então vou deixar assim por enquanto.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>performance</category></item><item><title>Ex Manga Downloadr - Parte 6: A Ascensão do FLOW</title><link>https://akitaonrails.github.io/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow/</guid><pubDate>Tue, 13 Jun 2017 15:59:00 -0300</pubDate><description>&lt;p&gt;Faz bem mais de um ano desde que escrevi sobre meu projeto pessoal &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener"&gt;Ex Manga Downloadr&lt;/a&gt;. Desde então fiz apenas pequenas atualizações para acompanhar as versões mais recentes do Elixir e das bibliotecas.&lt;/p&gt;
&lt;p&gt;Para recapitular rapidamente: o exercício é fazer web scraping no MangaReader.net, baixar um monte de imagens organizadas em páginas e capítulos e, no final, compilar PDFs organizados para poder ler num Kindle.&lt;/p&gt;
&lt;p&gt;Web scraping é basicamente um loop de HTTP GETs em cima de toneladas de URLs, raspando o HTML e coletando mais URLs para baixar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faz bem mais de um ano desde que escrevi sobre meu projeto pessoal <a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Ex Manga Downloadr</a>. Desde então fiz apenas pequenas atualizações para acompanhar as versões mais recentes do Elixir e das bibliotecas.</p>
<p>Para recapitular rapidamente: o exercício é fazer web scraping no MangaReader.net, baixar um monte de imagens organizadas em páginas e capítulos e, no final, compilar PDFs organizados para poder ler num Kindle.</p>
<p>Web scraping é basicamente um loop de HTTP GETs em cima de toneladas de URLs, raspando o HTML e coletando mais URLs para baixar.</p>
<p>Em linguagens mais simples, as pessoas costumam resolver isso de dois jeitos ingênuos:</p>
<ul>
<li>Um loop aninhado simples. Uma única thread, fetches sequenciais. Se você tem 5.000 links e cada um leva 10 segundos para buscar, são 10 * 5.000 = 50.000 segundos — um tempo ridiculamente longo.</li>
<li>Spawn de processos, fibers, threads ou I/O paralelo em um reactor, tudo de uma vez. Uma tentativa de paralelizar todos os fetches simultaneamente.</li>
</ul>
<p>Todo mundo concorda que a primeira opção é ingênua. Mas a segunda tem seus problemas.</p>
<p>O ponto complicado é o <strong>CONTROLE</strong>.</p>
<p>Qualquer pessoa em Go diria <em>&ldquo;ah, é fácil, é só fazer um loop e disparar um monte de goroutines&rdquo;</em>, ou qualquer pessoa em Node.js diria <em>&ldquo;ah, é fácil, é só fazer um loop, chamar o fetch — tudo roda assincronamente — e adicionar callbacks, um async/await básico.&rdquo;</em></p>
<p>Não estão errados, mas ainda é uma implementação ingênua demais. Disparar centenas ou milhares de requisições paralelas é trivial. O problema vem depois: o que acontece se uma falha e você precisar fazer retry? E se o MangaReader tiver um sistema de throttling que começa a derrubar ou travar conexões? E se sua banda de internet simplesmente não der conta e, a partir de um certo ponto, você começar a ter retornos decrescentes e timeouts?</p>
<p>A mensagem é: disparar coisas em paralelo é trivialmente fácil. Controlar coisas em paralelo é complicado de verdade.</p>
<p>Por isso, na minha primeira implementação em Elixir, introduzi uma solução mais elaborada: uma combinação de um GenServer customizado, a infraestrutura de Task do próprio Elixir para o padrão async/await, e o <a href="https://github.com/devinus/poolboy"target="_blank" rel="noopener">Poolboy</a> para controlar a taxa de paralelismo. É assim que você controla o gargalo de recursos lentos: usando pools e filas (por isso todo banco de dados decente tem um connection pool — lembra do <a href="https://sourceforge.net/projects/c3p0/"target="_blank" rel="noopener">C3P0</a>?)</p>
<p>Um trecho da minha implementação mais antiga:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">chapter_page</span><span class="p">(</span><span class="o">[</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="p">,</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="vi">@genserver_call_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">,</span> <span class="vi">@task_async_timeout</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Feio, né? E ainda tem todo o boilerplate do GenServer, do Supervisor customizado para inicializar o Poolboy e por aí vai. O código de workflow de alto nível ficava assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dentro do módulo <code>Worker</code>, cada método público encapsulava as chamadas internas ao GenServer em um <code>Task async</code>, e na iteração da coleção adicionávamos <code>Task.await</code> para aguardar todas as chamadas paralelas terminarem — só então reduziamos os resultados.</p>
<p>O Elixir agora vem com uma infraestrutura muito interessante chamada <a href="https://elixir-lang.org/blog/2016/07/14/announcing-genstage/"target="_blank" rel="noopener"><code>GenStage</code></a>, que promete substituir o <code>GenEvent</code>. O caso de uso ideal é quando você tem uma situação de produtor-consumidor com back-pressure — exatamente quando há endpoints lentos e você precisa controlar gargalos.</p>
<p>Aí vem o <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a>, uma abstração de alto nível que você usa de forma quase idêntica ao <code>Enum</code> nas suas coleções — mas em vez de mapeamento sequencial, ele cuida do traversal paralelo e do controle de lotes. O código fica muito parecido, só que sem você precisar gerenciar os controles de paralelização na mão.</p>
<p>Esse é o <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/b117f5236098f6d37e332633acb787be46a09d84"target="_blank" rel="noopener">commit completo</a> onde consegui remover o Poolboy, remover o meu GenServer customizado, reimplementar o Worker como um módulo simples de funções e fazer o workflow descartar o padrão async/await em favor do Flow:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">from_enumerable</span><span class="p">(</span><span class="ss">max_demand</span><span class="p">:</span> <span class="vi">@max_demand</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">MangaWrapper</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">partition</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="o">[]</span> <span class="k">end</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">to_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O único boilerplate que sobrou é o <code>Flow.from_enumerable()</code> e o <code>Flow.partition()</code> envolvendo o <code>Flow.map</code> — e pronto!</p>
<p>Repara que configurei <code>@max_demand</code> como 60. Você precisa ajustar esse valor para mais ou para menos dependendo da sua conexão com a internet — é questão de experimentar. Por padrão, o Flow dispara 500 processos em paralelo, o que é demais para web scraping numa conexão residencial normal: você vai sofrer retornos decrescentes. Era exatamente isso que eu controlava antes com o Poolboy, limitando o pool a cerca de 60 transações simultâneas no máximo.</p>
<p>Infelizmente, nem tudo é tão simples quanto parece. Rodando essa nova versão no modo de teste, o resultado foi:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>58,85s user 13,93s system 37% cpu 3:13,78 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mais de 3 minutos no total, usando cerca de 37% da CPU disponível.</p>
<p>Minha versão anterior imediata, com toda aquela engenharia do Poolboy, Task.Supervisor, GenServer e companhia, ainda me dá isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>100,67s user 20,83s system 152% cpu 1:19,92 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Menos da <strong>METADE</strong> do tempo, ainda que usando todos os núcleos da CPU. Minha implementação customizada ainda aproveita melhor os recursos disponíveis. Tem algo na implementação com Flow que ainda não acertei. Já tentei aumentar o <code>max_demand</code> de 60 para 100 e não melhorou nada. Deixar no padrão 500 piora tudo — mais de 7 minutos.</p>
<p>De qualquer forma, pelo menos a implementação ficou muito mais fácil de ler (e consequentemente de manter). Mas ou a implementação com Flow tem gargalos que ainda não identifiquei, ou estou usando ele de forma errada. Se você souber o que é, deixa nos comentários.</p>
]]></content:encoded><category>elixir-e-erlang</category><category>performance</category><category>sistemas-distribuidos</category></item></channel></rss>