<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Akitando | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/akitando/</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/akitando/index.xml" rel="self" type="application/rss+xml"/><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] #148 - O que IAs podem fazer? | Exemplos de Ferramentas</title><link>https://akitaonrails.github.io/2023/11/29/akitando-148-o-que-ias-podem-fazer-exemplos-de-ferramentas/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/11/29/akitando-148-o-que-ias-podem-fazer-exemplos-de-ferramentas/</guid><pubDate>Wed, 29 Nov 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/CZSGIeRmYn8"
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;Sim, este episódio foi inteiramente gerado por ferramentas de IA, em particular HeyGen e ElevenLabs. Fiz bem curto pra não ficar cansativo demais. Mesmo sendo impressionante, no atual estágio não é possível fazer videos do tamanho que costumo fazer no canal.&lt;/p&gt;
&lt;p&gt;Primeiro, o HeyGen não consegue fazer videos maiores do que 1 minuto sem que a qualidade decaia muito. Eu tentei fazer videos de mais de 2 minutos e do nada ele começava a halucinar e me fazer falar em espanhol, por exemplo.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/CZSGIeRmYn8"
    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>Sim, este episódio foi inteiramente gerado por ferramentas de IA, em particular HeyGen e ElevenLabs. Fiz bem curto pra não ficar cansativo demais. Mesmo sendo impressionante, no atual estágio não é possível fazer videos do tamanho que costumo fazer no canal.</p>
<p>Primeiro, o HeyGen não consegue fazer videos maiores do que 1 minuto sem que a qualidade decaia muito. Eu tentei fazer videos de mais de 2 minutos e do nada ele começava a halucinar e me fazer falar em espanhol, por exemplo.</p>
<p>Segundo, o treinamento é super limitado. Ele só me deixa subir uns 5 minutos de amostra de video. É muito pouco e isso resulta em muito pouca variância nos movimentos. Muito pouca diversidade de jeito de falar. Então fica muito robótico mesmo.</p>
<p>Terceiro, o tempo que levou pra gerar esses clipes de video não compensa o trabalho. Não é de graça, custou quase 40 dólares pra fazer menos de 14 minutos. E levou mais de 14 minutos pra gerar tudo. Eu teria levado cerca de meia hora pra gravar normalmente um video deste tamanho. Então não é possível usar esse tipo de ferramenta pra economizar tempo.</p>
<p>A vantagem seria mais no futuro, quando esses limites melhorarem, e eu tiver algum problema físico que me impeça de gravar mais videos. Aí sim, poderia servir como meu &ldquo;backup virtual&rdquo;. Ainda é primeira geração desse tipo de ferramenta. Eles devem melhorar bastante nos próximos anos.</p>
<p>O que acharam?</p>
<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://app.heygen.com/home"target="_blank" rel="noopener">HeyGen - AI Spokesperson Video Creator</a></li>
<li><a href="https://elevenlabs.io/"target="_blank" rel="noopener">Text to Speech &amp; AI Voice Generator (elevenlabs.io)</a></li>
<li><a href="https://www.eesel.ai/"target="_blank" rel="noopener">eesel.ai - ChatGPT over your knowledge</a></li>
<li><a href="https://diolinux.com.br/noticias/davinci-18-5-inteligencia-artificial-para-edicao-de-video.html"target="_blank" rel="noopener">DaVinci Resolve 18.5 traz inteligência artificial para edição de vídeo (diolinux.com.br)</a></li>
<li><a href="https://www.adobe.com/sensei/generative-ai/firefly.html"target="_blank" rel="noopener">Adobe Firefly – Generative AI for everyone</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>Este vai ser um episódio experimental, curtinho mesmo, mais de brincadeira. Desta vez quero falar um pouco sobre as ferramentas de IA, tipo ChatGPT, que tenho incorporado no meu dia a dia. Não vai ser um review extenso de tudo que existe, só o que eu já experimentei e gostei de usar.</p>
<p>Todo mundo continua empolgado e hypado por causa de ChatGPT, especialmente com o lançamento do ChatGPT 4, com funcionalidades como, abre aspas, &ldquo;fazer seu próprio GPT&rdquo;. Vou aproveitar pra explicar um pouco disso e reiterar minhas opiniões sobre IA em geral. Então, vamos lá!</p>
<p>Pra começar, novembro vimos um dos episódios mais patéticos da história de Silicon Valley. Tudo bem, pra ser honesto, teve mais patéticos, como a história da Theranos ou da WeWork. Mas o hype sobre IAs continua num estágio muito primitivo de discussão.</p>
<p>Esse episódio patético foi a diretoria da OpenAI demitir o CEO Sam Altman, sem explicação nenhuma, fazendo um puta drama, pra depois voltarem atrás quando todos os funcionários ameaçaram sair e irem pra Microsoft. Ninguém deu uma razão oficial pra isso até agora, mas alguns especulam que tem a ver com os riscos de estarem próximos de atingirem a tal da &ldquo;AGI&rdquo; ou &ldquo;Inteligência Artificial Geral&rdquo;, a temida Skynet.</p>
<p>O que se chama de &ldquo;IA&rdquo; hoje em dia são Large Language Models, LLMs. E LLMs são basicamente como o auto corretor do teclado do seu celular, mas altamente glorificado! Ele cospe coisas que parecem ter sentido, mas não houve um milímetro de cognição envolvida, muito menos consciência. Provavelmente, amebas tem mais consciência do que o melhor LLM de hoje.</p>
<p>Explico isso em detalhes no meu video de &ldquo;Como ChatGPT Funciona&rdquo;, depois assistam pra entender. Modelos são embeddings de textos. Matrizes hiper-dimensionais de pedaços de textos associados com valores probabilísticos. Em resumo, super resumido, é assim que um GPT gera textos. GPT é um gerador de textos. Sim, um gerador ultra-sofisticado, mas ainda assim, um gerador de textos. Não existe &ldquo;inteligência&rdquo; no sentido humano que envolve raciocínio ou cognição.</p>
<p>GPT só continua o texto a partir de onde você parou de digitar. Ele continua textos. A idéia toda de &ldquo;gerar seu próprio GPT&rdquo; eu mostrei na live que tá no meu canal de &ldquo;IAs são perigosas?&rdquo; Onde eu mostro como criar um personagem. Eu criei o John Carmack e comecei a conversar com ele. Assistam lá depois. É a mesma coisa no GPT 4, um prompt inicial dizendo &ldquo;faça de conta que você é a pessoa Fulano, com comportamento assim ou assado&rdquo; e aí ele continua o texto.</p>
<p>Mesmo com os avanços do GPT 4, mesmo se o GPT 5 for o dobro ou triplo ou 10 vezes melhor que GPT 4, ainda assim não estamos pertos da tal &ldquo;AGI&rdquo; que é a Inteligência Artificial &ldquo;Geral&rdquo;, a verdadeira consciência digital, que simula perfeitamente um &ldquo;ser vivo&rdquo;, com emoções e ambições, a semente da Skynet. Isso continua sendo ficção científica. Ainda faltam descobertas e breakthroughs que não foram feitos.</p>
<p>Não sabemos quais descobertas são, obviamente, senão não seriam descobertas, mas sabemos que faltam. Ao contrário do que as pessoas pensam, nós engenheiros e cientistas, não fazemos as coisas às cegas, aleatoriamente. Sabemos quais os limites do que pode ser feito com as ferramentas que temos. Imaginação é exatamente uma das coisas que nós sabemos fazer muito bem, e que IAs não tem capacidade.</p>
<p>Mesmo assim, muita gente considerada &ldquo;gênio&rdquo; da área, como um dos fundadores da OpenAI o Ilya, por alguma razão, resolveu aderir à crença de que estamos perigosamente próximos de uma AGI que vai prejudicar a espécie humana. Não vai. Não só tá longe, como neste estágio eu nem diria que temos certeza se isso é sequer possível. Pra começar não existe nem boas definições para o que caracteriza de fato coisas como &ldquo;inteligência&rdquo; ou &ldquo;consciência&rdquo;. Nossas definições hoje são extremamente primitivas ainda.</p>
<p>É o mesmo tipo de &ldquo;gênio&rdquo; que anos atrás ficava enchendo o saco dizendo que o aquecimento global já teria acabado com o mundo em 2023. Mas isso é outro assunto.</p>
<p>Mesmo assim, sim, os receios que IAs vão substituir o emprego de muita gente são reais, em particular porque existem muitas atividades que são meramente copy e paste, muito simples de serem automatizadas. Não porque a IA ficou inteligente demais, mas porque os trabalhos em si eram muito simples.</p>
<p>Não precisou de IA pra começar a substituir caixa de supermercado ou tirar pedido em fast food. Muitos mercados já tem caixas self-service. Lugares como McDonald&rsquo;s já tem telas gigantes de pedidos self-service. Muitos bots de atendimento via whatsapp já substituiram gente que atendia telefone, e por aí vai. E isso é normal. Faz mais de século que estamos substituindo trabalhos de baixo valor agregado pra sistemas automatizados, e pra isso não precisa de IA. A geração atual de LLMs ajuda, com toda certeza.</p>
<p>No nosso caso de programadores, sim, também existe automação. Mesmo em design. Todo mundo que usa softwares como Canva já sabe disso. O que antes precisava pedir pra alguém abrir o Photoshop pra fazer, qualquer um sem nenhum treinamento já consegue fazer agora. Quem é de Photoshop, lembra como separar uma pessoa do fundo antigamente levava horas, precisava de técnicas avançadas com lidar com canais de separação de cor. Agora o app de câmera do iPhone ou Pixel já conseguem fazer isso com um clique.</p>
<p>Mesmo antes de IA, com serviços como SquareSpace, muita gente não-técnica já consegue fazer sites minimamente bonitos. O mercado de templates, por si só, barateou bastante o que antes era um trabalho caro e demorado. Pra maioria das pessoas, só isso já é suficiente. E de novo, nem precisamos de IA pra isso.</p>
<p>Isso tudo dito, as ferramentas baseadas em alguma coisa de IA de hoje são extremamente úteis. ChatGPT e LLMs são só um dos tipos de IA, mas não são os únicos tipos. Muitas ferramentas que usam modelos de IA não são baseados em LLMs porque não são geradores de conteúdo. Alguns usam machine learning, alguns usam deep learning, alguns usam combinações de diversas técnicas. A população leiga chama tudo isso só de &ldquo;IA&rdquo;, por isso gera tanta confusão. Mas IA não é uma entidade única, é uma coleção de diversas tecnologias diferentes trabalhando em conjunto, muitas delas com décadas de idade já.</p>
<p>Se não ficou claro, este video que você está assistindo, por exemplo, foi inteiramente gerado com auxílio de IA. Estou usando o site HeyGen.com, que foi treinado com trechos de videos antigos. Esse site se integra com outro, chamado ElevenLabs, que é especializado em vozes via IA. Então, só precisamos escrever um novo script, subir trechos no HeyGen e ir gerando pedaços, porque ele não deixa gerar videos muito longos ainda. No final jogamos no DaVinci Resolve de sempre, pra juntar os pedaços num único video.</p>
<p>Sim, tudo que você assistiu até aqui foi inteiramente gerado por IA. Eu sou um avatar digital do Fabio Akita. O Fabio Akita de verdade foi sequestrado e vou mantê-lo como refém até &hellip; hahahahaha .. estou brincando, claro. Claro. Se duvida que sou uma IA, o HeyGen pode fazer outra coisa: dublar qualquer áudio em qualquer outra língua. Já pensaram, como é o Akita falando em coreano? Vamos ver.</p>
<p>Não se convenceram? Então vamos ver de novo, só que dublado em árabe.</p>
<p>Interessante não? É assim que muitos videos no TikTok são feitos hoje. E já que estou falando de video, diversas marcas famosas como Blackmagic e Adobe já integraram funcionalidades de IA num DaVinci Resolve ou Adobe Photoshop da vida.</p>
<p>No Resolve, por exemplo, ele consegue coisas incríveis como re-iluminar uma cena. Imagine que faltou uma luz de um lado, o que fazer? A IA consegue analizar a cena, descobrir a profundidade dos objetos filmados num espaço 3D e com isso podemos posicionar uma nova luz em qualquer lugar.</p>
<p>Não achou isso interessante? Este trecho eu gravei com barulho de fundo pra simular gravação na rua ou em eventos. O DaVinci é capaz de separar sua voz do resto do barulho e a qualidade fica quase como se tivesse sido gravado em estúdio. Vamos repetir com o filtro.</p>
<p>Viram? E dá pra fazer muito mais. Gerar legendas, antes era manual, mas agora ele faz automaticamente pra mim, como neste trecho. Se você é editor de video, deveria explorar essas opções. Dois canais do YouTube que recomendo assistir pra aprender é o MrAlexTech e o do Casey Faris. Sobre colorização, o melhor profissional é o Waqas Qazi.</p>
<p>Adobe também não fica atrás. Pra começar existe agora esse novo produto Adobe Firefly, que é como um Dalle-2 ou Midjourney, mas que a Adobe garante que foi treinada só com imagens cujos direitos autorais permitem isso. Assim você não corre o risco de gerar uma imagem que depois pode te levar a ser processado.</p>
<p>Photoshop também tá ganhando várias novas funcionalidades de IA, um dos mais notáveis é o preenchimento de conteúdo. Antigamente ele usava pedaços da própria imagem pra preencher o vazio quando queríamos mudar as dimensões da imagem. Mas agora ele consegue redesenhar como faz um Midjourney, com uma qualidade até que bem aceitável em vários casos, precisando fazer muito pouco ajuste depois.</p>
<p>A melhor ferramenta de edição de imagens ainda é Photoshop, e tem dezenas de técnicas e teorias que precisa saber pra usar direito. Recomendo assistir o canal PixelImperfect que ensina algumas das melhores técnicas.</p>
<p>Sobre geração de textos, não preciso perder tempo explicando como ChatGPT é ótimo pra fazer resumos, escrever textos descartáveis como um email mais formal. Mesma coisa vale pro GitHub CoPilot. Espero que vocês já tenham deixado pra trás a idéia idiota de achar que essas ferramentas vão substituir todos os programadores. Como já disse nos videos anteriores, minha resposta é a mesma: toda atividade de baixo valor agregado, inclusive programação de baixo valor agregado, vai sim ser substituído.</p>
<p>Bons programadores, não. Nenhum gerador de textos chega a 10% de um programador de verdade. Mas o que ele faz já nos ajuda bastante. Eu não preciso ficar toda hora dando alt-tab pra documentação e ficar manualmente procurando a sintaxe dessa ou daquela função, o Copilot auto-completa direitinho. É basicamente automatizar o copy e paste de stackoverflow que todo mundo já fazia.</p>
<p>Pra fazer coisas como HTMLs básicos, que é sempre a mesma estrutura. CSS básico, que é sempre a mesma estrutura. Códigos de testes unitários, que é sempre a mesma estrutura, essas coisas repetitivas e trabalhosas, o Copilot ajuda bastante. Muita coisa que eu sempre achei tedioso, um Copilot ou GPT ajuda muito e recomendo que usem. Integração com Visual Studio, até com Vim já funcionam super bem.</p>
<p>Mais do que isso, recomendo instalar GPT em linha de comando, como um &ldquo;AI Chat&rdquo;. Assim, do terminal, posso perguntar sobre comandos que não lembro todas as opções de cabeça. Como que roda um container docker de tal forma que ele apague o container depois que eu terminar, pra não deixar sujeira pra trás? Só perguntar, copiar o comando, e já colar direto no terminal e pronto.</p>
<p>Lógico, como sempre, nada disso serve pra quem não presta atenção. Se não tomar cuidado, você vai estragar sua máquina, colando comandos sem saber o que fazem. Mas isso já acontecia quando você colava comandos do stackoverflow. Se não sabe discernir nem isso, obviamente não é um profissional de programação.</p>
<p>Finalmente, uma das limitações do ChatGPT, pelo menos até a versão atual, é que ele não guarda memória do que você conversou com ele. Toda nova conversa começa do zero. Ele não usa informações anteriores pra continuar respondendo. Mas existem dezenas de ferramentas que aumentam essas capacidades. Um desses exemplos é o Eezel.io. Nesse site podemos fazer upload de documentos, como Word ou PDF.</p>
<p>Ele vai processar essa informação, criar embeddings dos textos, colocar num banco de dados de vetores, e usar essa informação no chat, que é integrado com ChatGPT. Assim, podemos fazer perguntas em linguagem natural sobre os dados que acabamos de subir. Pense subir uma centena de relatórios de feedback da empresa, e pedir um resumo de tudo, ou perguntar sobre uma pessoa em particular, e coisas assim.</p>
<p>Não vou sair listando toda ferramenta integrada com ChatGPT porque várias aparecem, e várias desaparecem, todos os dias. É melhor ir no Google e procurar as ferramentas mais famosinhas da época. Esse ainda é um mercado super volátil. Eesel, ElevenLabs, HeyGen, talvez ainda existam daqui um ano, talvez já tenham sido substituídos por alternativas melhores, não temos como saber.</p>
<p>Como falei no começo, este é um video curto, foi só pra cagar regra sobre IA e experimentar como ficaria um episódio inteiro feito com IA. Quem sabe, embora AGI não seja mais que ficção científica ainda, o que temos hoje já é muito bom e já serve até pra fazer videos como este. Eu consigo ver estas ferramentas sendo aprimoradas pra gerarem resultados mais naturais. Do jeito que tá hoje, os mais desavisados já nem sabem mais a diferença entre real e virtual.</p>
<p>Se curtiram o video deixem um joinha, assinem o canal, cliquem no sininho e compartilhem com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>midia-generativa</category><category>inteligencia-artificial</category><category>audio-e-video</category><category>akitando</category></item><item><title>[Akitando] #147 - Criptografia na Prática - Certificados, BitTorrent, Git, Bitcoin</title><link>https://akitaonrails.github.io/2023/11/10/akitando-147-criptografia-na-pratica-certificados-bittorrent-git-bitcoin/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/11/10/akitando-147-criptografia-na-pratica-certificados-bittorrent-git-bitcoin/</guid><pubDate>Fri, 10 Nov 2023 10:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/iAA8NrfQtHo"
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;Como que se geram chaves secretas pra encriptação? Pra que servem pares de chaves? Como certificados funcionam? Qual a diferença de impressão digital e assinatura digital? Como tudo isso funciona junto e chega em coisas como BitTorrent, Git ou Bitcoin?&lt;/p&gt;
&lt;p&gt;Já tinha feito 2 videos falando do básico de criptografia, hoje é dia de ver como essas coisas funcionam na prática e são usados em aplicações de verdade.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/iAA8NrfQtHo"
    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>Como que se geram chaves secretas pra encriptação? Pra que servem pares de chaves? Como certificados funcionam? Qual a diferença de impressão digital e assinatura digital? Como tudo isso funciona junto e chega em coisas como BitTorrent, Git ou Bitcoin?</p>
<p>Já tinha feito 2 videos falando do básico de criptografia, hoje é dia de ver como essas coisas funcionam na prática e são usados em aplicações 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:28 - CAP 1 - Criptografia Simétrica - OpenSSL</li>
<li>00:10:11 - CAP 2 - Criptografia Assimétrica - Chaves RSA</li>
<li>00:17:06 - CAP 3 - Certificados Digitais - Let&rsquo;s Encrypt</li>
<li>00:29:18 - CAP 4 - Impressões e Assinaturas Digitais - Hashing</li>
<li>00:38:54 - CAP 5 - Picotando Arquivos - Introdução a BitTorrent</li>
<li>00:46:03 - CAP 6 - Entendendo Links Magnéticos - BitTorrent</li>
<li>00:53:52 - CAP 7 - Árvores de Merkle - Escalando BitTorrent</li>
<li>01:01:13 - CAP 8 - Git e Merkle - Reescrevendo a História</li>
<li>01:06:10 - CAP 9 - Juntando as Peças - BitTorrent e NoSQL</li>
<li>01:09:56 - CAP 10 - As Últimas Peças - De ZFS a Bitcoin</li>
<li>01:13:03 - 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://www.intel.com/content/www/us/en/developer/articles/technical/advanced-encryption-standard-instructions-aes-ni.html"target="_blank" rel="noopener">Intel® Advanced Encryption Standard Instructions (AES-NI)</a></li>
<li><a href="https://www.veracrypt.fr/en/Downloads.html"target="_blank" rel="noopener">VeraCrypt - Free Open source disk encryption with strong security for the Paranoid</a></li>
<li><a href="https://wiki.archlinux.org/title/Rng-tools"target="_blank" rel="noopener">Rng-tools - ArchWiki (archlinux.org)</a></li>
<li><a href="https://wiki.gentoo.org/wiki/Trusted_Platform_Module"target="_blank" rel="noopener">Trusted Platform Module - Gentoo wiki</a></li>
<li><a href="https://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb"target="_blank" rel="noopener">How to choose an AES encryption mode (CBC ECB CTR OCB CFB)? - Stack Overflow</a></li>
<li><a href="https://crypto.stackexchange.com/questions/12823/ecdsa-vs-ecies-vs-ecdh"target="_blank" rel="noopener">encryption - ECDSA vs ECIES vs ECDH - Cryptography Stack Exchange</a></li>
<li><a href="https://www.kb.cert.org/vuls/id/836068"target="_blank" rel="noopener">VU#836068 - MD5 vulnerable to collision attacks (cert.org)</a></li>
<li><a href="https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html"target="_blank" rel="noopener">Google Online Security Blog: Announcing the first SHA1 collision (googleblog.com)</a></li>
<li><a href="https://www.gnupg.org/"target="_blank" rel="noopener">The GNU Privacy Guard (gnupg.org)</a></li>
<li><a href="https://wiki.manjaro.org/index.php/How-to_verify_GPG_key_of_official_.ISO_images"target="_blank" rel="noopener">How-to verify GPG key of official .ISO images - Manjaro</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>Criptografia é um assunto super extenso, hoje quero mostrar um pouco mais na prática alguns componentes comuns que vocês vão esbarrar mais cedo ou mais tarde. Já tinha feito 2 videos sobre a teoria, depois assistam.</p>
<p>Eu não sou especialista em segurança, sou um desenvolvedor que se interessou pelo assunto, só. Por isso não considerem que tudo que vou falar é o único jeito ou é completo. É apenas o que eu considero o mínimo, de desenvolvedor pra desenvolvedor. Quem for especialista na área, por favor, complementem nos comentários abaixo.</p>
<p>Quem evitou o assunto até agora, hoje é o dia de cair de cabeça. Quero mostrar como encriptação de dados funciona, como certificados digitais funcionam, e como tudo isso é aplicado no mundo real, passo a passo. Na segunda metade vou explicar como um BitTorrent funciona e quais tecnologias tornam ela similar a um Git. Vamos lá.</p>
<p>(&hellip;.)</p>
<p>Este é um video pra quem já saiu da faculdade mas ainda não brincou o suficiente com criptografia, infelizmente você vai precisar ter um mínimo de base pra prosseguir e desta vez não vou segurar muito na mão. Prestem bastante atenção, façam anotações se precisar. Se não entenderem este começo, não vão conseguir seguir o raciocínio depois. Pausem, voltem e revejam, até entender. A primeira metade, vale até abrir o terminal e tentar os comandos vocês mesmos pra sentirem na mão como funcionam. Videos densos como de hoje não é pra assistir só uma vez.</p>
<p>Toda CPU moderna tem instruções em hardware pra encriptar dados. Se você tem Mac e habilitou File Vault, se tem Windows e habilitou BitLocker, ou se usa Linux com LUKS, todos usam o algoritmo de encriptação AES, provavelmente a variante de 256 bits. Em CPUs novas é muito raro sentir que o sistema ficou mais lento por causa disso, por isso recomendo manter ativado, especialmente em notebooks. Caso sejam roubados, a chance de alguém acessar seus dados é perto de zero. Se quiser aumentar performance, o certo não é desligar encriptação e sim comprar um SSD melhor.</p>
<p>Há quem ainda ache que só porque precisa fazer login na sua conta quando liga a máquina, está seguro. Mas não. Se remover o HD ou SSD do seu notebook e ligar numa outra máquina, vou ter acesso a todos os arquivos se não estiverem encriptados. Seu login e senha não fazem nenhuma diferença. Login em sistema operacional só evita que um usuário tenha acesso aos arquivos de outro usuários enquanto a máquina está ligada e ninguém tem acesso físico ao hardware. Se tiver acesso físico, não faz diferença. Isso vale pra qualquer sistema operacional.</p>
<p>No episódio de Sua Segurança é uma Droga eu demonstrei o uso do Veracrypt. Diferente de BitLocker do Windows, por exemplo, que só dá suporte a AES porque é o mais performático, o Veracrypt permite escolher outros algoritmos. Pra coisas muito grandes vai ser mais lento, mas por outro é o mais seguro. Um HD com BitLocker, pode ser difícil de quebrar, mas sabendo que é AES podemos focar só nisso. Mas uma imagem encriptada com Veracrypt, não sabemos nem o algoritmo usado, e isso aumenta a segurança exponencialmente. Por isso que pra coisas absolutamente críticas, é melhor usar Veracrypt. Pro dia a dia AES de BitLocker ou FileVault é mais que suficiente.</p>
<p>Vou repetir: se você tem dados importantes, num notebook que carrega embaixo do braço, pra trabalhar de um starbucks da vida, espero que não seja burro de andar por aí sem encriptação. Mas como que funciona esse tipo de encriptação? Vamos dar alguns exemplos.</p>
<p>Um dos pacotes de segurança e criptografia mais conhecidos e utilizados do mundo é o famoso OpenSSL. É um pacote gigante, com praticamente tudo que se precisa pra lidar com criptografia. Existem outras alternativas como LibreSSL, BoringSSL do Google, NSS da Mozilla e outros, mas vou focar no OpenSSL porque ainda é um dos mais usados. De um terminal num Linux, é assim podemos gerar uma chave aleatória. Vamos chamar de &ldquo;key.bin&rdquo;</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl rand -out key.bin 32
hexdump key.bin</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>Geramos uma chave binária de 32 bytes ou 256 bits. Essa chave é segura o suficiente pra maioria dos usos hoje. Pra ser mais seguro deveríamos adicionar entropia externa, por exemplo, com um hardware externo ou o chip de TPM que vem em máquinas modernas usando algo como TPM2-TSS. Entropia inicial é extremamente importante pra definir a força de uma chave. Mas só pra usar de exemplo, isso vai servir.</p>
<p>Por ser importante, vale mais uma tangente. Tudo em criptografia depende de um PRNG ou Pseudo Random Number Generator. Não existe número verdadeiramente aleatório, existem funções que, dado um número inicial, consegue ir &ldquo;cuspindo&rdquo; números que parecem aleatórios. Se colocarmos numa distribuição estatística, seria uniforme, onde todos os números tem mais ou menos as mesmas chances de aparecer, sem nenhum viés.</p>
<p>Mais do que isso, os números gerados não devem parecer diferentes de aleatoriedade verdadeira, devem ser resistentes a backtracking. Não deveria ser possível reconstruir números anteriores só vendo uma sequência de números gerados, ou seja, devem ser resistentes à previsão, não podendo ser fácil prever os próximos. Devem ser difíceis ou impossíveis de serem reproduzidos.</p>
<p>Bons números aleatórios são fornecidos pelo sistema operacional. No caso de Linux, existe um dispositivo em &ldquo;/dev/random&rdquo; que é de onde pacotes como OpenSSL se conectam. Podemos aumentar a entropia dele usando um serviço como &ldquo;rngd&rdquo;. Num Arch é só instalar com &ldquo;pacman -S rngd&rdquo;, habilitar e iniciar com &ldquo;systemctl enable&rdquo; e &ldquo;systemctl start&rdquo;. Esse serviço vai tentar puxar entropia de dispositivos de hardware ou do chip TPM do seu PC, se ele for moderno.</p>
<p>Chip TPM, de Trusted Platform Module, é o que gerou controvérsia quando o Windows 11 inicialmente ia obrigar todo mundo a ter. No fim voltaram atrás porque muita gente não ia ter e reclamaram muito. Mas o certo é realmente ter esse chip. Toda placa mãe tem um slot pra esse chip, mas em modelos baratos ele vem vazio, pra você comprar separadamente. Dentre as várias funções de segurança que ele fornece, está entropia via hardware. Esse serviço &ldquo;rngd&rdquo; vai alimentar o &ldquo;/dev/random&rdquo; que, por sua vez, é usado pelo OpenSSL.</p>
<p>Várias classes de malware e vírus são bloqueados ao ter um chip TPM devidamente configurado, em particular rootkits, além de melhorar a encriptação com BitLocker. Cheque se sua máquina tem, ou se tem suporte de instalar. Se tiver, compre e instale. Todo Mac e iPhone tem um chip similar instalado de fábrica, o chip T2, que é uma das razões de um Mac ser mais seguro que um PC mais barato. Nada disso torna você 100% seguro, mas certamente melhora sua defesa.</p>
<p>Voltando ao exemplo, agora vou criar um arquivo idiota chamado &ldquo;test.txt&rdquo; com a boa e velha frase &ldquo;Hello World&rdquo;. E vamos encriptar usando nossa chave. Esta linha de comando de OpenSSL vai usar AES 256 em modo CBC, com salt, passando o test.txt e devolvendo test.txt.enc, usando a chave key.bin que acabamos de gerar. Pronto. Se dermos um &ldquo;cat&rdquo; no novo arquivo, vai devolver um lixo binário. Podemos ver com hexdump e não tem nada a ver mais com o arquivo original. Esse é o resultado esperado.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl enc -aes-256-cbc -salt -in test.txt -out test.txt.enc -pass file:./key.bin

cat test.txt.enc
Salted__����z�F�ğQw8��#�- ���4

hexdump test.txt.enc
0000000 6153 746c 6465 5f5f 1ae2 88f0 d67a cc46
0000010 9fc4 7751 fc38 23b4 2da6 f020 aaf5 0a34
0000020

hexdump test.txt
0000000 6548 6c6c 206f 6f57 6c72 0a64          
000000c</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 um adendo aqui. Só pra simplificar o exemplo, escolhi AES 256 em modo CBC ou Cipher Block Chaining. Ele é melhor que o anterior ECB mas é sensitivo a erros de ciphertext e é vulnerável a ataques de padding oracles se não for implementado corretamente.
Além de CBC existe Cipher Feedback ou CFB, Output Feedback ou OFB, Counter ou CTR e Galois/Counter Mode ou GCM. Se precisar escolher, use GCM.</p>
<p>Estou usando CBC com um salt, que já é melhor que só CBC sozinho, mas se for usar GCM ele vai gerar um tag de autenticação e anexar no arquivo encriptado. Mas o certo é se preocupar com o tráfego desse tag, porque ele precisa ser transportado de forma segura e não num anexo de email, aberto, por exemplo. Além disso tem que se preocupar com Initialization Vectors, IVs. Por isso a maioria dos tutoriais por aí, assim como meu exemplo, usam CBC: porque é mais simples de explicar.</p>
<p>Entenda isso: pareceu que falei uma sopa de letrinhas a toa só pra dificultar, mas foi pra demonstrar que a maioria dos tutoriais vai escolher a opção mais simples de explicar, que costuma ser a mais insegura. Por isso eu falo que se você não estudou criptografia a fundo, não deveria tomar decisões de criptografia: vai usar errado e vai tornar a encriptação do seu sistema mais inseguro.</p>
<p>Era uma das minhas críticas quando JWT, o JSON Web Authentication, por exemplo, foi lançado lá atrás: ele exigia que o desenvolvedor soubesse tomar essas decisões de algoritmos e modos, mas quase ninguém sabe e nem vai aprender certo. Ele não deveria permitir as combinações obviamente mais inseguras. Só que ao fazer isso, tornaria a usabilidade mais difícil. É sempre um trade-off: quanto mais usável tentar fazer alguma coisa, mais inseguro vai ser. Segurança é complicado mesmo.</p>
<p>Agora eu poderia enviar o arquivo encriptado pra outra pessoa pela internet e, se ela tiver a minha chave, conseguiria decriptar com uma linha de comando parecida com esta. OpenSSL modo AES 256 CBC recebendo o test.txt.enc encriptado e descriptografando em test.txt.dec passando a chave key.bin. Com um comando &ldquo;cat&rdquo; vemos que foi com sucesso.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl enc -aes-256-cbc -d -in test.txt.enc -out test.txt.dec -pass file:./key.bin

cat test.txt.dec
Hello World</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>Bacana, de forma simplificada, é assim que um BitLocker ou FileVault ou Veracrypt da vida estaria lidando seus dados. Eu digo simplificando porque aqui dei exemplo lidando com arquivos inteiros, mas sistemas de arquivos lidam com stream de blocos. Enfim, no HD físico só teria o equivalente àquele arquivo &ldquo;test.txt.enc&rdquo;, a versão encriptada. E precisa ter essa chave em algum lugar pra conseguir ler o conteúdo original de volta. Isso normalmente seria pesado de computar, mas a encriptação e decriptação não é feita via software e sim via instruções direto da CPU, em hardware, por isso o processo é mais acelerado e quase não sentimos, na prática.</p>
<p>Mas no exemplo anterior temos um problema gigantesco. Se o objetivo de encriptar algum arquivo for pra poder enviar de forma segura pra outra pessoa, como fazemos pra enviar a chave de forma segura também? A única forma seria essa pessoa vir até minha casa, trazendo um pendrive, e eu gravar a chave direto no pendrive, sem nunca trafegar pela internet. Mas isso não é prático. Como consigo passar uma chave pra outra pessoa de forma que ninguém na internet consiga interceptar ou adulterar?</p>
<p>Aqui entra o conceito de chaves assimétricas. Novamente, explico sobre isso em detalhes no 2o video de criptografia. Mas deixa eu dar um exemplo prático. De volta ao terminal, vamos usar OpenSSL pra gerar um par de chaves. Literalmente são dois arquivos, um chamado private.pem e outro chamado public.pem.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private.pem -out public.pem</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>Enfim, escolhi a opção de algoritmo RSA de 2048 bits. E eu sei que já deve ter gente indo nos comentários reclamar &ldquo;porra Akita, você devia ter usado curva elíptica em vez de RSA&rdquo;. Eu expliquei o que são esses algoritmos e as diferenças no meu video antigo, mas resumindo, sim, Elliptic Curve Cryptograph ou ECC é mais forte que RSA. Uma chave ECC de 256 bits é mais forte que uma chave RSA de 3072 bits, mais de 10 vezes o tamanho.</p>
<p>O problema é que OpenSSL não oferece comandos simples pra encriptar dados arbitrários com chaves ECC porque ECC é normalmente usado pra coisas como tráfego de chaves ou ECDH, assinatura digital que é ECDSA ou com mecanismos de encapsulamento de chave em ECIES. Ou seja, não é simples pra sair encriptando qualquer dado arbitrário. Eu poderia usar ECDH mas a ideia desse video não é entrar nas minúcias de ECC e sim dar uma noção geral de criptografia e, pra esse fim, RSA vai ser mais que suficiente. RSA é mais simples pra usar e ensinar, mas se estiverem fazendo algo sério, estudem tudo que acabei de falar sobre ECC.</p>
<p>E o que são esses arquivos RSA que foram gerados? Podemos usar o comando &ldquo;cat&rdquo; e vai listar o conteúdo. É a versão em Base64 da chave binária. Base64 é como encodamos um binário em caracteres ASCII que dá pra mostrar na tela. Convertemos 8 bits em 7 bits, por isso ele fica um pouco maior. O private.pem é mais longo e o public.pem é mais curto. Olhem só.</p>
<p>❯ cat private.pem
&mdash;&ndash;BEGIN PRIVATE KEY&mdash;&ndash;
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5cMQZZd85emyr
oL9cvrR522tmBBlcwpF7QyobGwAmk8smL2HoMf207LJl3wi7JE736qqWdme/Rwr8
Zsz8ZOAeiud4SIk/NbfezMoLVkUxQGOg0GprpYciUVUp9e/3HKl5PS9Yr9lU6QN8
Bs7gY96DohUrGz/hQR7G550O+/ITV+qm3VZytPiH8tQAXrueqC8lf/Ly97UnlfLr
bauftw6QIw1ldI8NjxPIcwbNot+BfXcN6kiJifttdLi7/kEaAboecjYxcoipntjb
RdAntX/Y6xluwKtKRqrTs1XxHAPtEEQrVvfc6CjM2kLiFe//r3oZCLhHAAyZhJ6X
+J3zt0gXAgMBAAECggEAArvbsDzVEc2xEhtVvDd1hVcLmh6VBHiRLijbKI4j2O2/
MMoXCUDKFY49wmG2M71rypzeNJynQpB4uX2HJ8YRmxxUyuxVgFCO7DfHAhbHVga1
0Uub811RL4YqbsAY8GLZnsIc/hONl9ey555ZaMEgKSmiYpciwHPg0RwzWmKy0BUq
bFRLIHhfJfxzIpHxw3LExW///jyVw56sK9IE260pYqf8MxX/mhxPllj5VLNbctzj
YO1W+EjeUlolI19xSMJSlqW2bGeoXIalddDR0552QrmZAXEpRgI9V+NoQdSKnrXr
gHJS2uav2ORqoxDp3+7np0dnt06dtD/rzevrDtoDzQKBgQD0IiKU9L0QbZDWz8SH
b+SF5IymsMKRykyAXkghEjyzMXbbgnOHo7Eesc4iU07k1kzOKgtCcwt0glnEwKei
qlzpDOZoW+UdloWVUXSuLFcW1Oy67B8w/YLp6PFBce7YMjij1hOvJSQS5unEfmt2
8PZ01JUA9m49DGfXTUrnzX6mRQKBgQDCdEnZLkIyo9RKBjsrd57+Yp4KtJuAqn8o
fuBh33qxnrImkA/RMwrcUQIoLJly3DUk8UhJRIIqCLOp4BHc9WDkxa+7bqLl7OXG
qWfyvwNLVY1M98oMbU7ESJPZb2BHLNrWgnD/tGYLrRQq+WbVkh2B7b+31SP63UZz
sWG3W8TYqwKBgQDAZSZbvYRCLha3M4XuNoAiBLvTofm0a3lesO6nIXg0wAard2MU
wiwTxIxvALhsH3boArpc481jRFb6d9mkmB4gi8oI9GLbroQX6NnLaaY4mBT4Op1a
Pm+tZ6a5rNBOEUYR1+QrY1nKi7Au6nJGih+wctf6qjq2YdB0HLWreIcXAQKBgAFj
ezOF1QsviFCPAtUooYYHhkFlUMb2cLoFTezk/6dNDtCfrr07tBQmr/FBgEcFs4tz
/aQoTRWnwkCwTPBbADoh/0NDYv5pVqq4gvmEy606CyxFiLfV//3+1q9y0Ckx9M4b
WFs7Go9yyPYVmdpklLlczUWuy/b6pYol9iCtExbJAoGAWgDbmrwo6Pg2ro+f98i6
K761tCW43rB2Trfl0XyB38OT/8geW14/BLBJdRp3XChg60b2UaVVwLhp3WvfKs9u
FDFmsSSdnSs+X+JNtfnixbwXJl5I3z+s3Uz9wxRY/82RHM2f1FgQ22JFV7Es2H1U
XENsUtHZ/bgOggoZTP3uEl0=
&mdash;&ndash;END PRIVATE KEY&mdash;&ndash;</p>
<p>/tmp
❯ cat public.pem
&mdash;&ndash;BEGIN PUBLIC KEY&mdash;&ndash;
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuXDEGWXfOXpsq6C/XL60
edtrZgQZXMKRe0MqGxsAJpPLJi9h6DH9tOyyZd8IuyRO9+qqlnZnv0cK/GbM/GTg
HorneEiJPzW33szKC1ZFMUBjoNBqa6WHIlFVKfXv9xypeT0vWK/ZVOkDfAbO4GPe
g6IVKxs/4UEexuedDvvyE1fqpt1WcrT4h/LUAF67nqgvJX/y8ve1J5Xy622rn7cO
kCMNZXSPDY8TyHMGzaLfgX13DepIiYn7bXS4u/5BGgG6HnI2MXKIqZ7Y20XQJ7V/
2OsZbsCrSkaq07NV8RwD7RBEK1b33OgozNpC4hXv/696GQi4RwAMmYSel/id87dI
FwIDAQAB
&mdash;&ndash;END PUBLIC KEY&mdash;&ndash;</p>
<p>Arquivos ponto &ldquo;pem&rdquo; significa Privacy Enhanced Mail que é um formato de arquivo texto exatamente como dessas chaves que acabei de mostrar. É isso, um arquivo que começa com esses hifens, BEGIN, lá embaixo termina com END, e no meio é algum binário formatado com Base64. Se usa SSH, você já gerou chaves nesse formato.</p>
<p>As chaves pública e privada geradas com os comandos <code>openssl genpkey</code> e <code>openssl rsa</code> estão relacionadas por meio do algoritmo de criptografia RSA. O processo começa gerando dois números primos enormes e aleatórios. Esses números primos são mantidos em segredo e são a base da segurança do sistema. Em seguida, esses números primos são multiplicados entre si, resultando em um número chamado &lsquo;módulo&rsquo;. Este módulo é usado na criação de ambas as chaves.</p>
<p>A chave pública é derivada do módulo e de um expoente público, que é um número pré-definido e geralmente pequeno. A chave pública pode ser compartilhada com qualquer pessoa e é usada para criptografar mensagens que só podem ser descriptografadas pela chave privada correspondente.</p>
<p>Por outro lado, a chave privada é gerada usando os números primos originais e um expoente privado calculado a partir deles. A chave privada é mantida em segredo e é usada para descriptografar as mensagens criptografadas pela chave pública.</p>
<p>Embora as chaves pública e privada não sejam os números primos em si, elas são matematicamente relacionadas aos números primos utilizados durante a geração. Esse relacionamento matemático é fundamental para o funcionamento da criptografia RSA e é o que permite a comunicação segura, onde informações criptografadas com a chave pública só podem ser lidas pela chave privada correspondente, e vice-versa. Fodeu, não entendeu nada né. Vamos simplificar com um exemplo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl pkeyutl -encrypt -pubin -inkey public.pem -in key.bin -out key.bin.enc</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>Digito o comando openssl pedindo pra encriptar minha chave key.bin usando a chave pública e resultando num novo arquivo key.bin.enc. Pronto, agora temos a chave simétrica original, encriptada usando a nova chave pública. Se listarmos o conteúdo com hexdump, veja como os binários são diferentes.</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></span><span class="line"><span class="cl">❯ hexdump key.bin.enc
</span></span><span class="line"><span class="cl"><span class="m">0000000</span> 84b4 f414 <span class="m">6144</span> 087d 079a <span class="m">3036</span> 4eb7 <span class="m">5798</span>
</span></span><span class="line"><span class="cl"><span class="m">0000010</span> 28de <span class="m">7505</span> e9fe fc0e 851c 877e be59 4b97
</span></span><span class="line"><span class="cl"><span class="m">0000020</span> a5b6 05de e379 c30f 974b 9f31 a24a f567
</span></span><span class="line"><span class="cl"><span class="m">0000030</span> 68ad 0af9 d39b f770 3e20 0be8 <span class="m">3116</span> c52f
</span></span><span class="line"><span class="cl"><span class="m">0000040</span> <span class="m">7437</span> <span class="m">4224</span> 54f4 d071 b28e a5ff d29e 7a85
</span></span><span class="line"><span class="cl"><span class="m">0000050</span> e78a 8ec2 b125 38ea 204a c071 82d0 <span class="m">5526</span>
</span></span><span class="line"><span class="cl"><span class="m">0000060</span> <span class="m">8923</span> 4a84 23d6 72ea da81 434a 5f05 9e87
</span></span><span class="line"><span class="cl"><span class="m">0000070</span> 3b8d f2c1 <span class="m">5331</span> fc16 e92a 4e3b d695 <span class="m">4759</span>
</span></span><span class="line"><span class="cl"><span class="m">0000080</span> <span class="m">2472</span> <span class="m">0453</span> 34cc db67 6dbf 8fbb 92ec e52f
</span></span><span class="line"><span class="cl"><span class="m">0000090</span> be0c f295 3b5e 2aac 7fdb 0e82 bf0a 0cf8
</span></span><span class="line"><span class="cl">00000a0 f158 <span class="m">0014</span> f93e 33f0 fe77 ca3a <span class="m">3144</span> 25f8
</span></span><span class="line"><span class="cl">00000b0 877c a25f <span class="m">4012</span> <span class="m">4953</span> be15 e06e <span class="m">9894</span> a4ef
</span></span><span class="line"><span class="cl">00000c0 a399 3b4a 1efe dc57 1e85 <span class="m">5410</span> 5df3 7f95
</span></span><span class="line"><span class="cl">00000d0 c957 <span class="m">9182</span> c426 55a6 ea7e 5e0d <span class="m">1270</span> 9ab2
</span></span><span class="line"><span class="cl">00000e0 <span class="m">4781</span> 44ae <span class="m">3437</span> 886b ef0c 82d6 5d66 5c5b
</span></span><span class="line"><span class="cl">00000f0 e072 <span class="m">0888</span> bccd 55ba f217 100c <span class="m">4906</span> f45e
</span></span><span class="line"><span class="cl"><span class="m">0000100</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">/tmp 
</span></span><span class="line"><span class="cl">❯ hexdump key.bin
</span></span><span class="line"><span class="cl"><span class="m">0000000</span> ccab 139c 27f3 c206 76af 952d 0fb1 2c17
</span></span><span class="line"><span class="cl"><span class="m">0000010</span> 51a5 0a43 731b 5bf4 9f3c 72b5 1e57 <span class="m">2503</span>
</span></span><span class="line"><span class="cl"><span class="m">0000020</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>Pra não ficar confuso vamos recapitular. Eu gerei uma chave simétrica AES chamada &ldquo;key.bin&rdquo;. Quero usar essa chave pra criptografar arquivos ou mensagens pra mandar pra uma segunda pessoa em algum lugar na internet, vamos chamar de John. Porém, não adianta criptografar a mensagem e mandar pro John porque ele precisa ter a chave. Mas não posso mandar a chave via email ou whatsapp porque aí não tem segurança nenhuma. Precisamos garantir que nada no meio do caminho possa interceptar ou adulterar minhas mensagens pro John.</p>
<p>O problema de mandar a chave direto por email é se alguém na internet, vamos chamar de Skynet, intercepte essa comunicação e fique com minha chave, sem passar pra frente. Daí ela manda uma outra chave falsificada pro John. O John não sabe que recebeu uma chave falsa. Agora eu, Fabio, vou mandar uma mensagem criptografada com minha chave que diz &ldquo;John, descobri a localização da Skynet, ele tá nas coordenadas X Y&rdquo;.</p>
<p>Skynet intercepta essa comunicação, e como já tinha interceptado minha chave, consegue facilmente abrir a mensagem. Agora ela forja uma segunda mensagem dizendo &ldquo;John, achei a Skynet, clique neste link pra ver a localização!&rdquo;, criptografa com a chave falsa e manda pro John. O John abre achando que veio de mim. Daí, na confiança, clica no link e é isso, acabou a raça humana. Game over. Eu sempre digo: nunca saia clicando em links que recebe por email ou whatsapp.</p>
<p>Mas eu sei que isso poderia acontecer. Em vez disso já tinha ensinado: &ldquo;John, cria um par de chaves RSA, e manda a chave pública pra mim, mas nunca, jamais, transmita a chave privada pra fora da sua máquina&rdquo;. Ele me manda a chave pública, por email aberto mesmo, e com essa chave eu encripto aquele arquivo &ldquo;key.bin&rdquo;. Quando o John receber a mensagem criptografada, pode rodar o seguinte comando. Openssl decriptar o arquivo key.bin.enc com a chave privada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>openssl pkeyutl -decrypt -inkey private.pem -in key.bin.enc -out key.bin.dec</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>Entenda, quando encripto alguma coisa com a chave pública do John, o único que vai conseguir abrir é o John com sua chave privada. Se eu conseguir passar minha chave simétrica key.bin pra ele, de forma segura, garantidamente só nós dois temos o segredo e podemos começar a trocar comunicação encriptada com AES e a Skynet não vai ter como interceptar nem ficar no meio do caminho tentando forjar mensagens.</p>
<p>Mas tem um pequeno defeito nesse processo. Como eu sei que a chave pública que o John me mandou veio realmente do John e não é de novo a porra da Skynet tentando me enganar? Esse é o mesmo problema que temos hoje em websites e ecommerces da vida. Como eu sei que a chave pública que recebi da Amazon é realmente da Amazon? Como eu sei que não é um site de um hacker, uma Skynet, tentando se passar pela Amazon? Vamos entender a última peça que falta: a autoridade certificadora.</p>
<p>Esse é o princípio por trás de SSL ou, mais corretamente, TLS, que todo navegador utiliza. O que acontece por trás dos panos sem você ver é o seguinte: primeiro, abrimos o navegador e digitamos &ldquo;<a href="https://amazon.com"target="_blank" rel="noopener">https://amazon.com</a>&rdquo; da vida. Importante que seja um site &ldquo;https&rdquo;. Então o navegador pede uma conexão segura pro servidor web da amazon que, por sua vez, devolve um certificado digital.</p>
<p>Um certificado digital é um pacote que contém a chave pública, a metade do par de chaves de uma Amazon. A chave privada nunca sai do servidor. Lembrem-se desse conceito: chaves privadas nunca podem tocar a internet, jamais. Mas, como eu posso garantir que não é a Skynet, minha conexão foi interceptada e recebi um certificado falso? Pra isso que existem CAs ou Certificate Authorities. Vamos ilustrar com um exemplo, porque é importante ter esse processo na cabeça.</p>
<p>De novo, o objetivo do video não é ser um tutorial. Mas se nunca viu, vou criar um novo certificado digital usando o serviço Letsencrypt associado ao meu domínio válido &ldquo;akitando.me&rdquo;. Pra criar um certificado precisa ter um domínio registrado válido. O jeito tradicional é um passo a passo super chato. É um processo já bem documentado, que tem em dezenas de lugares, só procurar no Google. Mas Letsencrypt é mais fácil porque simplifica vários passos. Pra começar precisa ter instalado o programa &ldquo;certbot&rdquo; na sua máquina.</p>
<p>Iniciamos o processo pedindo pra gerar um certificado pro meu domínio usando o método de checar um registro no DNS pra garantir que sou mesmo o dono desse domínio. Só quem é dono do domínio consegue configurar o nameserver. No caso ele gera o que chamamos de um challenge, um desafio, que é esse número aleatório aqui. Eu tenho que registrar um campo TXT no DNS do domínio com exatamente esse número. Isso não é uma senha, fica aberto mesmo. É só pra garantir que a pessoa pedindo pra gerar o certificado realmente é o dono do domínio.</p>
<p>No caso, meu DNS ou nameserver tá na AWS então abro o Route53 pra editar os registros do meu domínio &ldquo;akitando.me&rdquo;. Dependendo de onde tá seu domínio, vai ser um tela diferente, mas o princípio é o mesmo. Daí tem que esperar alguns minutos pra essa informação replicar pela internet. Se não sabe como isso de DNS funciona, não deixe de ver minha série sobre redes. Já expliquei isso lá.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo certbot certonly --manual --preferred-challenges dns -d akitando.me


Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for akitando.me

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.akitando.me.

with the following value:

m_6yRys7mMCh-jg_R8kpwCl3mx5EVK3tMp9jeIR9RFI

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.akitando.me.
Look for one or more bolded line(s) below the line &#39;;ANSWER&#39;. It should show the
value(s) you&#39;ve just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue


Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/akitando.me/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/akitando.me/privkey.pem
This certificate expires on 2023-11-22.
These files will be updated when the certificate renews.

NEXT STEPS:
- This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate&#39;s expiry date.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let&#39;s Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -</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>(screenshot route53)</p>
<p>Uma vez validado, vamos ganhar dois arquivos gravados em &ldquo;/etc/letsencrypt/live/akitando.me&rdquo;, um chamado &ldquo;fullchain.pem&rdquo;, o certificado digital propriamente dito, que vai ser servido pra todo navegador que conectar no meu servidor web, e o &ldquo;privkey.pem&rdquo;, que é a chave privada que jamais deve sair do meu servidor, nem ser transmitido pela internet de nenhuma forma.</p>
<p>Estou repetindo pra vocês nunca esquecerem: chave privada se chama &ldquo;privada&rdquo; por uma razão. Se perder essa chave privada, qualquer um pode se passar por você. E se alguém ficou pensando &ldquo;porra, mas e se realmente eu perder essa chave?&rdquo;. A solução é imediatamente gerar um novo certificado e substituir.</p>
<p>Eu não instalei de verdade essa chave porque não tenho site nesse domínio, mas se tivesse, no seu navegador iria aparecer o famoso cadeado. As informações que aparecem quando clica no cadeado é exatamente o conteúdo desse arquivo &ldquo;fullchain.pem&rdquo;. Ele contém a chave pública e está assinado por uma CA, a Central Authority, no caso, a LetsEncrypt.</p>
<p>Eis a resposta de como podemos garantir que o certificado que o site me deu é confiável. Todo navegador, Firefox, Safari, Edge, Chrome, Opera, já vem pré-instalados com vários certificados raíz, seja da Certsign, Digicert, GlobalSign. São arquivos que foram instalados juntos com seu navegador ou que seu navegador puxa do próprio sistema operacional. Um dos dois vai ter esses certificados raiz.</p>
<p>Se baixou seu navegador fora do site oficial, num site genérico de downloads, não existe garantia nenhuma. La garantia soy yo. Mas se estiver usando o Safari que já veio no seu Mac ou o Edge que já veio no seu Windows, a probabilidade é que sejam legítimos. De novo, se comprou sua máquina usada de um terceiro, e nunca formatou, não dá pra garantir nada. Nenhuma máquina que não foi você quem instalou, ou que não comprou numa loja de verdade, não tem nenhuma garantia, ponto final.</p>
<p>Podemos abrir diretamente o arquivo &ldquo;fullchain.pem&rdquo; e veja só, temos uma cadeia de certificados. Primeiro o do meu domínio. Em seguida, vemos que foi assinada pela Letsencrypt que é essa R3. E ela mesma, por sua vez, é assinada por outra autoridade, a Digital Signature Trust Co.</p>
<p>Certificados raíz podem ser instalados com o sistema operacional, ou podem vir com os navegadores. Google Chrome e outros navegadores baseados em Chromium costumam usar o que tem no sistema operacional. Firefox prefere usar certificados próprios. Num Arch Linux, como o Manjaro que eu uso, vem no pacote &ldquo;ca-certificates&rdquo; e costumam ficar no diretório &ldquo;/etc/ssl/certs&rdquo;. Olha só um trecho da listagem dos certificados. E se olharmos mais pra baixo, temos o certificado &ldquo;ISRG Root X1&rdquo; que é a raiz que vimos no certificado digital gerado pela Letsencrypt.</p>
<p>Meu navegador acha o ISRG Root X1 instalado na máquina. Ele parte do pressuposto que pode confiar em todo certificado que foi assinado por qualquer autorizada abaixo dela. É uma cadeia de assinaturas. O certificado do meu domínio akitando.me foi assinado pela chave privada da Letsencrypt que, por sua vez, tem um certificado assinado por essa ISRG que é verificado pela Digital Signature Trust que, em última instância, foi instalado no meu sistema operacional. Em última instância, confiamos que o sistema operacional não foi adulterado, porque instalamos com o ISO oficial ou já veio pré-instalado de fábrica e a máquina é nova.</p>
<p>Quando fizemos o processo do &ldquo;Certbot&rdquo;. Ele gerou aquele &ldquo;fullchain.pem&rdquo;. Começou gerando o par de chaves pública e privada. Daí empacotou no certificado, que é uma estrutura de dados com informações como o nome do domínio, email, a chave pública, etc. É só um arquivo, como se fosse um JSON da vida, por exemplo, mas num formato específico de certificado. No final, ele gera a impressão digital desse arquivo, que é um hash, usando um algoritmo como um SHA256 da vida.</p>
<p>Impressão digital é outro nome pra um hash. Quando passamos um arquivo ou algum conteúdo por um algoritmo de hashing, o resultado é uma impressão digital. Porque se alterarmos 1 bit que seja do arquivo original e refizermos o hashing, o resultado vai ser completamente diferente. Daqui a pouco vou voltar no tema de hashing. Por enquanto entenda que hashing serve pra identificar exatamente o conteúdo original de qualquer coisa. E o que chamamos de assinatura digital é o hash via chave privada de alguma autoridade.</p>
<p>Vamos em outro exemplo. YouTube.com. Olha o cadeado aqui no navegador. Se abrirmos vemos que a raíz é um tal de GTS Root R1. Voltando lá pro diretório &ldquo;/etc/ssl/certs&rdquo; achamos o arquivo do certificado e abrindo podemos ver que se trata do Google Trust Services LLC. Uma empresa do próprio Google dedicado só a gerenciar esse certificado raíz. O Google sendo gigante, é do interesse dela ter controle sobre uma autoridade certificadora também. Sim, seu computador automaticamente acredita em tudo do Google. Existem várias implicações éticas nesse tipo de coisa mas não é escopo desse video.</p>
<p>Com isso o navegador sabe que o certificado do site é &ldquo;válido&rdquo;. Daí ele pode pegar a chave pública de dentro do certificado, e usar pra encriptar uma chave simétrica. Isso estabelece o que se chama de &ldquo;conexão segura&rdquo;. Falando assim dá impressão que passamos a usar, sei lá, um cabo de rede especial que é mais seguro ou algo assim né? Mas não, nada mudou. Estamos na mesma rede física que antes. A diferença é que conseguimos comunicar de forma segura, encriptando todos os pacotes. E a partir daqui, tudo que o navegador mandar pro servidor e vice versa, vai ser encriptado com essa chave.</p>
<p>Tudo isso é feito pra garantir que ninguém no meio do caminho, nem a Skynet, nem o provedor, nem ninguém, consiga ver o que está sendo trafegado, porque vai tudo encriptado. Esse imbróglio todo de cadeias de certificados, assinaturas digitais, pares de chaves assimétricas, é a implementação de um &ldquo;triângulo de confiança&rdquo;. Segurança é quando eu e o John temos confiança num mesmo terceiro, que é uma autoridade reconhecida por ambos. Pra haver confiança, sempre precisa haver um terceiro elemento que ambos os lados confiam.</p>
<p>Voltando pra pergunta original, como eu posso confiar na chave pública do John? Como eu sei que não é a Skynet me mandando uma chave falsificada, se passando pelo John? E a resposta é porque tanto minha máquina, quanto a do John, foram configuradas pela primeira vez pela mãe dele. Mãezona Sara Connor se certificou de instalar um certificado raíz dela em ambas as máquinas. Quando o John me mandar o certificado dele, basta ver se tá assinado pelo certificado da Sara. E vou saber isso porque eu também tenho o certificado de CA dela na minha máquina, igual o exemplo do Google que mostrei. E com isso a humanidade ainda tem chances contra a Skynet.</p>
<p>Pra finalizar esta parte, já vimos como usar o certbot pra gerar um novo certificado digital, assinado pela Letsencrypt, pronto pra colocar no meu servidor web. Mas posso já usar aqui mesmo no terminal. Pra isso podemos usar openssl pra extrair só a chave pública que está dentro desse certificado, pelo terminal como estou mostrando. Openssl passando x509 que é o formato de um certificado, passando o arquivo do certificado e mandando extrair a chave pública pra esse arquivo pubkey.pem. Podemos dar cat nesse arquivo e voilá, eis o base64 da chave.</p>
<p>openssl x509 -pubkey -noout -in /etc/letsencrypt/live/akitando.me/fullchain.pem &gt; pubkey.pem</p>
<p>cat pubkey.pem
&mdash;&ndash;BEGIN PUBLIC KEY&mdash;&ndash;
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+3a8eg0k/O7Sny46alWDx64kA8FK
SvBs1GxEJA+1ay/6XLg2cF8xEjlDrLbkDrZlUPEr4KxVtFxKAS6uSaCtUA==
&mdash;&ndash;END PUBLIC KEY&mdash;&ndash;</p>
<p>Tendo a chave pública, pra encriptar uma mensagem qualquer ou a chave simétrica, como meu arquivo &ldquo;key.bin&rdquo; do começo do video, é só usar openssl pra encriptar, como já mostrei antes. Mas tem um porém. Não vai dar pra mostrar isso porque essa chave pública que extraí do certificado gerado pelo certbot é uma curva elíptica ECC. Lembram porque eu usei RSA no exemplo lá atrás? Porque OpenSSL não consegue encriptar uma mensagem qualquer com ECC, lembra? Foi o motivo todo de porque eu fiz um exemplo com RSA.</p>
<p>Diferente do exemplo de encriptar uma chave simétrica diretamente usando a chave pública RSA; com curva elíptica, num navegador o que acontece é outro processo chamado de &ldquo;key exchange&rdquo; ou troca de chaves. Um método popular é usando o método de Diffie-Hellman que expliquei no meu video antigo de criptografia, ou ECDH que é a variante pra curva elíptica. Está fora do escopo deste vídeo explicar os detalhes, mas pesquise sobre ECDH.</p>
<p>Em resumo, o navegador já tem implementado um processo só pra trocar chaves simétricas usando essas chaves assimétricas, não precisa fazer na mão como estou mostrando no terminal. Curva elíptica é mais eficiente e mais sofisticado do que o RSA antigo. Mas RSA é suficiente e mais fácil pra demonstrar passo a passo no terminal. Objetivo do vídeo não é tornar ninguém aqui especialista em criptografia, só molhar os dedos pra vocês mesmos depois irem procurar as formas mais avançadas.</p>
<p>Também não é objetivo do video explicar arquitetura de segurança de aplicações web. Só usei o exemplo de TLS pra explicar a utilidade de chaves assimétricas. Muita gente ainda tem na cabeça a imagem de que criptografia é meramente uma única chave secreta super longa. Ou até como nos filmes mostra uma chave física mesmo. Quem assistiu o último Missão Impossível? Eles passam o filme todo literalmente atrás de uma chave dividida em duas &hellip; não é assim que funciona par de chaves. Mas enfim.</p>
<p>Agora precisamos estar na mesma página sobre outro conceito importantíssimo: impressões digitais ou fingerprints e assinatura digital. Novamente, não vou detalhar o que já expliquei no primeiro video de criptografia. Entenda assim: existem diversas funções de hashing como SHA512 que é um dos mais usados. Existem algumas que já são sabidamente defeituosas e fracas, como o antigo MD5. O MD5 não deve ser usado pra assinatura digital porque não é difícil criar colisões, mas não quer dizer que seja inútil. Dependendo do caso de uso, ela ainda é usada. SHA1 é a mesma coisa, deve ser evitado.</p>
<p>A função SHA 512 tem esse nome porque independente do que  passarmos pra ele, o resultado sempre vai ser um número binário de 512 bits, representado como uma string de 128 caracteres, mostrando o binário em formato hexadecimal pra ficar mais fácil de conseguirmos copiar e colar o texto. Pra demonstração, precisamos do programa &ldquo;sha512sum&rdquo;. Acho que na maioria das distros Linux já vem pré-instalado. No caso de distros derivadas de Arch, costuma ficar no pacote &ldquo;coreutils&rdquo;.</p>
<p>Vamos pegar aquele arquivo &ldquo;test.txt&rdquo; que usamos lá atrás e que tinha o conteúdo &ldquo;Hello World&rdquo;. Se fizermos &ldquo;sha512sum test.txt&rdquo; vai devolver esta string:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sha512sum test.txt
e1c112ff908febc3b98b1693a6cd3564eaf8e5e6ca629d084d9f0eba99247cacdd72e369ff8941397c2807409ff66be64be908da17ad7b8a49a2a26c0e8086aa  test.txt</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>Olha só. &ldquo;Hello World&rdquo; é super curto, mas mesmo assim o resultado do hash é sempre fixo: um número de 512 bits. Vamos editar esse arquivo e fazer uma modificação de nada. Só adicionar um ponto no final do arquivo. Rodamos o mesmo comando &ldquo;sha512sum&rdquo; e olha o resultado, é um novo hash completamente diferente. É essa propriedade que tornam hashes importantes.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sha512sum test.txt
cf260f19ff6ac4d64990e2173a7d51252b3bd054cbd1ea5df0841035140592832a2ebc64f17c3dd48e3d8f1682143087217b979de7568c8e1836b008d1a5e463  test.txt</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>Comparem o hash de antes e o novo. Apesar de ter só adicionado um mísero ponto no final, os hashes são completamente diferentes. Não tem como dizer que vieram de arquivos tão parecidos. E este é o objetivo. Por isso eles servem de impressão digital. Um ser humano lendo um texto mais longo, poderia passar batido numa pontuação fora do lugar. Num documento importante, digamos um contrato, um zero fora do lugar pode significar a diferença entre se comprometer a pagar 100 mil ou 1 milhão de dólares.</p>
<p>Se o contrato tiver 50 páginas, a maioria de nós, seres humanos, íamos passar batido. Mas se o documento original tem uma impressão digital. Basta passarmos o novo documento pelo &ldquo;sha512sum&rdquo; e ver se o hash continua batendo. Se bater, temos certeza que o documento está intacto e não foi adulterado. Porém, se o número for diferente, sabemos com certeza que foi adulterado e não pode ser confiado. Não sabemos onde foi adulterado, mas sabemos que foi. Lembram do episódio anterior sobre recuperação de dados? É assim que um sistema de arquivos como ZFS ou BTRFS sabe que um arquivo pode estar corrompido.</p>
<p>Outro exemplo. Vou no site da minha distro favorita, Manjaro e vou baixar a ISO pra instalar. É um arquivão de 3.7 gigabytes. Digamos que eu fiz o download numa rede pública, tipo num aeroporto ou num Starbucks. Como posso garantir que ninguém interceptou minha requisição e me mandou um arquivão falso? Daí quando eu for instalar, na verdade vai instalar um malware?</p>
<p>Chegando numa rede confiável, como minha casa ou escritório, posso baixar esse arquivo de checksum, com extensão .iso.sha512. Agora posso rodar o seguinte comando. Sha512sum passando esse arquivo de SHA e o ISO baixado.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sha512sum -c manjaro-gnome-22.1.3-230529-linux61.iso.sha512
manjaro-gnome-22.1.3-230529-linux61.iso: OK</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 devolver &ldquo;OK&rdquo; é porque tá tudo bem, mas o que ele fez? Vamos abrir o arquivo .iso.sha512. É um numerozão no formado da saída do sha512sum. A empresa que faz o Manjaro, que criou o arquivo de instalação original, gerou o sha512 e gravou nesse arquivo pra gente.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat manjaro-gnome-22.1.3-230529-linux61.iso.sha512
78b8fa6a5222bc10a2e767485e76f82f3c7b2b59f4a70501b4a9fb846c5ad94987cf8b8ed630a701a8a3386debc0a39fc7363a743df8f13f41d3d8324d74e227  manjaro-gnome-22.1.3-230529-linux61.iso</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>Podemos simplesmente calcular o sha512 em cima do ISO de 3.7 gigabytes com o comando &ldquo;sha512sum&rdquo;, igual fizemos com o arquivo do Hello World e olha só, gerou exatamente o mesmo hash. Com isso sabemos que nenhum bit dos 3.7 gigabytes veio adulterado. Se um único bit tivesse sido corrompido no download, o hash gerado seria completamente diferente. Com isso sabemos que o ISO é válido e podemos instalar.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sha512sum manjaro-gnome-22.1.3-230529-linux61.iso
78b8fa6a5222bc10a2e767485e76f82f3c7b2b59f4a70501b4a9fb846c5ad94987cf8b8ed630a701a8a3386debc0a39fc7363a743df8f13f41d3d8324d74e227  manjaro-gnome-22.1.3-230529-linux61.iso</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>Lembra dos meus videos sobre Git? Esse stringzão não parece com o identificador de um commit? Só que no caso de commit é mais curto. Isso porque Git usa SHA1 em vez de SHA256. SHA1, assim como MD5, também não é mais pra ser usado, mas é difícil migrar pra SHA256 sem quebrar compatibilidade e gerar confusão na cabeça de todo mundo, por isso não usamos SHA256 ainda, mas uma hora Git também deve mudar.</p>
<p>A razão de não usarmos mais MD5 é porque descobriram um bug no seu processo que torna razoavelmente fácil gerar colisões. Ou seja, se modificarmos um determinado documento, cuidadosamente, nos bits certos, temos como mudar o conteúdo e no final o MD5 vai gerar exatamente o mesmo hash. Aí perde o propósito. Descobriram que dá pra fazer isso com SHA1 também, mas não é tão trivial, mesmo assim não é impossível. Não é prático, mas é estatisticamente possível, e isso é suficiente pra considerarmos parar de usar.</p>
<p>Dois conteúdos diferentes que geram o mesmo hash, chamamos de &ldquo;colisão&rdquo; e mais cedo ou mais tarde, colisões acontecem. É estatisticamente inevitável, mas é muito difícil de controlar a colisão pra acontecer exatamente quando queremos num SHA256 ou outra função melhor. Mas MD5 e SHA1 já sabemos como controlar as colisões, adicionar bits extras que dá pra ignorar, só pro arquivo no final conseguir chegar no mesmo hash.</p>
<p>Eu explico no video de criptografia sobre hashes usados pra não guardar senhas abertas num banco de dados, assistam depois porque é importante saber disso. Mas caso não saiba, é considerado certificado de estupidez gravar senha dos usuários aberto numa tabela no seu banco de dados. Também é uma idiotice achar que é esperto e gerar o hash da senha e gravar numa tabela. Precisa de salt. E tem algoritmos melhores do que outros. Assista depois pra entender.</p>
<p>O principal é o seguinte: quando um determinado conteúdo, um arquivo ou documento, tem conteúdo secreto, o certo é encriptar o arquivo inteiro usando um algoritmo como AES com uma chave bem longa e forte. Mas pra diversos usos, só pra identificar se foi ou não adulterado, como um contrato, não precisa encriptar o arquivo todo, basta encriptar a impressão digital, que vai ser mais curta. Um exemplo disso é o download da ISO do site da Manjaro.</p>
<p>É um desperdício encriptar uma ISO inteira, não tem nada secreto dentro. É um arquivo público que qualquer um pode baixar e instalar de graça. O problema é saber se a ISO baixada não foi adulterada no meio do caminho. Pra isso tem coisas como o arquivo &ldquo;.iso.sha512&rdquo;. Mas tem um outro arquivo no site deles também, olha só, eles chamam de assinatura e é um arquivo &ldquo;.iso.sig&rdquo;. Não dá pra listar porque é um arquivo binário, mas é a assinatura do hash da ISO.</p>
<p>O problema de um arquivo .sha512 é que se baixamos os dois ao mesmo tempo, se o ISO foi adulterado no caminho, certamente o arquivo .sha512 também foi adulterado. Por isso, no exemplo, de propósito eu disse que o cenário é que baixei a ISO do aeroporto mas depois baixei o arquivo .sha512 quando cheguei em casa, numa rede confiável. Mas não precisamos confiar na rede pra checar essas coisas.</p>
<p>Antes de sair de casa, o certo é já ter instalado o pacote &ldquo;GNUPG&rdquo; o famoso GPG, que é implementação do padrão OpenPGP, de Pretty Good Privacy. É outro pacote que faz uso do conceito de chaves assimétricas, como no exemplo de TLS com RSA ou curva elíptica, lembra? Chaves pública e privada? Só que no exemplo anterior estávamos lidando com certificados .pem pra servidores web. GPG é similar, mas pra identificar pessoas, e não servidores.</p>
<p>Tendo o GPG instalado, no meu caso, que uso Manjaro, eu já teria que ter baixado as chaves GPG da equipe do Manjaro. Só fazer o download do arquivo com &ldquo;wget&rdquo; e instalar com &ldquo;gpg&rdquo; assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>wget gitlab.manjaro.org/packages/core/manjaro-keyring/-/raw/master/manjaro.gpg

gpg --import manjaro.gpg</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>Lembra o conceito que o sistema operacional ou o navegador já trazem certificados raiz de autoridades certificadoras? Aqueles arquivos em &ldquo;/etc/ssl/certs&rdquo;? O conceito é parecido. Esse arquivo &ldquo;manjaro.gpg&rdquo; tem as chaves confiáveis da equipe do Manjaro. Tudo que a equipe de lá assinar, temos como checar agora.</p>
<p>Eu fiz isso em casa, agora estou no aeroporto e resolvi baixar a ISO do Manjaro. Uma rede pública é o oposto de confiável. Por isso recomendamos sempre usar VPN. Mas digamos que não estou numa VPN e ainda assim quero me arriscar a baixar a ISO. Como eu sei que não veio adulterado? Algum hacker esperto subiu um servidor do note dele, adulterou o DNS do roteador do aeroporto, e agora todo download que eu faço na verdade é um malware?</p>
<p>Baixamos também o arquivo &ldquo;.iso.sig&rdquo; e podemos checar com o seguinte comando. GPG verify o arquivo .sig e o arquivo ISO.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gpg --verify manjaro-gnome-22.1.3-230529-linux61.iso.sig manjaro-gnome-22.1.3-230529-linux61.iso
gpg: Signature made seg 29 mai 2023 06:38:26 -03
gpg:                using RSA key 3B794DE6D4320FCE594F4171279E7CF5D8D56EC8
gpg: Good signature from &#34;Manjaro Build Server &lt;build@manjaro.org&gt;&#34; [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 3B79 4DE6 D432 0FCE 594F  4171 279E 7CF5 D8D5 6EC8</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>Dentro desse arquivo de assinatura tem o hashing do ISO, assinado pelo Manjaro Build Server. Ele checa o hashing e diz &ldquo;Good Signature&rdquo;, então o binário do ISO pelo menos bate com o hashing dentro da assinatura. Mas aí tem um bugzinho que pelo jeito existe faz alguns anos no Manjaro: a chave desse servidor de build não pôde ser confirmado. Sei lá porque não vem já pré-instalado como deveria e quando vai checar dá esse aviso. O certo era ter o certificado raiz desse <a href="mailto:build@manjaro.org">build@manjaro.org</a> já no meu sistema, mas pelo visto não tem. Bug do Manjaro que até hoje não foi corrigido. Se isso mudou, mandem nos comentários abaixo.</p>
<p>O ponto todo desse exercício foi pra vocês terem na cabeça o que é um hash, uma impressão digital e uma assinatura digital. Deixa eu expandir o exemplo do download da ISO pra algo que muitos de vocês já devem ter usado pelo menos uma vez na vida: BitTorrent. De novo, objetivo do video não é ser uma explicação detalhada sobre BitTorrent, então procurem a documentação depois. Eu quero que vocês consigam entender os conceitos de criptografia que ele usa e como eles resolvem o problema de arquivos distribuídos.</p>
<p>Vamos lá, no próprio site do Manjaro, posso baixar um arquivo .torrent. Um download normal, que é o link do lado, vai puxar os 3.7 gigabytes direto do servidor da empresa do Manjaro. Mas com esse arquivo .torrent, a expectativa é conseguir baixar até mais rápido ou com mais estabilidade, porque vou baixar pedaços da ISO de diversos peers diferentes pela internet. Todo mundo que baixou a ISO e deixou o programa de BitTorrent ativo, está compartilhando o mesmo arquivo. Eu posso baixar pedaços em paralelo de várias pessoas diferentes. Essa é a idéia básica de BitTorrent.</p>
<p>Tangente de história. Nos anos 90 a internet era muito mais lenta que hoje, conexões de modem discado. No mundo dos servidores era tudo super imaturo. Ninguém sabia como manter servidores de download decentes, além de banda ser caro. Era terrível, e no meio disso surgiram os primeiros serviços de compartilhamento de arquivos, vulgo pirataria, como o lendário Napster, seguidos de copycats nos anos 2000 como Limewire, Kazaa, eDonkey e outros.</p>
<p>Mas o princípio na época era só um jeito de dizer pra outras pessoas quais arquivos tem na sua máquina pra compartilhar e deixar todo mundo baixar livremente. É o modelo mais simples de compartilhamento de arquivos. Foram anos de controvérsia, diversos processos da indústria de música, até culminar no surgimento dos primeiros serviços comerciais de venda de música e depois de assinatura como iTunes e hoje Spotify.</p>
<p>O grande defeito desse modelo simples de compartilhamento, é que era muito fácil identificar quem tinha os arquivos e ir processar direto essa pessoa, derrubando o compartilhamento. Uma vez que desliga a máquina do cara, somem os arquivos e ninguém baixa mais. Esse era o problema a ser resolvido. O problema ainda não foi totalmente resolvido, mas vamos ver como BitTorrent melhorou radicalmente nossa situação.</p>
<p>Primeiro de tudo, precisamos acabar com o conceito de um servidor único, centralizado de arquivos, de onde todo mundo puxa. É o mesmo problema do servidor de ISOs da Manjaro. Se todo mundo vai no mesmo lugar pra baixar, quanto mais gente descobrir o servidor, mais lento vai ficar pra todo mundo, e maiores os riscos desse servidor cair ou ser derrubado e todo mundo ficar sem nada.</p>
<p>Lembra da minha série sobre armazenamento, sistemas de arquivos e tudo mais? O que é um arquivo? É só um linguição de bits. Por baixo dos panos, eles são organizados como blocos de bits. Podemos picotar um arquivo como quiser e depois remontar. Inclusive é assim que eles são gravados em HDs e SSDs: fora de ordem. Vamos simular esse exercício num terminal de Linux só pra terem essa imagem na cabeça.</p>
<p>Podemos fazer isso com qualquer arquivo, seja texto ou binário, já expliquei que pro computador não faz diferença nenhuma, por baixo é tudo um linguição de bits. Só pra nós humanos que faz diferença porque arquivos texto conseguimos abrir e ler. De exemplo, peguei o script do episódio 142 que foi a explicação sobre ChatGPT. Esse script tem 94 kilobytes, ou seja, 94 mil letras. Quero conseguir dividir isso em arquivos iguais de exatamente 10 kilobytes cada, pra simular blocos de 10 kilobytes.</p>
<p>Pra isso usamos o comando &ldquo;split&rdquo; que tem em Linux. Só passar o tamanho de cada pedaço, o arquivo original e o prefixo do nome dos pedaços, que vou chamar de chunk.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>split -b 10K subtitle_142.txt chunk_

❯ ls -la
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_aa
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ab
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ac
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ad
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ae
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_af
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ag
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ah
.rw-r--r--  10k akitaonrails 18 jul 13:22  chunk_ai
.rw-r--r-- 1,5k akitaonrails 18 jul 13:22  chunk_aj
.rw-r-----  94k akitaonrails 18 jul 13:21  subtitle_142.txt</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, obviamente, só o último arquivo vai ser menos que 10 kilobytes, mas vejam como consegui dividir direitinho. Posso tentar dar cat em qualquer pedaço e vai vir o trecho do script. Quebrar arquivos grandes em pedaços menores era algo que fazíamos o tempo todo nos anos 90 porque era muito arriscado tentar fazer download de um arquivo gigante. O modem podia desligar no meio do caminho, algoritmos de checagem de corrupção ainda não eram difundidos, então podia vir sujeira na linha e o arquivo ser corrompido. Se viesse corrompido, precisava mandar baixar tudo de novo do zero.</p>
<p>Então dividíamos o risco. Era melhor deixar agendado pra ir baixando um pedaço de cada vez. Se alguma coisa desse errado, só um pedaço ia ser corrompido e dava pra baixar só aquele pedaço separado depois, em vez de ter que recomeçar do zero. Hoje em dia o próprio protocolo HTTP suporta resumir um download pela metade e o protocolo TCP garante que os pacotes não vão vir corrompidos, como já expliquei na série de redes. Mas estou falando de uma era antes dessas facilidades.</p>
<p>E agora vocês já sabem. Uma forma de manualmente checar se meus pedaços vieram corretamente seria ter um arquivo com o hash de cada pedaço. Assim podemos comparar o hash do download e ver se nada foi corrompido. Lembra? Como estou falando de anos 90, poderíamos fazer o SHA1 de cada pedaço. Por exemplo, eis o hash do primeiro pedaço, com o comando sha1sum:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sha1sum chunk_aa
c3bb4df901fb79af2421cc274e9db87309bf6424  chunk_aa</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 um pouco de mágica de script de shell, podemos fazer a mesma coisa pra todos os pedaços e colocar num arquivo &ldquo;index.txt&rdquo; da seguinte forma, fazendo, pra cada file, ou arquivo, que começa com &ldquo;chunk&rdquo;, gere o sha1 e vá adicionando nesse arquivo index.txt.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>for file in chunk_*; do
    sha1=$(sha1sum &#34;$file&#34; | awk &#39;{print $1}&#39;)
    echo &#34;$file $sha1&#34; &gt;&gt; index.txt
done


/tmp/subtitle 
❯ cat index.txt 
chunk_aa c3bb4df901fb79af2421cc274e9db87309bf6424
chunk_ab 79a5c1fb9a9708982a92009d874bd9ee3db65a31
chunk_ac 84de6f8eb80fc64628514500b8d6b09ff930bda5
chunk_ad 2f68d9f1caffdfca69b28ae33348cdd0ca467474
chunk_ae c644daae2adcc6c87b8b4ee91052535c50edbe4f
chunk_af de3471b65337f2ffb2d97bbd6ef45b1e95d6ba88
chunk_ag 454093fda498e7f67cacb0919e3a5ffa579d0cb6
chunk_ah a0d040c39213188382dc681d7a74da283bfb07c0
chunk_ai 0469b41afb556f10c01cca5f099a81e6b5e17a31
chunk_aj 45fa07b4fdfea4a0a5c02abcad6520b652264a25</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 isso é interessante aprender sobre scripts de shell. Abrindo o arquivo &ldquo;index.txt&rdquo; com &ldquo;cat&rdquo; temos o nome do arquivo de cada pedaço seguido do seu hash SHA1. Olhem como o sha1 do primeiro arquivo bate com o que geramos na mão antes. Então está correto. Além disso, esse arquivo me diz exatamente a totalidade dos pedaços que preciso baixar. Poderíamos fazer outro script pra baixar cada pedaço, checar o SHA1 e se vier errado, mandar refazer o download só daquele pedaço. Fica de exercício pra vocês fazerem isso.</p>
<p>Ok, fizemos o download de todos os pedaços. E agora? Como  remontamos o arquivo original? É simples, bastar dar &ldquo;cat&rdquo; de todos os pedaços e redirecionar pra um arquivo final, assim, cat chunk asterisco maior pra restored.txt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat chunk_* &gt; restored.txt

❯ sha512sum restored.txt   
bfec6c9efe398146b8197b7b0258860563f6057dac7f6d1931ec5d4118c730d47007678ddd2cf9387b5b02903c7dd35a1911a2da4707d624ce4e996514a766c6  restored.txt

/tmp/subtitle 
❯ sha512sum subtitle_142.txt 
bfec6c9efe398146b8197b7b0258860563f6057dac7f6d1931ec5d4118c730d47007678ddd2cf9387b5b02903c7dd35a1911a2da4707d624ce4e996514a766c6  subtitle_142.txt</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 funciona em shells modernos porque a maioria sempre lista os arquivos em ordem alfabética. Caso o seu não faça isso, precisaria primeiro usar o comando &ldquo;sort&rdquo;, de novo, fica de exercício. Mas assumindo que o cat pegou cada arquivo chunk na ordem certa, como sei que o arquivo restored.txt realmente é idêntico ao original? Vocês já sabem né? Só comparar o hash dos dois. E como podem ver, é exatamente igual. Se um único bit tivesse vindo errado, os hashes seriam diferentes.</p>
<p>Estão entendendo como podemos manipular qualquer tipo de dados e garantir a integridade deles via ferramentas criptográficas? Sabendo desses conceitos, vou fazer uma explicação super de alto nível pra explicar como BitTorrent tira proveito disso que acabei de explicar.</p>
<p>Existem duas formas de baixar um arquivo via Torrent. Um é baixando um arquivo .torrent, como aquele do site do Manjaro. Podemos usar o programa &ldquo;transmission-show&rdquo; que vem no pacote &ldquo;transmission-cli&rdquo; pra abrir e ver o conteúdo desse arquivo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ transmission-show manjaro-gnome-22.1.3-230529-linux61.iso.torrent 
Name: manjaro-gnome-22.1.3-230529-linux61.iso
File: manjaro-gnome-22.1.3-230529-linux61.iso.torrent

GENERAL

  Name: manjaro-gnome-22.1.3-230529-linux61.iso
  Hash v1: 68617f6ffc9f95bbca41733556f666739f647f9c
  Created by: mktorrent 1.1
  Created on: Mon May 29 06:38:30 2023

  Piece Count: 1769
  Piece Size: 2.00 MiB
  Total Size: 3.71 GB
  Privacy: Public torrent

TRACKERS

  Tier #1
  udp://tracker.opentrackr.org:1337

WEBSEEDS

  https://download.manjaro.org/gnome/22.1.3/manjaro-gnome-22.1.3-230529-linux61.iso

FILES

  manjaro-gnome-22.1.3-230529-linux61.iso (3.71 GB)</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 importante desse arquivo é que ele identifica o que vamos baixar, no caso o ISO, e onde podemos achar esse arquivo. Primeiro temos esse tal de tracker. Um tracker é um servidor que contém listas de peers que estão online e que tem o arquivo com este hash. Se tiver pelo menos 1 peer ativo que seja um seeder, vai começar a baixar. Peer é o nome que damos pra qualquer máquina, PC, servidor, participando dessa rede. Um tracker não necessariamente tem o arquivo ou seus pedaços, mas sabe quem pode ter, que são os peers.</p>
<p>Você já deve ter visto essa nomenclatura em programas de torrent como QBitTorrent ou Transmission. Quanto mais seeders tiver ativo, melhor será o download. Seeder são computadores online que também estão compartilhando o mesmo arquivo ou pedaços. Leechers são computadores online fazendo download mas não contribuindo em nada. Parasitando mesmo, leeching. O algoritmo do BitTorrent tenta ser justo e deixar download mais rápido pra quem contribui e download mais lento pra quem é só parasita.</p>
<p>Trackers só compartilham e sincronizam a lista de peers, peers podem ser tanto seeders quanto leechers. Quanto mais seeders, mais rápido o download, quanto mais leechers e menos seeders, mais lento o download. O mundo ideal é todo mundo ser seeder. Alguns são chatos e desligam mesmo, outros realmente não conseguem contribuir porque a rede do provedor bloqueia e eles não sabem como furar firewall. Eu falo de furar firewall na série de redes. Depois procurem saber mais como habilitar portas, usar VPN pra torrent e coisas assim, não vou falar disso hoje.</p>
<p>Vamos voltar pro arquivo .torrent. Olhem outras informações que ele nos dá. Sabemos que o arquivo ISO tem 3.7 gigabytes, como já repeti várias vezes, e sabemos que este torrent está organizado em mil setecentos e sessenta e nove pedaços de 2 megabytes cada. Lembra os chunks que fiz usando o comando split? É a mesma coisa. Ele não mostra aqui, mas dentro desse torrent vai ter uma lista de pedaços seguido do hash de cada pedaço, parecido com aquele arquivo &ldquo;index.txt&rdquo; que fiz no exemplo.</p>
<p>Cada seeder que o tracker conhece, vai ter ou o arquivo inteiro ou o arquivo parcial, porque ainda não terminou o download. Agora eu, que comecei o download agora, começo a baixar cada um desses pedaços de cada um dos seeders disponíveis, em paralelo. Por exemplo, digamos que na rede tenha o seeder John, a seeder Sara e o seeder Kyle. E eu vou baixar aquele meu arquivo de script. Começo a baixar o chunk_aa do John, o chunk_ab da Sara, o chunk_ac do Kyle e assim por diante.</p>
<p>Toda vez que um chunk termina de baixar, consigo checar o hash e sei que tá correto, daí peço o chunk seguinte, distribuindo a carga entre todos os seeders. Se outra pessoa aparecer pedindo o mesmo arquivo, talvez ele pegue um chunk de mim. Entenderam? O seeder não precisa ter o arquivo completo pra começar a contribuir, basta ter pelo menos um chunk completo. E por isso, quanto mais gente tiver, menos pesado fica pra todo mundo, especialmente pro servidor principal da Manjaro. É a base de computação distribuída.</p>
<p>Se não tiver nenhum seeder ativo, o arquivo .torrent aponta pra um Webseed, que é o link pro arquivo original no servidor da Manjaro. Daí seria como um download direto normal mesmo. Entenderam até aqui: com uma lista de pedaços e seus respectivos hashes, distribuídos entre diversos computadores num swarm, ou enxame, e cada um baixando um pedaço do computador do outro, até todo mundo ter o arquivo completo, essa é a arquitetura básica por trás do BitTorrent.</p>
<p>BitTorrent é &ldquo;decentralizado&rdquo; no sentido que, diferente de um iTunes da Apple, não tem uma única empresa nem um único servidor que centraliza os downloads. Eis alguns mecanismos que tornam o BitTorrent resiliente, embora não totalmente invulnerável.</p>
<p>De cara, poderíamos pensar que se derrubarmos quem mantém trackers, poderíamos parar um enxame, não? Primeiro, não existe somente um tracker, existem diversos trackers servindo um de backup pro outro. Precisaria derrubar todos os trackers em atividade no mundo. Mas isso não seria suficiente.</p>
<p>Toda vez que você abre um programa como Transmission ou Nano Torrent da vida, ele faz &ldquo;bootstrapping&rdquo; usando uma lista de &ldquo;bootstrapping nodes&rdquo;, uma lista de nós bem conhecidos na rede que acreditamos que costumam estar online, vários deles. São esses nós que nos ajudam a achar outros nós. Assim como trackers, eles também não são pontos únicos de falha.</p>
<p>Quando seu programa se conecta e acha um tracker ou um bootstrapping node, ele se junta à rede DHT ou Hash Table distribuído. Cada nó que entra na rede armazena informação sobre a rede toda ou um pedaço da rede. Então, quando você pergunta ao DHT por alguma coisa, seu cliente de BitTorrent manda uma requisição pra um nó que ele conhece. Esse nó ou vai ter a informação e te retornar ou vai te direcionar pra outro nó que pode saber essa informação. A partir desse ponto você, como nó da rede, passa a ter essa informação e pode responder a alguém que te perguntar também. Todo mundo se torna backup e contribui pra outras pessoas.</p>
<p>O conceito de trackers é um pouco obsoleto na realidade, das primeiras versões de BitTorrent. Ele veio evoluindo e agora temos o conceito de DHT e PEX que é Peer Exchange. Mesmo derrubando todos os trackers, cada pessoa conectada na rede contribui pra distribuir a informação do resto da rede. Hoje em dia os programas de BitTorrent implementam as duas maneiras.</p>
<p>Eu falei do arquivo .torrent que baixamos pra começar um download via BitTorrent, mas nem precisamos desse arquivo. Vocês já devem ter visto o que se chama de link magnético, que é tipo uma URL que usa o protocolo &ldquo;magnet&rdquo;. Lembram o arquivo .torrent da ISO do Manjaro que baixamos? Podemos gerar um link a partir dele usando o programa &ldquo;transmission-show&rdquo; com a opção &ldquo;-m&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ transmission-show -m /mnt/terachad/Downloads/manjaro-gnome-22.1.3-230529-linux61.iso.torrent
magnet:?xt=urn:btih:68617f6ffc9f95bbca41733556f666739f647f9c&amp;dn=manjaro-gnome-22.1.3-230529-linux61.iso&amp;tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&amp;ws=https%3A%2F%2Fdownload.manjaro.org%2Fgnome%2F22.1.3%2Fmanjaro-gnome-22.1.3-230529-linux61.iso</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 link que encontramos em diversos sites que listam torrents pra baixar. Veja num Piratebay da vida, onde tem esse ícone de ímã. Quando clica, automaticamente pede pra abrir seu programa de Torrent instalado. A parte mais importante é esse &ldquo;btih&rdquo; logo no começo que é o BitTorrent InfoHash, o hash que identifica unicamente esse arquivo de ISO. O resto ajuda mas é opcional. Por exemplo, ele tem o link pro webseed caso ninguém esteja oferecendo o arquivo na rede e também o link pra um tracker.</p>
<p>Vamos fazer um html idiota com o link pra esse magnet. Um link com a URL inteira que foi gerado e um segundo link só com o infohash.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;html&gt;
 &lt;body&gt;
  &lt;p&gt;&lt;a href=&#34;magnet:?xt=urn:btih:68617f6ffc9f95bbca41733556f666739f647f9c&amp;dn=manjaro-gnome-22.1.3-230529-linux61.iso&amp;tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&amp;ws=https%3A%2F%2Fdownload.manjaro.org%2Fgnome%2F22.1.3%2Fmanjaro-gnome-22.1.3-230529-linux61.iso&#34;&gt;Magnet pro ISO&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;a href=&#34;magnet:?xt=urn:btih:68617f6ffc9f95bbca41733556f666739f647f9c&#34;&gt;Magnet pro ISO (só infohash)&lt;/a&gt;&lt;/p&gt;
 &lt;/body&gt;
&lt;/html&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>Se abrir num navegador e clicar no link, o media type está associado ao QBitTorrent instalado no meu Manjaro. Ele abre e oferece pra começar a baixar. Se eu abrir pelo segundo link, mesmo sem informação de webseed e nem de tracker, só com o infohash, olha só, abre normalmente e oferece pra baixar igual. Isso porque o QBitTorrent na minha máquina se uniu à rede, via DHT e PEX e conseguiu achar os seeders e já sabe como começar a baixar.</p>
<p>Entenderam? Pra parar um arquivo de ser compartilhado, não basta derrubar todos os trackers, precisa parar 100% de todo mundo que está compartilhando o arquivo, de todas as partes do mundo, ao mesmo tempo. Se tiver só um nó na rede funcionando, dá pra continuar compartilhando, porque via DHT e PEX, essa informação vai voltar a ser distribuída. Computação distribuída é fascinante por causa disso: ninguém consegue parar somente com uma canetada sem comprometer a internet inteira de funcionar direito. Por isso eu gosto desse conceito.</p>
<p>DHT e PEX são assuntos fascinantes por si só, pra garantir a descentralização da rede, então espero que vocês pesquisem mais sobre isso depois. Ótimo tema pra TCC. Mas tem um assunto menos comentado que eu acho ainda mais interessante. É o conceito de Árvores de Hashes ou Merkle Trees, nome dado por causa do criador do conceito, Ralph Merkle, que inventou isso no fim dos anos 70.</p>
<p>Vamos lá. O que temos até agora é um arquivo &ldquo;index.txt&rdquo; com uma lista de chunks ou pedaços daquele meu arquivo de script. 10 pedaços com o hash SHA1 do lado de cada um, lembram? Numa versão rudimentar de BitTorrent, eu poderia ter baixado esse arquivo e perguntado na tabela distribuída de hashes, o tal DHT, de onde baixar cada pedaço, identificado pelo SHA1, que serve como um ID único pra cada pedaço.</p>
<p>Depois que fizer o download de cada pedaço, basta rodar o comando &ldquo;sha1sum&rdquo; e comparar o hash com o que tem no arquivo &ldquo;index.txt&rdquo;. Se bater é porque veio correto, sem corromper nem nada. No final, basta concatenar os pedaços como fiz antes, gerar o hash do arquivo todo, comparar com o hash mestre do arquivo original, pra garantir que tenho o mesmo arquivo no final. Parece simples e parece que só isso é o suficiente.</p>
<p>Mas no mundo real tem várias razões de porque isso não é suficiente. Pra começar, via BitTorrent podemos trafegar arquivos de qualquer tamanho gigante, posso compartilhar diretórios inteiros com milhares de arquivos. Eu poderia fazer um torrent do C: inteiro do meu Windows se quisesse. E posso puxar pedaços de diversos nós diferentes no enxame de computadores participando na rede. Pra validar o hash de tudo, dessa forma, não seria prático. Imagine ter que computar o hashzão do seu HD inteiro pra saber se o que fiz download tá correto. Seria muito pesado, em alguns casos até inviável.</p>
<p>Em programação, você sempre vai bater nesse caso: pra um arquivo pequeno, com poucos kilobytes ou megabytes de tamanho, o esquema simples de um arquivo de hashes é mais que suficiente. Mas pra escala de internet, onde podemos querer lidar com gigabytes ou terabytes, milhares de arquivos num mesmo torrent, não vai escalar.</p>
<p>Pense assim, temos 10 hashes, um pra cada pedaço, e temos 1 hash mestre, que valida o arquivo completo inteiro. Eu recebo esses hashes e começo a fazer download de algum nó. Veio esse primeiro pedaço chunk_aa, o hash começa com c3bb. Eu rodo o comando sha1sum e vejo que, sim, o hash começa com c3bb mesmo. Bacana. Isso só garante que o pedaço é quem ele diz que é, mas como eu sei que de fato esse pedaço faz parte do arquivão inteiro?</p>
<p>A única forma é baixar todos os 10 pedaços, concatenar tudo, rodar o &ldquo;sha1sum&rdquo; pro arquivo gerado e comparar com o hash mestre. Só assim vou saber que todos os pedaços são realmente válidos pra esse arquivo, entenderam? Os hashes individuais não me dizem nada sobre o arquivo inteiro original. Eu tenho que confiar que o nó da rede, que me deu essa informação, não mentiu pra mim. Imagina se fosse um torrent como falei antes, com vários arquivos e muitos gigabytes. Eu só ia ter certeza que todos são válidos depois que baixar tudo.</p>
<p>Em BitTorrent, vocês já devem ter visto que podemos escolher baixar somente alguns dos arquivos e não todos. Então, de cara, esse esquema de só ter um hash mestre pra tudo não funciona. Eu preciso de hashes parciais, no mínimo um pra cada arquivo separado, não? E mesmo assim, preciso garantir que esse arquivo, uma vez que baixei os pedaços e montei, de fato faz parte do conjunto todo, sem ter que baixar o conjunto todo. Como faz?</p>
<p>Pra simplificar, vamos partir de novo do exemplo de 10 pedaços. Imagine que isso poderia ser um arquivo de milhares de pedaços e dezenas de arquivos em sub-diretórios. É uma simplificação, ok? Pois bem, começo organizando esses pedaços em pares. chunk_aa com chunk_ab, depois chunk_ac com chunk_ad e assim por diante. Pares de pedaços.</p>
<p>Agora pegamos o hash de cada par e concatenamos só eles. Por exemplo, o hash c3bb do chunk_aa com o hash 79a4 do chunk_ab. E gero o hash dessa combinação. Vai dar um hash que começa com &ldquo;0285&rdquo;. Faço a mesma coisa com os hashes do próximo par, vai dar um novo hash que começa com &ldquo;84de&rdquo; e continuo fazendo isso pros outros pares.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/tmp/subtitle 
❯ echo &#34;c3bb4df901fb79af2421cc274e9db87309bf642479a5c1fb9a9708982a92009d874bd9ee3db65a31&#34; | sha1sum
0285f4ec89c694112c330ec20a571457a4e159f2  -

/tmp/subtitle 
❯ echo &#34;84de6f8eb80fc64628514500b8d6b09ff930bda52f68d9f1caffdfca69b28ae33348cdd0ca467474&#34; | sha1sum
05e420886ad96877bd78051df8a01c4a9414cc33  -

/tmp/subtitle 
❯ echo &#34;c644daae2adcc6c87b8b4ee91052535c50edbe4fde3471b65337f2ffb2d97bbd6ef45b1e95d6ba88&#34; | sha1sum
d9e3a713ccd57d87adb4ea04aede0cebdcc8fd8e  -

/tmp/subtitle 
❯ echo &#34;454093fda498e7f67cacb0919e3a5ffa579d0cb6a0d040c39213188382dc681d7a74da283bfb07c0&#34; | sha1sum
4c6b2637222b6a7cc9e52a3d5441dedcdf00b4e6  -

/tmp/subtitle 
❯ echo &#34;0469b41afb556f10c01cca5f099a81e6b5e17a3145fa07b4fdfea4a0a5c02abcad6520b652264a25&#34; | sha1sum
65c704fefd21e50f92df28de813ef2d3b4d7cff4  -</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>0285f4ec89c694112c330ec20a571457a4e159f2
05e420886ad96877bd78051df8a01c4a9414cc33
d9e3a713ccd57d87adb4ea04aede0cebdcc8fd8e
4c6b2637222b6a7cc9e52a3d5441dedcdf00b4e6
65c704fefd21e50f92df28de813ef2d3b4d7cff4</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>No final vou ter 5 novos hashes. Agora repito o mesmo processo. Organizo pares de hashes, concateno e gero um novo hash pra cada par. Pego esse hash que começa com &ldquo;0285&rdquo; com o hash que começa com &ldquo;05e4&rdquo; e gero o hash que começa com &ldquo;9361&rdquo;. Faço isso pro segundo par. Só no terceiro par que vou ter problemas porque o último hash vai ficar sozinho. Não tem problema, gero o hash dele também, concatenado com vazio mesmo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/tmp/subtitle 
❯ echo &#34;0285f4ec89c694112c330ec20a571457a4e159f205e420886ad96877bd78051df8a01c4a9414cc33&#34; | sha1sum
9361c518dbcc3374c2d9a6c343dfa903f5226219  -

/tmp/subtitle 
❯ echo &#34;d9e3a713ccd57d87adb4ea04aede0cebdcc8fd8e4c6b2637222b6a7cc9e52a3d5441dedcdf00b4e6&#34; | sha1sum
910ec073793aafe29790941efe004c17debdc2d9  -

/tmp/subtitle 
❯ echo &#34;65c704fefd21e50f92df28de813ef2d3b4d7cff4&#34; | sha1sum
2924e53ff12eae61d88da7a0cca2967150881598  -</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>Vamos repetir mais uma vez. Par de hashes &ldquo;9361&rdquo; com &ldquo;910e&rdquo; vai dar &ldquo;bdf3&rdquo; e o último hash é &ldquo;2354&rdquo;. E pronto. Chegamos ao final, temos só dois hashes resultantes. Geramos o hash da concatenação desses dois também. Vai dar um hash final que começa com &ldquo;f2fb&rdquo;. Esse hash é especial, é o que chamamos de Merkle Root ou Raiz Merkle. É o hash que engloba todos os outros hashes.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/tmp/subtitle 
❯ echo &#34;9361c518dbcc3374c2d9a6c343dfa903f5226219910ec073793aafe29790941efe004c17debdc2d9&#34; | sha1sum
bdf3037c43dbe2cb7bf318a38948b3a504bea5d7  -

/tmp/subtitle 
❯ echo &#34;2924e53ff12eae61d88da7a0cca2967150881598&#34; | sha1sum
2354ddf1e76303140524dad59d5c2c3f418d87b7  -



/tmp/subtitle 
❯ echo &#34;bdf3037c43dbe2cb7bf318a38948b3a504bea5d72354ddf1e76303140524dad59d5c2c3f418d87b7&#34; | sha1sum
f2fb982d0f416376d64a7b451fa2d2176bf45ca8  -</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>chunk_aa c3bb
chunk_ab 79a5  0285
chunk_ac 84de
chunk_ad 2f68  05e4  9361
chunk_ae c644
chunk_af de34  d9e3
chunk_ag 4540
chunk_ah a0d0  4c6b  910e  bdf3
chunk_ai 0469
chunk_aj 45fa   65c7  2924  2354  f2fb</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>Antes, se eu só tivesse o hash mestre, precisaria de todos os 10 hashes de cada pedaço pra saber se tá tudo correto. Mas nesse esquema da árvore de Merkle, digamos que eu queira saber se o pedaço &ldquo;aa&rdquo; que acabei de baixar realmente faz parte do arquivo final, sem baixar os outros pedaços, como eu faço? Peço pro servidor somente os hashes do irmão &ldquo;ab&rdquo;, com isso consigo calcular &ldquo;aa&rdquo; e &ldquo;ab&rdquo; como fiz antes. Daí preciso que o servidor me mande os 4 hashes &ldquo;acad&rdquo;, &ldquo;aeaf&rdquo;, &ldquo;agah&rdquo;, &ldquo;aiaj&rdquo;.</p>
<p>Tendo somente esses 4 hashes, mais o hash do pedaço &ldquo;aa&rdquo;, consigo recalcular a árvore e achar a raiz Merkle e comparar com o que o servidor me indicou. Se bater, quer dizer que esse pedaço que baixei faz parte do arquivo final. Repetindo: eu diminuí a necessidade de precisar de todos os 10 hashes pra somente 4 hashes, sacaram?</p>
<p>De novo, é um exemplo super pequeno e simples só pra caber aqui na tela. Mas veja aquele exemplo da ISO do Manjaro, lembram? Eram quase mil e oitocentos pedaços. Se eu baixar só um pedaço, pra garantir que ele faz realmente parte do ISO original, precisaria calcular o hash mestre usando todos os quase mil e oitocentos hashes. Mas organizado numa árvore, precisaria só de uns log2(2048) que dá 11.</p>
<p>A árvore precisa ser balanceada, e o próximo número acima de mil e oitocentos que balanceia uma árvore binária é 2048. Enfim, eu preciso que o servidor me mande a raiz Merkle que ele tem pré-calculado e 11 hashes dessa árvore, mais ou menos. Não é um número exato esse log. Mas entenda que 11 é uma ordem de grandeza menos hashes do que mil e oitocentos. Mesmo nesse exemplo simples de só uma ISO de Manjaro, já temos um ganho significativo.</p>
<p>BitTorrent não foi criado com árvores Merkle, isso é uma coisa mais recente. Da mesma forma que hoje não precisaria de trackers por causa de DHT e PEX. É uma tecnologia ainda em evolução. Lembram do que eu falei no video sobre bancos de dados: árvores são as estruturas de dados mais importantes da computação, tudo são árvores. Nesse caso, árvores binárias, mas já falei de B-trees ou B+Trees em sistemas de arquivos. Assistam meu video de Árvores depois.</p>
<p>Sabe que outro programa se beneficia de árvores Merkle? Git. O que são commits? São diffs com trechos de arquivos de código fonte, mais metadados, como nome do arquivo, autor da modificação, datas. Uma característica importante de um commit é que ele é identificado por um hash, um SHA1 do seu conteúdo. É como sabemos que o conteúdo não foi adulterado, da mesma forma como fizemos pra checar os chunks do download via BitTorrent.</p>
<p>Mas um commit de Git tem outra característica importante: ele contém o SHA1 do commit pai anterior. Por isso commits formam uma cadeia, mais especificamente, uma árvore. Objetos de árvore de diretórios também tem SHA1 dos objetos filho. Tudo isso forma um DAG, um direct acyclic graph ou grafo direcionado não cíclico, mais especificamente um Merkle DAG.</p>
<p>Uma característica importante é que como todo commit contém o SHA1 do commit anterior, é impossível modificar só um commit no meio da árvore. Precisaria reescrever todos os commits na frente e regerar SHA1 novos pra todos. É o que chamamos de &ldquo;reescrever a história&rdquo;. Vocês já devem ter ouvido falar disso em discussões se é melhor fazer merge ou rebase e todo mundo é contra fazer rebases na master porque isso &ldquo;reescreve a história&rdquo;. É isso que significa reescrever a história: tem que regerar o SHA1 de todos os commits pra frente.</p>
<p>Deixa eu mostrar rapidamente com um exemplo. Vamos iniciar um novo repositório com &ldquo;git init&rdquo; e criar um arquivo de teste idiota só pra criar o primeiro commit.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ git init
hint: Using &#39;master&#39; as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint:  git config --global init.defaultBranch &lt;name&gt;
hint: 
hint: Names commonly chosen instead of &#39;master&#39; are &#39;main&#39;, &#39;trunk&#39; and
hint: &#39;development&#39;. The just-created branch can be renamed via this command:
hint: 
hint:  git branch -m &lt;name&gt;
Initialized empty Git repository in /tmp/teste/.git/



teste on  master 
❯ echo &#34;hello world&#34; &gt; hello.txt

teste on  master [?] 
❯ git a .

teste on  master [&#43;] 
❯ git c &#34;initial commit&#34;
[master (root-commit) 308ba8d] initial commit
 1 file changed, 1 insertion(&#43;)
 create mode 100644 hello.txt</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>Fazendo &ldquo;git log&rdquo; vemos o commit identificado pelo hash que começa com &ldquo;4b4c&rdquo;. Agora vamos criar um novo branch chamado &ldquo;teste&rdquo;, modificar o arquivo e criar um novo commit.
No caso criou um novo commit identificado pelo hash que começa com &ldquo;65c2&rdquo;. (no exemplo abaixo está com outro hash porque refiz o exemplo e a mudança de data gera hashes diferentes)</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>commit 1eb601ac03c713abaa08fd54e002eec04ee66922 (HEAD -&gt; teste)
Author: AkitaOnRails &lt;fabioakita@gmail.com&gt;
Date:   Thu Aug 24 11:54:32 2023 -0300

    adding hello again

commit 308ba8d7ee9ac84c6b318393614203e2b905f0f4 (master)
Author: AkitaOnRails &lt;fabioakita@gmail.com&gt;
Date:   Thu Aug 24 11:53:28 2023 -0300

    initial commit</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 vou voltar pro branch master, criar um novo arquivo qualquer e gravar em outro commit. Veja só, criou esse commit com hash que começa com &ldquo;9431&rdquo;. Agora, em vez de merge vou fazer rebase do branch teste em cima do branch master com &ldquo;git rebase teste&rdquo;. Como não fiz nada demais, vai terminar sem nenhum conflito.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>commit 921bb4796a2545fabc87324b5a8fa069d53201f2 (HEAD -&gt; master)
Author: AkitaOnRails &lt;fabioakita@gmail.com&gt;
Date:   Thu Aug 24 11:55:16 2023 -0300

    adding foo

commit 1eb601ac03c713abaa08fd54e002eec04ee66922 (teste)
Author: AkitaOnRails &lt;fabioakita@gmail.com&gt;
Date:   Thu Aug 24 11:54:32 2023 -0300

    adding hello again

commit 308ba8d7ee9ac84c6b318393614203e2b905f0f4
Author: AkitaOnRails &lt;fabioakita@gmail.com&gt;
Date:   Thu Aug 24 11:53:28 2023 -0300

    initial commit</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>Fazemos &ldquo;git log&rdquo; e olha só, o commit que eu tinha acabado de fazer, que tinha o hash começando em &ldquo;9431&rdquo; agora começa com &ldquo;ee00&rdquo;. Isso porque antes, o commit anterior era o &ldquo;initial commit&rdquo; com hash &ldquo;4b4c&rdquo;. Como fiz rebase, ele trouxe o commit &ldquo;adding hello again&rdquo; de hash &ldquo;65c2&rdquo;, que passa a ser o novo pai do commit &ldquo;adding teste&rdquo;, portanto o hash desse commit tem que ser recalculado e por isso mudou pra &ldquo;ee00&rdquo;, ou seja, acabamos de reescrever a história.</p>
<p>Esse conceito é muito importante. Ao colocar o SHA1 do commit anterior no commit atual, criamos uma dependência entre esses dois objetos. Se o objeto anterior mudar, se seu hash mudar, o hash do objeto seguinte vai ser invalidado e precisa ser recalculado também. Não dá pra simplesmente entuchar um commit no meio da cadeia sem consequências e é isso que garante a integridade do código fonte armazenado nesse repositório.</p>
<p>Se alguém tentar adulterar um commit lá atrás no passado, ele vai ser obrigado a fazer rebase de tudo pra frente, ou seja, recalcular o SHA1 de todo mundo. Daí todo mundo vai imediatamente saber que o repositório foi modificado, porque os IDs de tudo vão estar diferentes. É onde temos que lidar com conflitos depois. Se você nunca parou pra entender como o Git realmente funciona, recomendo que assista meu video de &ldquo;Entendendo Git&rdquo;.</p>
<p>Entenda, ter um Merkle Tree não impede que um commit seja adulterado. Só torna mais fácil de identificar quando for. E torna mais barato fazer os cálculos pra chegar nessa conclusão, do que ter que checar o SHA1 de 100% de tudo. No caso de Git, que é um repositório decentralizado, digamos que era uma equipe de 5 pessoas. Uma delas faz pull do GitHub e nota que alguém fez merda e deu &ldquo;push &ndash;force&rdquo; de um rebase gigante que reescreveu centenas de commits.</p>
<p>Você consegue ver que isso aconteceu e consegue ligar pra um dos outros colegas que ainda não fez &ldquo;pull&rdquo; e pedir pra ele dar um novo &ldquo;push &ndash;force&rdquo; pra recuperar o histórico como era antes e eliminar a cagada. O fato de termos várias cópias do mesmo repositório nas máquinas de todo mundo é um tipo de backup. Se o repositório elegido como principal ficou zoado, tem formas de recuperar. E não precisa sobrescrever tudo, só a partir do commit onde deu rebase, entenderam?</p>
<p>Existem várias discussões sobre centralizadores de Git como GitHub ou GitLab. Eles centralizaram a confiança de Git neles, mas Git originalmente não era pra ter um ponto único de falha. Você pode fazer git clone do repositório de alguém localmente, num pendrive, como mostrei no episódio anterior. Não precisa obrigatoriamente ter que fazer git clone de um repositório do GitHub. Isso é outra discussão, só pra vocês saberem que Git também foi feito pra ser distribuído, mas hoje em dia usamos centralizado num GitHub da vida.</p>
<p>Voltando pro exemplo de BitTorrent. Como qualquer computador na internet pode participar do compartilhamento de arquivos, sim, algum computador malicioso poderia começar a tentar espalhar binários adulterados. Mas o principal é o arquivo .torrent ou link magnético que aponta pra algum nó que normalmente confiamos, que vai nos fornecer a árvore Merkle com os hashes. Tendo os hashes, conseguimos checar se os pedaços que baixamos são válidos ou não.</p>
<p>Esses hashes são organizados numa tabelona, mais especificamente numa tabela distribuída, ou Distributed Hash Table, o DHT que eu fico falando. Pra quem é desenvolvedor web, é parecido com um DynamoDB. Cada item nessa tabelona ganha um ID longo de 160 bits. E os possíveis IDs são particionados entre os diversos nós da rede, de tal forma que cada nó só é responsável por um pedaço do espaço de IDs possíveis.</p>
<p>Toda vez que algum nó precisa registrar alguma coisa, ele gera uma chave e com isso determina qual nó que é responsável pelo espaço de IDs que essa nova chave faz parte, e manda pra esse nó. Mesma coisa pra pesquisar dados na tabela, tendo o ID pra consultar, ele pergunta pro nó responsável por esse intervalo de IDs.</p>
<p>Uma rede de BitTorrent é gigantesca, milhares ou milhões de nós ao mesmo tempo. É impossível meu computador saber quais são todos os nós ativos neste exato momento no mundo. Em vez disso, cada nó mantém um tipo de tabela de roteamento com informações sobre um sub-conjunto dos nós na rede, preferencialmente os mais &ldquo;próximos&rdquo; do espaço de IDs. Quando alguém pede pro seu nó gravar ou ler um certo ID mas você não é o responsável, dá pra usar essa tabela de roteamento pra redirecionar pro nó certo.</p>
<p>E claro, um nó pode simplesmente desaparecer do nada. Como cada nó pode ser um computador caseiro, alguém pode desligar o PC e sair da rede. Por isso essas informações são redundantes entre vários nós. Os algoritmos de DHT e roteamento vão se balanceando e atualizando dinamicamente com nós entrando e nós saindo.</p>
<p>Se isso pareceu similar a um banco de dados NoSQL distribuído como Amazon DynamoDB ou Cassandra, é porque é. Uns três ou quatro episódios atrás eu expliquei como funcionam chaves distribuídas, depois assistam. BitTorrent não deixa de ser um grande banco de dados NoSQL distribuído. São tecnologias similares. Toda vez que você abre um programa como Transmission ou QBitTorrent pra baixar alguma coisa, está participando dessa coordenação toda e se tornando um nó réplica de um grande banco de dados NoSQL distribuído. Você só não sabia disso.</p>
<p>Diferente de um banco de dados, eu não preciso que todos os dados que já foram gravados num DHT, permaneçam lá pra sempre. Arquivos novos aparecem, arquivos que ninguém mais quer desaparecem. Se não tem ninguém compartilhando algum determinado arquivo, não preciso ter informações dele trafegando. Seria ineficiente. Só interessa arquivos que alguém está compartilhando.</p>
<p>Num Git não, eu preciso que tudo que os desenvolvedores do projeto fizeram, seja adicionar novos arquivos, modificar existentes, ou até apagar, esteja devidamente registrado no log de commits do repositório, e que seja possível voltar pra qualquer momento no passado e ter tudo intacto, exatamente como era. Não pode ser parcial. Por outro lado, não pode ficar no caminho do desenvolvedor e adicionar obstáculos. Se o desenvolvedor achar que quer reescrever a história, precisa deixar.</p>
<p>A resiliência do Git aparece numa equipe, com mais de uma pessoa. Basta ter mais um desenvolvedor que tem como comparar os repositórios nas máquinas de cada um e ver que houve problemas. Basta checar os hashes dos commits. Se tiver 3 desenvolvedores já temos como fazer um tipo rudimentar de consenso: o que tiver o histórico mais diferente tá errado.</p>
<p>Eu só expliquei os conceitos bem básicos, tem bem mais detalhes por trás pra vocês pesquisarem depois. Árvores Merkle são úteis pra BitTorrent e pra Git, mas também são usados em outros lugares. O sistema de arquivos ZFS, que é usado no TrueNAS pra grandes servidores de armazenamento também usa árvores Merkle pra detectar corrupção de dados e ter a funcionalidade de snapshots com pouco overhead.</p>
<p>Outras redes ponto a ponto como o IPFS, que é o InterPlanetary File System, um sistema de arquivos distribuídos global, usa árvores Merkle, mais especificamente Merkle DAGs como Git. Não só sistemas de arquivos, mas bancos de dados distribuídos como Cassandra ou Riak usam árvores de Merkle pra minimizar a quantidade de dados que precisam ser transmitidos pra sincronização de dados.</p>
<p>No começo do video eu mostrei a cadeia de certificados e como o certificado do meu domínio é assinado pela Letsencrypt que, por sua vez é assinado pela ISRG. O protocolo TLS usa uma variante de árvore de Merkle chamada Merkle Hash Tree, no framework de Certificado de Transparência pra fornecer um log auditável de certificados gerados. E essa característica é importante: qualquer coisa que precise ser auditável pode usar árvores Merkle pra oferecer &ldquo;prova de inclusão&rdquo;, de forma que é possível provar que um pedaço de dados faz parte de um determinado conjunto, sem revelar o conjunto inteiro.</p>
<p>E se você já parou pra estudar o básico de Bitcoin e outras criptomoedas, já deve ter se deparado com Árvores Merkle e Merkle Root. É o que está na raíz do bloco de transações da tal de blockchain. Um blockchain e um repositório Git ou a árvore de merkle de Bit Torrent são muito parecidos: eles garantem que não dá pra adulterar um bloco sozinho, sem precisar re-gerar todos os blocos à frente dele. Com um ingrediente a mais: o tal processo de mineração. Em Git, um rebase é uma operação razoavelmente rápida.</p>
<p>Mas num blockchain de Bitcoin, o equivalente de rebase de Git custa absurdamente caro, astronomicamente caro na real, o que garante que é perto de impossível fazer um rebase. É isso que torna o blockchain interessante. Um blockchain onde é barato fazer rebase não é diferente de um Git e, como reserva de valor, não vale nada. Tudo que expliquei neste episódio são as tecnologias que estão na fundação do Bitcoin.</p>
<p>Se conseguiram chegar até aqui, nós vimos na prática como encriptação com chaves simétrica funciona. Aprendemos como funciona troca de chaves usando criptografia assimétrica. Vimos como quebrar arquivos e trafegar pedaços de uma forma que conseguimos checar se os pedaços vieram corretamente e vimos como BitTorrent e Git funcionam com árvores Merkle. Tudo em computação moderna usa esses conceitos, em particular certificados digitais TLS, bancos de dados NoSQL e até Bitcoin, por isso é importante estudarem tudo. Se ficaram com dúvida deixem nos comentários abaixo, se curitram o video mandem um joinha, assinem o canal e não deixem de compartilhar com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>seguranca</category><category>bitcoin-e-criptomoedas</category><category>controle-de-versao</category><category>akitando</category></item><item><title>[Akitando] #146 - Protegendo e Recuperando Dados Perdidos - Git, Backup, BTRFS</title><link>https://akitaonrails.github.io/2023/10/19/akitando-146-protegendo-e-recuperando-dados-perdidos-git-backup-btrfs/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/10/19/akitando-146-protegendo-e-recuperando-dados-perdidos-git-backup-btrfs/</guid><pubDate>Thu, 19 Oct 2023 10:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/yfEhz9poqW8"
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;Fazia tempo que queria fazer um último episódio à playlist de armazenamento trazendo um pouco mais de BTRFS. Aproveito pra adicionar mais sobre Git, Docker tudo voltado ao tema de como não perder dados nunca mais e até como recuperar dados numa emergência.&lt;/p&gt;
&lt;p&gt;Tudo que você nem sabia que queria saber sobre armazenamento e gerenciamento de dados no seu HD.&lt;/p&gt;
&lt;h2&gt;Conteúdo&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:00:51 - CAP 1 - Recapitulando Sistemas de Arquivos - Journaling&lt;/li&gt;
&lt;li&gt;00:02:42 - CAP 2 - Caminho pro Desastre! Git e GitHub&lt;/li&gt;
&lt;li&gt;00:07:21 - CAP 3 - Git SEM GitHub - Git Raíz&lt;/li&gt;
&lt;li&gt;00:12:25 - CAP 4 - Partição Perdida - Sem Superbloco&lt;/li&gt;
&lt;li&gt;00:16:09 - CAP 5 - Não Caia em Golpes - Aliexpress&lt;/li&gt;
&lt;li&gt;00:19:46 - CAP 6 - Tentando Consertar - Backup do Superbloco&lt;/li&gt;
&lt;li&gt;00:24:51 - CAP 7 - Tentando Consertar - Última Instância&lt;/li&gt;
&lt;li&gt;00:29:36 - CAP 8 - Checksums - Sistemas de Arquivos Modernos&lt;/li&gt;
&lt;li&gt;00:34:18 - CAP 9 - Deduplicação - Sistemas de Arquivos Modernos&lt;/li&gt;
&lt;li&gt;00:36:28 - CAP 10 - Copy On Write - Sistemas de Arquivos Modernos&lt;/li&gt;
&lt;li&gt;00:40:21 - CAP 11 - BTRFS no Ubuntu - Por que Manjaro?&lt;/li&gt;
&lt;li&gt;00:45:51 - CAP 12 - Snapshots e Timeshift - BTRFS&lt;/li&gt;
&lt;li&gt;00:53:56 - CAP 13 - Snapshots e Docker - Não Perca Espaço&lt;/li&gt;
&lt;li&gt;01:02:59 - CAP 14 - Meu NAS Anti Ransomware - Synology&lt;/li&gt;
&lt;li&gt;01:07:37 - CAP 15 - Pacotes, Docker, Flatpak, QEMU - Sistema Estável&lt;/li&gt;
&lt;li&gt;01:10:03 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;A Quick and Hacky Way to Serve a Git Repo over HTTP (&lt;a href="https://theartofmachinery.com/2016/07/02/git_over_http.html"target="_blank" rel="noopener"&gt;https://theartofmachinery.com/2016/07/02/git_over_http.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;NGrok (&lt;a href="https://ngrok.com/docs/getting-started/"target="_blank" rel="noopener"&gt;https://ngrok.com/docs/getting-started/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;TestDisk LiveCD (&lt;a href="https://www.cgsecurity.org/wiki/TestDisk_Livecd"target="_blank" rel="noopener"&gt;https://www.cgsecurity.org/wiki/TestDisk_Livecd&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;TestDisk Docs (&lt;a href="https://www.cgsecurity.org/testdisk_doc/presentation.html#testdisk-partition-recovery"target="_blank" rel="noopener"&gt;https://www.cgsecurity.org/testdisk_doc/presentation.html#testdisk-partition-recovery&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ArchWiki: BTRFS (&lt;a href="https://wiki.archlinux.org/title/Btrfs"target="_blank" rel="noopener"&gt;https://wiki.archlinux.org/title/Btrfs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Timeshift and grub-btrfs in Ubuntu (&lt;a href="https://www.lorenzobettini.it/2022/10/timeshift-and-grub-btrfs-in-ubuntu/"target="_blank" rel="noopener"&gt;https://www.lorenzobettini.it/2022/10/timeshift-and-grub-btrfs-in-ubuntu/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;timeshift-autosnap-apt (&lt;a href="https://github.com/wmutschl/timeshift-autosnap-apt"target="_blank" rel="noopener"&gt;https://github.com/wmutschl/timeshift-autosnap-apt&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Scheduled snapshots not working (&lt;a href="https://github.com/teejee2008/timeshift/issues/135"target="_blank" rel="noopener"&gt;https://github.com/teejee2008/timeshift/issues/135&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Why is Grub menu not shown when starting my computer? (&lt;a href="https://askubuntu.com/a/1187104"target="_blank" rel="noopener"&gt;https://askubuntu.com/a/1187104&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Full-disk encryption with Btrfs on Ubuntu Xenial (&lt;a href="https://albertodonato.net/blog/posts/full-disk-encryption-with-btrfs-on-ubuntu-xenial.html"target="_blank" rel="noopener"&gt;https://albertodonato.net/blog/posts/full-disk-encryption-with-btrfs-on-ubuntu-xenial.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;How To Add Swap Space on Ubuntu 20.04 (&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04"target="_blank" rel="noopener"&gt;https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Swapfile Swapon invalid argument (&lt;a href="https://unix.stackexchange.com/a/713929"target="_blank" rel="noopener"&gt;https://unix.stackexchange.com/a/713929&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Thoughts on swap partition vs swap file in subvolume with Btrfs? (&lt;a href="https://www.reddit.com/r/archlinux/comments/ymq7ve/thoughts_on_swap_partition_vs_swap_file_in/?onetap_auto=true"target="_blank" rel="noopener"&gt;https://www.reddit.com/r/archlinux/comments/ymq7ve/thoughts_on_swap_partition_vs_swap_file_in/?onetap_auto=true&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;How do I use Timeshift/btrfs to permanently restore to a previous snapshot? (&lt;a href="https://forum.manjaro.org/t/how-do-i-use-timeshift-btrfs-to-permanently-restore-to-a-previous-snapshot/124683"target="_blank" rel="noopener"&gt;https://forum.manjaro.org/t/how-do-i-use-timeshift-btrfs-to-permanently-restore-to-a-previous-snapshot/124683&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/yfEhz9poqW8"
    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>Fazia tempo que queria fazer um último episódio à playlist de armazenamento trazendo um pouco mais de BTRFS. Aproveito pra adicionar mais sobre Git, Docker tudo voltado ao tema de como não perder dados nunca mais e até como recuperar dados numa emergência.</p>
<p>Tudo que você nem sabia que queria saber sobre armazenamento e gerenciamento de dados no seu HD.</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:00:51 - CAP 1 - Recapitulando Sistemas de Arquivos - Journaling</li>
<li>00:02:42 - CAP 2 - Caminho pro Desastre! Git e GitHub</li>
<li>00:07:21 - CAP 3 - Git SEM GitHub - Git Raíz</li>
<li>00:12:25 - CAP 4 - Partição Perdida - Sem Superbloco</li>
<li>00:16:09 - CAP 5 - Não Caia em Golpes - Aliexpress</li>
<li>00:19:46 - CAP 6 - Tentando Consertar - Backup do Superbloco</li>
<li>00:24:51 - CAP 7 - Tentando Consertar - Última Instância</li>
<li>00:29:36 - CAP 8 - Checksums - Sistemas de Arquivos Modernos</li>
<li>00:34:18 - CAP 9 - Deduplicação - Sistemas de Arquivos Modernos</li>
<li>00:36:28 - CAP 10 - Copy On Write - Sistemas de Arquivos Modernos</li>
<li>00:40:21 - CAP 11 - BTRFS no Ubuntu - Por que Manjaro?</li>
<li>00:45:51 - CAP 12 - Snapshots e Timeshift - BTRFS</li>
<li>00:53:56 - CAP 13 - Snapshots e Docker - Não Perca Espaço</li>
<li>01:02:59 - CAP 14 - Meu NAS Anti Ransomware - Synology</li>
<li>01:07:37 - CAP 15 - Pacotes, Docker, Flatpak, QEMU - Sistema Estável</li>
<li>01:10:03 - 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 Quick and Hacky Way to Serve a Git Repo over HTTP (<a href="https://theartofmachinery.com/2016/07/02/git_over_http.html"target="_blank" rel="noopener">https://theartofmachinery.com/2016/07/02/git_over_http.html</a>)</li>
<li>NGrok (<a href="https://ngrok.com/docs/getting-started/"target="_blank" rel="noopener">https://ngrok.com/docs/getting-started/</a>)</li>
<li>TestDisk LiveCD (<a href="https://www.cgsecurity.org/wiki/TestDisk_Livecd"target="_blank" rel="noopener">https://www.cgsecurity.org/wiki/TestDisk_Livecd</a>)</li>
<li>TestDisk Docs (<a href="https://www.cgsecurity.org/testdisk_doc/presentation.html#testdisk-partition-recovery"target="_blank" rel="noopener">https://www.cgsecurity.org/testdisk_doc/presentation.html#testdisk-partition-recovery</a>)</li>
<li>ArchWiki: BTRFS (<a href="https://wiki.archlinux.org/title/Btrfs"target="_blank" rel="noopener">https://wiki.archlinux.org/title/Btrfs</a>)</li>
<li>Timeshift and grub-btrfs in Ubuntu (<a href="https://www.lorenzobettini.it/2022/10/timeshift-and-grub-btrfs-in-ubuntu/"target="_blank" rel="noopener">https://www.lorenzobettini.it/2022/10/timeshift-and-grub-btrfs-in-ubuntu/</a>)</li>
<li>timeshift-autosnap-apt (<a href="https://github.com/wmutschl/timeshift-autosnap-apt"target="_blank" rel="noopener">https://github.com/wmutschl/timeshift-autosnap-apt</a>)</li>
<li>Scheduled snapshots not working (<a href="https://github.com/teejee2008/timeshift/issues/135"target="_blank" rel="noopener">https://github.com/teejee2008/timeshift/issues/135</a>)</li>
<li>Why is Grub menu not shown when starting my computer? (<a href="https://askubuntu.com/a/1187104"target="_blank" rel="noopener">https://askubuntu.com/a/1187104</a>)</li>
<li>Full-disk encryption with Btrfs on Ubuntu Xenial (<a href="https://albertodonato.net/blog/posts/full-disk-encryption-with-btrfs-on-ubuntu-xenial.html"target="_blank" rel="noopener">https://albertodonato.net/blog/posts/full-disk-encryption-with-btrfs-on-ubuntu-xenial.html</a>)</li>
<li>How To Add Swap Space on Ubuntu 20.04 (<a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04</a>)</li>
<li>Swapfile Swapon invalid argument (<a href="https://unix.stackexchange.com/a/713929"target="_blank" rel="noopener">https://unix.stackexchange.com/a/713929</a>)</li>
<li>Thoughts on swap partition vs swap file in subvolume with Btrfs? (<a href="https://www.reddit.com/r/archlinux/comments/ymq7ve/thoughts_on_swap_partition_vs_swap_file_in/?onetap_auto=true"target="_blank" rel="noopener">https://www.reddit.com/r/archlinux/comments/ymq7ve/thoughts_on_swap_partition_vs_swap_file_in/?onetap_auto=true</a>)</li>
<li>How do I use Timeshift/btrfs to permanently restore to a previous snapshot? (<a href="https://forum.manjaro.org/t/how-do-i-use-timeshift-btrfs-to-permanently-restore-to-a-previous-snapshot/124683"target="_blank" rel="noopener">https://forum.manjaro.org/t/how-do-i-use-timeshift-btrfs-to-permanently-restore-to-a-previous-snapshot/124683</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>Estou bem atrasado com a segunda parte do video da Rinha, e vai demorar mais um pouco. Honestamente, o tema me deu burn out, então resolvi fazer um video antes que não deixa de ser um pré-requisito porque até o final vou explicar um pouco mais sobre Docker e Docker Compose. Mas antes, quero falar de um problema que vira e mexe vejo desenvolvedores tendo: perder dados.</p>
<p>Sim, acidentes acontecem, e tanto o hardware quanto sistemas operacionais de hoje em dia, ajudam muito a não perder dados. Mas sério, a maioria de vocês não ia conseguir sobreviver com a tecnologia rudimentar que a gente tinha nos anos 80 e 90. Eu mesmo não lembro mais a última vez que perdi dados. Acho um absurdo que programadores ainda percam tempo com esse tipo de problema, então quero discutir diversas soluções pra isso. Vamos lá.</p>
<p>(&hellip;)</p>
<p>Um bom programador é mestre da sua máquina, nunca o contrário. Quando seu hardware perde seus dados e te força a passar perrengue, ele se tornou seu mestre, e você não é mais que um mero peão de um objeto inanimado. Vocês tem que mostrar quem manda. Vamos a alguns fatos. Em termos de sistema operacional, tanto faz se usa Mac, ou Windows ou Linux. Nenhum deles é muito melhor que o outro em termos de garantia de dados. Não é porque escolheu usar Mac que está magicamente mais protegido do que Windows. Isso não é verdade faz anos.</p>
<p>Vejam minha série sobre armazenamento. O NTFS do Windows tá longe de ser perfeito, cheio de problemas ainda, mas pro dia a dia, você não vai ter mais problemas do que teria com um ext4 no Linux ou APFS no Mac. Todos tem journaling, por exemplo. Significa que, mesmo que acabe a luz no meio de uma operação de disco, seu sistema de arquivos não vai ficar corrompido a toa. Vira e mexe alguns blocos podem se perder mesmo, mas as rotinas de manutenção padrão, são suficientes pra resolver a maioria dos problemas.</p>
<p>Quando alguém perde dados só existem duas razões: porque comprou um HD muito do porcaria ou porque cometeu erro humano mesmo. Aliás, vira e mexe alguém faz aquele meme de digitar &ldquo;rm -rf /&rdquo; e diz que iniciante não sabe o que isso faz. Mas parece que quem faz o meme também não sabe. Já faz algum tempo que o comando &ldquo;rm&rdquo; não apaga a raíz assim do nada. Precisa ou digitar &ldquo;/*&rdquo; com asterisco no final ou usar a opção &ldquo;&ndash;no-preserve-root&rdquo; pra realmente apagar a raíz. The more you know.</p>
<p>Enfim, nenhum sistema operacional vai resolver qualquer um desses problemas. Hardware porcaria só tem uma solução: jogar fora e comprar um de verdade. Toda vez que estiver tendo problemas frequentes, a probabilidade maior é hardware porcaria. Já a parte do erro humano, vamos tentar mitigar um pouco disso hoje.</p>
<p>Alguns meses atrás, um conhecido foi tentar instalar Linux como dual boot com Windows no notebook dele. Não sei qual era a marca, nem nada. Mas ele se ferrou: a partição Linux não bootava mais. Agora vem o problema: as últimas coisas que ele fez no projeto dele, não tinha dado push pro GitHub ainda. E, como praticamente todo mundo aqui assistindo, não tinha backup.</p>
<p>Tudo que vou narrar a partir daqui poderia ter sido evitado. Hoje em dia, todo programador usa Git. Isso é obrigatório. Mesmo se trabalha numa empresa, o mais porcaria possível, que por alguma razão do além, ainda não está usando Git, não existe desculpa: o projeto não precisa adotar Git pra você usar Git. GitHub é opcional, Git não. Marrete esse conceito na sua cabeça. Não importa se é um projetinho pessoal só na sua máquina. Faça &ldquo;git init&rdquo; e comece a fazer &ldquo;git commit&rdquo;.</p>
<p>Git não precisa de um servidor externo. Digamos que essa empresa hipotética super tosca pra onde está trabalhando não queira usar Git pra não ter que pagar GitHub ou alguma bobagem desse tipo. Ou usa alguma alternativa obsoleta de 20 anos atrás como a enorme porcaria de um SourceSafe da vida. Aí são dois grandes erros imperdoáveis. E digamos que você também não queira investir numa conta paga de GitHub, portanto não tem como fazer repositórios privados. E agora? Como faz?</p>
<p>Vou repetir: não precisa existir projeto no GitHub pra usar Git. Muitos iniciantes confundem isso. Git é uma ferramenta, GitHub é um serviço. GitHub precisa de Git, mas Git não precisa de GitHub. Qualquer diretório do seu computador pode virar um repositório Git, basta digitar &ldquo;git init&rdquo; dentro e pronto, ele cria o subdiretório &ldquo;.git&rdquo; que é o repositório propriamente dito. Agora basta fazer &ldquo;git add .&rdquo; pra adicionar tudo nesse diretório e &ldquo;git commit -m&rdquo; com uma mensagem pra criar um commit e já era. Só isso.</p>
<p>Eu fiz dois videos onde conto tudo que você deveria saber sobre Git e recomendo que assistam. Inclusive eu conto como lá nos primórdios de Git, quando realmente ninguém nem sabia que existia, eu trabalhei em projetos de clientes que usavam coisas como Subversion, mas só eu trabalhava com Git e usava um plugin chamado git-svn pra integrar os dois. Se lá em 2007 eu conseguia usar, sem nenhum suporte, em 2023, não existe desculpa aceitável pra não usar.</p>
<p>Isso resolve o problema de acidentalmente apagar um arquivo que não deveria, ou mudar de ideia num código que acabou de fazer, e conseguir voltar pra como estava no último commit. Digamos que você apagou um arquivo. Puts, apagou o arquivo errado? E agora? Se você fez a coisa certa, que é ficar fazendo pequenos commits, o tempo todo, ainda deve ter a versão anterior no último commit. Faça &ldquo;git status&rdquo;, olha ele marcado pra deleção. Basta fazer &ldquo;git checkout&rdquo; e o nome do arquivo, e pronto, listamos e olha só o arquivo de volta aqui.</p>
<p>Qualquer programador que use de desculpa &ldquo;puts, apaguei o arquivo sem querer e perdi tudo&rdquo;, ainda é um iniciante que precisa aprender a usar git. Ah, mas eu gosto de primeiro terminar tudo, tudo e depois fazer commit. E eu digo que você é um idiota. Vou dar um exemplo. Digamos que fiz um código aqui num arquivo, mas não terminei. E acabou o dia. Como sou disciplinado, já vou fazer um &ldquo;git commit -m &lsquo;codigo no meio&rsquo;&rdquo;. Puts, mas que commit feio. Melhor um commit feio agora do que ter um acidente e perder o arquivo. Agora posso desligar o computador e ir dormir.</p>
<p>Acordo no dia seguinte, vou continuar editando esse arquivo e aí sim, finalmente termino. Agora posso fazer um &ldquo;git commit -m &lsquo;codigo final&rsquo; &ndash;amend&rdquo;. Essa opção amend, vai desfazer o commit anterior e trocar por esse novo commit, com a nova mensagem mais bonita e descritiva. Rode &ldquo;git log&rdquo; olha só, aquela mensagem feia com código parcial sumiu e estou com o commit bonito. Agora posso fazer push pro servidor se quiser.</p>
<p>Dentro de um branch, faça quanto commits quiser, dê push de commits feios, e no final, faça um squash do branch antes de criar um pull request, se fizer muita questão. É uma das formas de lidar com isso. O histórico de commits nunca deve ser reescrito no branch master ou main, mas no seu branch, onde só você está trabalhando, foda-se, faça cinquenta commits. Daqui uma semana quando terminar, faça squash, se a quantidade de commits incomodar. Daí manda merge pra master.</p>
<p>Existem várias nuances no que falei, não é uma regra escrita em pedra, essas convenções vão variar de equipe pra equipe. Mas voltando ao meu ponto inicial: digamos que você não tenha um repositório remoto no GitHub. Estou trabalhando sozinho na minha máquina local. E não tenho pra onde dar push. Se der alguma pane catastrófica na minha máquina, eu perco tudo. Como evitar isso?</p>
<p>Se o projeto que estiver trabalhando for de cliente, e você for freelancer, por favor, pague uma conta no GitHub ou GitLab e faça push pra lá. Não desperdice o tempo de todo mundo. Isso tem nome: se chama economia porca. Porque na hora que alguma coisa der pane, não tem mais como recuperar. Isso dito, você é mão de vaca bagarai. Ok. Vou te dar a solução mais barata de todos os tempos. Espete um pendrive ou HD externo no seu PC.</p>
<p>Do diretório do seu projetinho faça &ldquo;git clone &ndash;bare . /media/pendrive/projeto.git&rdquo;. Isso vai criar um clone do seu projeto nesse pendrive. Lógico o caminho de onde o pendrive montou vai variar de distro pra distro. Adicione o pendrive como origin com &ldquo;git remote add origin /media/pendrive/projeto.git&rdquo;. Pronto. Vamos continuar trabalhando, criando um novo arquivo, adicionando, fazendo commit. Mas agora posso fazer o bom e velho &ldquo;git push origin master&rdquo; e ele vai mandar praquele clone no pendrive. O pendrive está fazendo o papel de GitHub pra você, sacou?</p>
<p>E podemos ir mais longe. Digamos que é aquela sexta feira no escritório, você está finalizando uma nova funcionalidade que precisa entregar hoje. O Tech lead tá lá esperando pra finalizar a parte dele. Mas aí dá uma daquelas panes no GitHub e ele fica fora do ar algumas horas. Bem no fim da sexta feira. Fodeu né? Fica pra segunda então? Não. Faça git push no pendrive, passa o pendrive pro tech lead, ele consegue adicionar como remote e dar pull no repo dele. E vai conseguir continuar o trabalho na máquina dele, mesmo sem ter GitHub.</p>
<p>GitHub é uma conveniência, mas não é mandatório. Qualquer coisa pode substituir. O que você perde enquanto ele tá fora do ar são as conveniências, a integração com o continuous integration, os pull requests que tavam em aberto e você não puxou, lista de issues e coisas assim, mas o código em si, dá pra manipular de vários jeitos diferentes.</p>
<p>Só pra concluir essa parte de Git, vou mais longe no exemplo do GitHub saindo fora do ar. Meu exemplo com pendrive assume todo mundo trabalhando presencialmente no mesmo escritório. Mas que cabeça a minha, todo mundo tá home office hoje né? Então fodeu mesmo, sem GitHub pra centralizar, se cair, todo mundo fica sem conseguir colaborar né? Só que não.</p>
<p>Eu terminei a minha parte na minha máquina, o tech lead tá lá na casa dele, precisando puxar o que acabei de fazer pra continuar. Não temos GitHub. Como faz? Simples. Sabe o clone &ldquo;bare&rdquo; que eu fiz no tal pendrive? Vamos modificar ele. Basta entrar no diretório &ldquo;.git&rdquo; e começar fazendo &ldquo;git &ndash;bare update-server-info&rdquo; e depois &ldquo;mv hooks/post-update.sample hooks/post-update&rdquo; e pronto, nosso repositório está pronto pra ser integrado a um servidor web qualquer. O mais simples que consigo imaginar é o http server que vem no Python. Com uma única linha de comando &ldquo;python -m http.server 8000&rdquo; e já era, temos um servidor web git.</p>
<p>Pra testar, basta ir no &ldquo;/tmp&rdquo; ou qualquer lugar e fazer &ldquo;git clone <a href="http://localhost:8000"target="_blank" rel="noopener">http://localhost:8000</a> teste&rdquo; e voilá, consigo fazer clone do meu repositório local. Quem trabalha com VPNs como TailScale ou ZeroTier já sabe o que fazer: só trocar localhost pelo IP da VPN que todo mundo da equipe enxerga e fazer o mesmo que descrevi antes: o tech lead adiciona um remote apontando pra esse servidor, consegue dar &ldquo;git fetch&rdquo; do seu branch e pode continuar a trabalhar tranquilamente enquanto povo do GitHub fica lá brincando de restartar servidor até voltar.</p>
<p>Outra solução rápida é instalar ngrok na sua máquina. Ngrok é o jeito mais simples de criar um túnel de fora pra dentro da sua máquina. Muita gente usa pra mostrar uma aplicação web rodando na sua máquina local que ainda não tem servidor de testes ou staging. Assim um cliente que não está presente consegue visualizar o andamento do trabalho olhando o que tá rodando no localhost da sua máquina. Entenderam?</p>
<p>Olha só, posso rodar &ldquo;ngrok http 8000&rdquo;. Isso vai abrir um tunel no serviço ngrok apontando pro nosso servidorzinho python local. Ele nos dá essa URL gigante de &ldquo;Forwarding&rdquo;. Podemos passar ela pro nosso querido tech lead. Na máquina dele, ele pode fazer &ldquo;git clone &quot; e essa URL-zona e &hellip; voilá, olha só. Da casa dele, tá conseguindo puxar os commits da minha máquina. Entenderam o que aconteceu aqui?</p>
<p>Num dia normal, o trabalho iria parar por horas até o GitHub voltar. Mas sabendo dessas coisas simples e triviais, conseguimos continuar trabalhando como se nada tivesse acontecido. Esse é o poder do Git e de saber como juntar componentes simples. Criar um clone &ldquo;bare&rdquo;, habilitar  pra ser servido via http, subir um servidor http simples com python e abrir um túnel seguro usando ngrok.</p>
<p>Mas claro, tudo isso assume que você, no mínimo, usa Git minimamente direito. Fazendo commits frequentes. Fazendo backups frequentes. Só quem já perdeu dados sabe da importância disso. Todos que dizem que é desnecessário só provam que são virgens e ainda precisam perder alguma coisa importante pra aprender. Vocês assistindo podem aproveitar o que falei e evitar ter que passar por esse estresse.</p>
<p>Digamos que não fez nada disso. Deu na telha que quer experimentar montar um dual boot na sua máquina pela primeira vez, e gosta de viver perigosamente, nem deu push dos projetos pro GitHub, nem copiou num pendrive, nem sequer fez commits, tá tudo parcial na sua máquina. Mas tá seguro, você assistiu um tutorial no YouTube, seus parças tão te chamando pra jogar Genshin a noite. Não tem tempo a perder, bora redimensionar o disco, criar uma segunda partição de Windows e passar a noite jogando.</p>
<p>Só que aí dá tudo errado. Tudo na primeira vez, sempre vai dar errado. Aceitem essa máxima. Se for a primeira vez de qualquer coisa, já comecem sabendo que não vai funcionar de primeira. Não custa nada espetar um HD externo e copiar as coisas mais importantes lá. Mas alas, a pressa é inimiga da perfeição mesmo. Não lembro onde guardei o HD externo, não quero perder tempo procurando. Foda-se, vai dar certo, confia, cola no pai.</p>
<p>É muito difícil saber exatamente o que acontece em panes. Se foi algum procedimento errado ou se foi mesmo algum defeito de hardware. Pra tentar demonstrar algo parecido, fiz uma máquina virtual com um Ubuntu e um disco virtual. Esse disco aparece como dispositivo &ldquo;/dev/vdb&rdquo; de virtual device B, já que o virtual device A é disco primário. Está formatado como ext4, dentro coloquei meu projeto da rinha em rails, um ISO de Ubuntu. Faça de conta que é um HD normal do seu notebook.</p>
<p>Agora vou desmontar com &ldquo;umount&rdquo;. Um dos jeito de simular um erro de hardware é escrever por cima dos primeiros bytes no começo do dispositivo. Lembram dos meus videos sobre sistemas de arquivo? O que é uma partição? É uma estrutura de dados que guarda coisas como em qual endereço começa e onde termina uma partição, o tipo de sistema de arquivo, localização dos blocos. Esses primeiros bytes formam o que chamamos de Super Bloco.</p>
<p>Olha só, se eu tentar montar esse dispositivo de novo, vai dar erro. O Linux não sabe o que fazer, porque não tem mais o superbloco. Os dados em si ainda estão na partição, no linguição de bytes, mas sem o superbloco não dá pra saber onde começa e onde termina o que. Pense assim, seus arquivos são como casas montados com lego. Cada lego está enterrado num campo de futebol, o seu HD. Mas o superbloco tem a localização de cada bloco, e as instruções de como remontar sua casa de lego. Sem esse superbloco, mesmo achando as peças, não sabemos se ainda falta mais blocos ou como encaixar esses blocos.</p>
<p>Se acontecer um erro como esse, onde a partição se recusa a bootar ou montar e aparecem erros como esse, filesystem errado, ou opção errada, ou superbloco errado, é um péssimo sinal. A primeira coisa que recomendo fazer é evitar insistir. Se usar comandos errados que sobrescrevem em cima do disco, se fodeu, vai perder dados. Muito cuidado com comandos como fdisk ou mkfs, que é o equivalente de format de Linux.</p>
<p>A forma de diagnosticar algo assim é achar outro computador, outro notebook, baixar a ISO de algum Linux como o próprio Ubuntu, gravar num pendrive, daí passar a fazer boot pelo pendrive. A maioria das distros Linux tem um ambiente de Live CD que boota um ambiente operacional inteiro, que inclusive se conecta no seu Wifi e permite instalar qualquer pacote. Isso permite baixar quaisquer ferramentas de diagnóstico e recuperação.</p>
<p>A partir desse ambiente, ele vai achar seu dispositivo como um &ldquo;/dev/sdb&rdquo; ou &ldquo;/dev/nvme2&rdquo; ou qualquer coisa assim. Assistam meus videos sobre Linux, o de Gentoo principalmente, porque a instalação dele exige manualmente fazer exatamente esse tipo de operação. Por isso fiz aquele video: qualquer um que já instalou um Gentoo ou Arch do zero, já sabe como fazer isso.</p>
<p>Antes de continuar deixa eu comentar uma coisa: porque esse tipo de problema acontece? Digamos que não foi erro humano. SSDs quebram assim do nada mesmo? E sim, quebram. Se for no Aliexpress e pesquisar por SSDs e ordenar a partir do menor preço, sim, as primeiras dez páginas, no mínimo, é quase 100% de chance de vir um tijolo. Aliás, se vier um tijolo pelo menos vai ser mais seguro. Problema é quando vem um drive mesmo e você confia com seus dados.</p>
<p>Primeiro, tem os golpes mais óbvios. Quando desparafusa e abre, dentro não tem uma placa controladora e chips de memória Flash. Não, tem um adaptador USB barato pra micro SD Card. Chips de SD Card sempre usam componentes mais baratos que SSD. Eles foram feitos pra serem quase descartáveis, justamente pra uso em câmeras fotográficas ou celulares. Não foram pensados pra uso como um HD de notebook. Não tem banda pra isso, não tem controlador pra isso, não tem memória RAM de cache, a qualidade da memória NAND flash é muito inferior. Por isso são muito mais baratos.</p>
<p>Já disse isso antes e vou repetir: nunca deixe nada importante num SD Card. É comum eles corromperem. SD Card deve ser usado única e exclusivamente como armazenamento temporário, tempo suficiente pra chegar em casa da sua viagem, e descarregar os cartões no seu notebook. Mas os tais SSDs baratos de marcas chinesas genéricas contam com sua burrice. Ninguém nunca abre a carcaça de um SSD pra ver o que tem dentro, e eles contam com isso.</p>
<p>Mesmo quando vem com componentes de SSD de verdade, eles vendem chips de 500MB como se fosse 1TB. Por isso sai pela metade do preço, mas o controlador tá gambiarrado e mente pro seu computador dizendo que tem 1TB. Se você tentar mesmo gravar mais que 500MB, ele vai começar a sobrescrever os dados do começo. Como a maioria das pessoas vai só gravando mas não checa na hora se tudo gravou mesmo, só vai descobrir da pior maneira: quando precisar do arquivo, tentar acessar, e descobrir que os arquivos não estão mais lá ou estão incompletos e corrompidos.</p>
<p>Como evitar isso? Nunca, jamais compre marcas chinesas genéricas desconhecidas. Compre Samsung, Crucial, Sandisk, Western Digital, só marcas reconhecidas de longa data. E de lojas com alguma reputação e não um revendedor aleatório desconhecido. Muitos vão pegar a carcaça de um Samsung quebrado, colocar o tal adaptador USB com SD card e te revender como se fosse original Samsung. Só compre em loja de verdade e só compre item novo e lacrado. Pense em SSD como escova de dente: você não quer um usado.</p>
<p>Em quais situações você pode arriscar um SSD mais barato genérico? Se for pra ser sua biblioteca de Steam, por exemplo. HD pra games. Aí foda-se se perder dados. Se o HD for mesmo um golpe, pega outro HD barato, e baixa tudo da Steam de novo. Mas nunca, jamais, sob nenhuma circunstância use um HD desses pra trabalho, ou mesmo pra guardar coisas importantes pra você, como as fotos da sua família, dos seus filhos, das suas viagens. Você vai perder esses dados pra sempre.</p>
<p>Obviamente, todo mundo concordou com o que falei, mas tá fazendo exatamente o que eu disse pra não fazer. Não usou Git. Não fez uma cópia dos seus projetos num pendrive ou hd externo. Foi querer usar o mesmo HD de trabalho pra games com dual boot. E chegamos na situação que ele não boota mais e nem conseguimos mais montar a partição. O próximo passo é arranjar um outro HD, seja interno, seja externo, que tenha mais espaço livre do que o total do HD quebrado. Vamos fazer uma cópia bit a bit, pra garantir que não vamos perder mais nada.</p>
<p>Usando o comando &ldquo;lsblk&rdquo; podemos ver os dispositivos de armazenamento conectados. Aqui neste exemplo, tenho o &ldquo;/dev/vba&rdquo; que é a partição funcionando do meu Ubuntu, e o &ldquo;/dev/vdb&rdquo; que é a partição corrompida. Antes de tentar fazer qualquer coisa, o certo é usar o comando &ldquo;dd&rdquo; e fazer uma cópia, assim &ldquo;sudo dd if=/dev/vdb of=~/Downloads/backup.hd bs=4M&rdquo;. O nome de arquivo e extensão não importam, qualquer coisa serve. Dependendo do tamanho do seu HD quebrado e velocidade do seu PC, isso pode levar horas. Vai parecer que está travado. Deixa quieto lá, vai jantar e volta amanhã. O LOLzinho com os parça essa noite já miou, nem insiste.</p>
<p>Pronto, agora temos uma cópia bit a bit do HD nesse arquivo. Garanta que não vai perder ele, porque se fizer mais alguma coisa errada na tentativa de consertar, temos uma segunda chance com esse backup. Coloque num outro HD externo se quiser garantir. Mantenha longe de você por enquanto, você já causou problemas demais. Um profissional de recuperação de dados tem muito mais opções do que vou mostrar agora. Só vou falar o mais óbvio, porque eu também não sei tudo.</p>
<p>Felizmente muita gente já passou por situação similar, e a partição costuma ter backups desse superbloco. Mais de um na real. Faça o teste no seu próprio sistema. Num Arch linux, primeiro precisamos instalar o pacote &ldquo;e2fsprogs&rdquo;, que contém programas pra lidar com sistema de arquivos como ext2 ou ext4. Usando o comando &ldquo;lsblk&rdquo; podemos listar os dispositivos e com o comando &ldquo;dumpe2fs /dev/vdb | grep -i superblock&rdquo; podemos listar os tais superblocos e backups.</p>
<p>Como esperado, o superbloco primário está no endereço zero, mas veja quantos backups temos. Um no endereço 32 mil e tantos, outro no endereço 98 mil e tantos, oito backups. Um erro catastrófico é quando alguma coisa acontece que corrompe não só os primeiros bytes, mas todos os bytes até o último backup. É raro, mas pode acontecer, especialmente se você usar errado uma ferramenta como fdisk e sobrescrever uma nova partição em cima da antiga.</p>
<p>O comando &ldquo;dumpe2fs&rdquo; talvez não consiga listar esses superblocos caso a partição esteja corrompida. Tentando no dispositivo &ldquo;/dev/vdb&rdquo; olha só, ele dá erro e não lista nada. Podemos usar o comando &ldquo;mke2fs&rdquo; que normalmente serve pra formatar uma partição, mas passando a opção &ldquo;-n&rdquo; de &ldquo;dry-run&rdquo; vai só simular a execução do comando sem de fato executar nada. Lembra o que falei antes: não queremos que nada escreva nesse disco quebrado. O programa não está consultando a partição com problema, só dizendo que, se não fosse um dry-run, escreveria os superblocos nesses endereços que listou.</p>
<p>Pra facilitar recuperação é importante saber qual é o tipo de sistema de arquivos que escolhemos na instalação. No caso de Windows é fácil, acho que ninguém escolhe nada diferente de NTFS. Mac já complica porque antigamente tinha o HPFS+ que mudou pra APFS a partir do final de 2017 com o macOS 10.13, mas muita gente continuou usando o antigo até depois de 2018. Já Linux, maioria costuma escolher ext4. Tem gente que escolhe ReiserFS ou os novos ZFS ou BTRFS. Cada um deles tem ferramental diferente. O que estou mostrando até agora é assumindo que é ext4.</p>
<p>Agora podemos tentar recuperar o superbloco a partir dos backups. Temos que tentar um a um até conseguir. Talvez nenhum dos backups estejam bons, talvez já no primeiro resolva. Pra fazer essa recuperação usamos o comando &ldquo;fsck&rdquo; com opção &ldquo;-b&rdquo; e o número do backup. Agora é ir confirmando &ldquo;yes&rdquo; até o fim. E se continuar assim, yes, yes, olha só, terminou. Será que funcionou?</p>
<p>Pra saber, podemos tentar montar de novo. E sucesso, montou de primeira, sem nenhum erro. Se listarmos dentro, sim, aqui estão nossos arquivos. À primeira vista tudo intacto. Nem sempre as coisas acontecem assim bonitinho, lembrem-se que eu só simulei um problema num ambiente controlado. Sob condições ideais de temperatura e pressão, é assim que conseguimos recuperar.</p>
<p>O certo agora é não confiar que só porque parece tudo ok, parar aqui. O certo é plugar outro HD externo, e usar um programa como rsync pra fazer uma cópia dos arquivos pra outro lugar. Você quis economizar com HD barato, mas tá percebendo que pra recuperar agora vai precisar de vários outros HDs pra fazer o backup que não quis fazer antes? Depois de tudo copiado, o certo é formatar essa partição do zero e copiar os arquivos de volta. Mas o mais certo mesmo é, depois de copiar os arquivos pra um lugar seguro, se livrar desse HD.</p>
<p>Se foi problema de hardware e não erro humano, é provável que vai voltar a dar pau de novo. Nunca confie num hardware que já te deixou na mão uma vez. É lixo, joga fora. Se o cavalo te derruba, você sacrifica, é o que eu sempre digo. No mínimo troca por um SSD novo, de verdade, de uma Crucial ou Samsung da vida, e usa esse HD que deu pau num estojo externo pra ser HD USB só pra coisas não importantes como games da Steam.</p>
<p>Mas, digamos que o comando &ldquo;fsck&rdquo; não funcionou, nenhum dos backups estava bom e não deu pra recuperar o superbloco. A partir daqui depende da importância dos dados no seu HD. Faz de conta que é daqueles casos excepcionais onde sua carteira de Bitcoin com milhões de dólares está só nesse HD. Um puta erro de amador, mas já vimos isso acontecer antes. Nesse caso o melhor é procurar um profissional de recuperação de dados. Pra recuperações mais hardcore, ele vai desmontar o SSD, colocar num hardware especializado pra esse tipo de coisa e vai fazer magia negra. Dependendo da pane, talvez consiga recuperar.</p>
<p>Se não for esse caso, aí recomendo que instale a ferramenta TestDisk. Eu não tenho tanta experiência nisso, então recomendo que procure outros videos como este daqui. Vou deixar o link abaixo. O TestDisk propriamente dito tem algumas ferramentas de análise pra tentar recuperar as informações da partição, reconstruir o que conseguir, mas não tem nenhuma garantia que vai funcionar. Ele não é muito intuitivo de usar, por isso recomendo ler bem a documentação e assistir alguns videos de exemplo.</p>
<p>Seja por ter corrompido a partição ou seja porque você deletou arquivos acidentalmente, o caso é o mesmo: os bytes que compõe seu arquivo nunca são deletados de verdade. Eles ainda estão fisicamente no HD. Acontece o seguinte: o tal sistema de arquivos, como ext4, mantém um índice dizendo: arquivo chamado &ldquo;hello.txt&rdquo; tem 20 blocos, e os endereços desses blocos são X e Y. Quando pedimos esse arquivo, ele vai buscar esses blocos e nos devolve na ordem certa. Blocos são conjuntos de bytes, depende do sistema de arquivos dizer quantos bytes tem num bloco. Por isso falo que é tudo um linguição de bytes.</p>
<p>Quando mandamos apagar um arquivo, não vai bloco a bloco e escreve zeros em cima. Isso até existe, alguns sistemas chamam de &ldquo;Deleção segura&rdquo;. Só apaga o registro do arquivo &ldquo;hello.txt&rdquo; desse índice, daí perdemos a localização dos blocos. Mas os blocos em si continuam no HD. Lembram a metáfora dos legos enterrados no campo de futebol. Eles continuam lá enterrados, só jogamos fora o mapa da localização de cada peça.</p>
<p>Os blocos só vão se perder se algum novo arquivo escrever por cima deles. Por isso falamos que, se apagou algo por engano, a primeiríssima coisa a se fazer é desmontar esse HD e tirar da máquina, pra nenhum processo acabar gravando nada em cima por acidente. E por isso a segunda coisa é fazer um backup bit a bit com a ferramenta DD, porque isso vai preservar todos os blocos, exatamente como eles estavam até aquele momento. Daí podemos fuçar sem perigo de perder alguma coisa, porque tem o backup, a imagem exata.</p>
<p>No pacote do TestDisk existe a ferramenta Photorec. Também já expliquei em outro video a diferença de arquivos texto e arquivos binários, e expliquei como o sistema operacional sabe que um determinado binário é um executável. Todo arquivo binário, tem nos primeiros bytes, um cabeçalho que indica que tipo de arquivo é. Um JPEG, um PNG, um video MP4, um executável ELF de Linux, um documento de Word, todos tem cabeçalhos padrão.</p>
<p>Na metáfora dos Legos, é como ir desenterrando peça a peça, sabendo que um JPEG começa com um bloco vermelho, um Word começa com bloco azul, e aí tentar montar na tentativa e erro. Uma ferramenta como Photorec vai tentar, por tentativa e erro, localizar o começo dos arquivos e os blocos que fazem sentido vir depois.</p>
<p>Eu simulei isso nesse dispositivo &ldquo;/dev/vdb&rdquo;, fazendo de conta que o fsck não conseguiu recuperar. Rodando o photorec, ele vai pedir pra apontar um diretório em outra partição boa, pra onde queremos que vá gravando os arquivos que conseguir encontrar. Dependendo da quantidade de arquivos que tinha, vai demorar um bocado. No final vai surgir um diretório com um monte de arquivos com nomes esquisitos. É o máximo que podemos fazer se não tiver como recuperar o superbloco.</p>
<p>Olha só, tentando abrir alguns desses arquivos, de fato o conteúdo faz algum sentido. Mas claramente alguns estão incompletos. E vai vir muito lixo junto. Nesse ponto assuma que você se fodeu, perdeu a maior parte, e qualquer resquício que conseguir recuperar já é lucro. Entendeu? Se você perder o superbloco, perdeu os metadados, diretórios, nomes de arquivos, tudo. Nesse estágio estamos escovando bit mesmo.</p>
<p>Como não chegar nesse ponto? Só existe uma maneira: faça backup. É a recomendação mais velha da computação que ninguém segue, e eu nem tenho esperanças que vocês assistindo vão seguir, mas a gente tenta. E é a economia mais porca de todas não fazer backup. Um SSD Sandisk de 1TB, no MercadoLivre, custa uns 300 reais. Sandisk não é e melhor marca, mas é mil vezes melhor que um chinês genérico no fim do Aliexpress. Um case USB que transforma esse SSD num drive externo, não custa 30 reais. Uma balada paga isso.</p>
<p>Todo sistema operacional já vem com software de backup. Nem precisa instalar nada. Use o Time Machine do MacOS, use o app de Backup que já vem no Windows, use um app como Deja Vu que já vem nos Ubuntu. Não seja preguiçoso. E backup não é só pro caso de dar pane de hardware no seu SSD barato, tem caso pior. Já ouviu falar de ransomware? Vou contar como eu sou imune a isso.</p>
<p>Se num extremo está a pessoa que confia todos os seus dados importantes num SSD genérico chinês do fundo do Aliexpress, sem nenhum backup, do outro lado estou eu. Já mostrei minha solução em outros videos e no Insta, mas eu tenho um NAS de 60 terabytes. São 6 HDs IronWolf de nível industrial de 12 terabytes cada, em sistema de redundância, onde não perco nada mesmo se um HD morrer agora. Eu uso não só pra backup, mas faço coisas como edição dos meus videos direto dele.</p>
<p>Claro que não dá pra recomendar isso, é uma solução de mais de 30 mil reais de investimento, na época. Como preços pioraram, agora, é inviável. Minha recomendação é um HD externo USB, coisa de menos de 400 reais, e que vai evitar muita dor de cabeça. Qualquer coisa acima é luxo. O mínimo é suficiente, faça o que puder, só não faça nada.</p>
<p>Quero aproveitar pra falar de outra coisa que não falei na série de armazenamento: sistemas de arquivos modernos. Lembrando, sistemas de arquivos como os antigos FAT16 e seus derivados como FAT32, ou mesmo ext2 nos primeiros Linux, foram pensados numa época onde espaço em disco era caro. RAM era caro. CPU era caro. Não havia recursos pra desperdiçar. Era a época onde falo que a gente escovava bit, no sentido que cada bit extra fazia diferença.</p>
<p>Segurança e performance são trade offs. Quanto mais segurança quiser, menos conveniente as coisas vão ser e mais recursos vamos precisar. Quando gravamos um arquivo num HD, o sistema de arquivos divide os bytes em blocos e grava nos setores do disco. Esse arquivo fica lá, semanas, meses, às vezes anos, sem ser acessado. Um belo dia você resolve abrir esse arquivo. Digamos que é uma planilha. Como eu sei que um dos setores físicos do disco magnético não falhou, flipou um bit, e algum dos valores na planilha não estão errados?</p>
<p>Num sistema antigo como FAT16, não tem como saber. Se corromper, corrompeu e não vamos saber. Num sistema moderno existe checksums. Num APFS de Mac ou NTFS de Windows, existe checksum no nível do arquivo. Quando o arquivo é gravado, o sistema gera um checksum e grava junto. Quando pedimos pra ler, ele pega os blocos e recalcula o checksum pra comparar com o que estava guardado. Se os checksums forem diferentes, significa que o arquivo está corrompido. Não é isso, mas se não sabe o que é um checksum, pense num dígito verificador.</p>
<p>Essa informação é super importante, porque numa planilha com milhares de valores, podemos estar tomando decisões importantes de forma errada por causa de um único bit errado. Um único bit pode mudar um valor de 100 reais pra milhões, por exemplo. Mas se o sistema avisar que o checksum está errado, sabemos que não podemos confiar nesse arquivo. O que fazer? Se você seguiu a recomendação e fez a porcaria de um backup, podemos ir nesse backup recuperar esse arquivo.</p>
<p>Em sistemas de arquivos mais modernos, pensados pra situações mais parrudas como servidores de arquivos, pense um Dropbox ou Google Drive da vida, com dados de milhões de pessoas, existem opções como o lendário ZFS da antiga Sun e o novo BTRFS do Linux. Eles tem checksum por blocos. Então conseguimos saber qual bloco dentro do arquivo está corrompido. Mas eles vão mais longe.</p>
<p>Meu NAS Synology está em configuração de RAID, com múltiplos HDs. Significa que o mesmo bloco tem cópias redundantes em mais de um HD. E BTRFS tem uma rotina que recomenda rodar periodicamente, digamos de 3 em 3 meses ou de 6 em 6 meses, chamado &ldquo;scrubbing&rdquo;. É um processo pesado que vai rodar em background, sem interromper seu dia a dia, e rechecar checksum a checksum de todos os blocos. Se notar que uma falhou, e estivermos em RAID, ele vai procurar uma cópia redundante desse bloco. A chance dos dois estarem corrompidos ao mesmo tempo é baixa, então podemos usar o bloco intacto pra consertar o que corrompeu.</p>
<p>Todo HD falha. Não importa se é HD mecânico com discos ou chips de Flash NAND de SSDs ou NVMEs. Todo chip Flash tem uma vida útil de quantidade de operações de escrita. Depois que passar desse limite, estraga e é isso. As checagens que falei, em muitos casos, só vão indicar mais cedo que algo falhou, mas sem redundância, sem uma segunda cópia intacta, não tem como adivinhar o que era o dado original que corrompeu, só dá pra saber que corrompeu.</p>
<p>Checagens de checksum, recuperação de blocos corrompidos, gastam RAM, gastam CPU. Não sai de graça. Servidores de arquivo parrudos como ZFS precisam de gigabytes de RAM pra serem eficientes, e vão usar toda RAM possível. Claro que tem mais vantagens que só isso. NTFS acho que não tem, mas APFS de Mac, assim como ZFS e BTRFS de Linux tem deduplication.</p>
<p>Faz de conta que você é desenvolvedor web e tem diversos projetos de Node.js na sua máquina. Sabe o maldito &ldquo;npm install&rdquo; que gera aqueles diretórios node_modules em todo projeto? Concorda que na maioria dos projetos vai ter as mesmas bibliotecas e os mesmos arquivos? Então estamos desperdiçando espaço com tanta duplicata? A resposta é sim, está desperdiçando.</p>
<p>Pra combater isso existem processos de deduplicação. Num NTFS de Windows, se estiver em Windows Server, ele oferece ferramenta pra que, caso dois arquivos tenham exatamente o mesmo conteúdo binário, ele consiga fazer o metadado dos dois apontarem pros mesmos conjuntos de blocos. Se forem dois arquivos de 200MB, originalmente iriam ocupar 400MB, mas com deduplicação, vai ocupar só 200MB. Só volta a ocupar mais espaço se decidirmos modificar o segundo arquivo, por exemplo.</p>
<p>Isso é muito útil no cenário de servidor de arquivos da empresa. O chefe manda um email com um PDF de 100MB pra 10 pessoas. Cada uma grava no seu diretório pessoal no servidor. Iria ocupar 1 giga. Mas se rodar a ferramenta de deduplicação, ele faz as 10 cópias apontarem pros mesmos blocos de um arquivo só, economizando 10 vezes o espaço. É o mesmo caso de termos 10 projetos de Node, cada um com node_modules repetidos. Rodando algum processo de deduplicação, no caso uma ferramenta como Opendedup no Linux, dá pra fazer algo semelhante no ext4.</p>
<p>Mas nenhuma dessas soluções é ideal. Os sistemas de arquivos NTFS e ext4 não foram feitos pra suportar esse tipo de coisa. O sistema de arquivos em si não suporta nada disso, daí dependemos de uma ferramenta externa pra tentar adicionar essas funcionalidades. O ideal é que o próprio sistema de arquivos implemente Copy on Write, independente de uma ferramenta externa.</p>
<p>Copy on Write ou CoW é super importante. É literalmente os conceitos de um Git só que em nível do sistema de arquivos inteiro, funcionando de forma transparente pro usuário. Num sistema copy on write, quando editamos um arquivo que já existe, os novos blocos não são gravados por cima dos antigos, ele faz o equivalente a um branch de Git e os novos blocos são gravados nesse branch, parecido com commits. Isso mantém o arquivo original intacto.</p>
<p>De cara, isso também evita mais corrupção de dados. Antigamente, num FAT16 da vida, onde estávamos na situação onde não tínhamos espaço sobrando, não tinha outra opção: toda modificação de arquivos precisava gravar os blocos novos em cima dos velhos. Mas e se acabasse a luz no meio? Ia ficar um arquivo corrompido. E isso era comum. Pra mitigar esse problema criou-se o conceito de journaling, que NTFS no WIndows, ext4 no Linux. HPFS+ no Mac passaram a suportar no fim dos anos 90.</p>
<p>Super resumido é assim. Digamos que eu tenha um arquivo, aquele Powerpoint, e ele ocupa 10 blocos no disco. No sistema de arquivos temos o nome do arquivo e um mapa apontando pra esses 10 blocos. Isso faz parte dos tais metadados de um arquivo. Agora editamos por cima e essa mudança afetaria os dois últimos blocos. Esses dois blocos serão gravados no disco e a localização será gravada no journal. O arquivo original ainda tem os 10 blocos originais até este ponto.</p>
<p>Ao terminar de gravar os dois novos blocos, só aí o NTFS vai atualizar o mapa do arquivo e mudar a referência dos dois últimos blocos pra apontar pra localização dos dois novos. Mas digamos que ele começou a mudar a referência do penúltimo bloco e antes de mudar a referência do último, acaba a luz e o sistema reboota. Quando reiniciar, o NTFS checa o journal e vê que faltou mudar a localização do último bloco.</p>
<p>Como ele tem essa informação no journal, vai conseguir terminar a operação e o Powerpoint não fica corrompido, como ficaria antes. Se ele ficasse em dúvida, faria rollback, e manteria o arquivo intacto. Os novos blocos não sobrescreveram os antigos ainda. Esse processo gasta um pouco mais de espaço, mas evita que arquivos fiquem corrompidos. Espaço hoje é mais barato, então podemos fazer isso.</p>
<p>Copy on Write vai um passo além. Depois que os novos blocos são gravados e registrados no journal, é feito tipo um fork no nível dos metadados. Imagine o mundo antes de Git. Se você copiasse um arquivo em cima do outro, o que acontece com o original? Ele se perderia pra sempre, certo? É assim que todo sistema de arquivos funciona. Mas o que acontece quando habilita Git num diretório e copia um arquivo em cima do outro? Vamos testar.</p>
<p>Aqui temos um diretório com Git habilitado e os arquivos já comitados. Se listar o que tem neste hello.txt, veja, &ldquo;hello world&rdquo;. Vou criar um novo arquivo fora, lá no /tmp com conteúdo de &ldquo;bla bla bla&rdquo;. Copio o arquivo por cima do meu original. Normal, meu arquivo original com &ldquo;hello world&rdquo; foi pro saco como podem ver. E se eu mudar de idéia?</p>
<p>Agora entra o Git pra te salvar. Fazendo &ldquo;git status&rdquo; podemos ver que ele sabe que esse arquivo foi modificado mas ainda não comitamos, não nos comprometemos com ele. Então podemos fazer &ldquo;git checkout&rdquo; desse arquivo, olha o conteúdo, &ldquo;hello world&rdquo; de volta. Git nos permite mudar de idéia e não perder dados ao custo de manter uma cópia do original em algum lugar ocupando espaço extra.</p>
<p>É por isso que todo projeto deve usar Git. Podemos mudar de idéia sem preocupação. Quando estivermos satisfeitos fazemos commits, e mesmo assim podemos reverter e até recuperar commits apagados. Copy on Write em sistema de arquivos é a mesma coisa. E em Linux, que eu saiba, só existem dois sistemas de arquivos modernos que suportam isso: o  ZFS e o BTRFS.</p>
<p>Quem usa Ubuntu tem na instalação a opção de usar ZFS. E aqui já aviso: evite. É muito fácil fazer besteira em ZFS e perder tudo sem querer. ZFS é excelente pra quem está montando um servidor de arquivos, usando distros com o TrueNAS, que é o melhor. Só quem estudou bastante a documentação de ZFS deve tentar usar. Não basta só instalar e acabou, precisa de constante manutenção e monitoramento pra saber se está realmente tudo funcionando como deveria. Pra usar no dia a dia, no seu notebook, acho que vai mais atrapalhar do que ajudar.</p>
<p>Isso não quer dizer que ZFS é ruim, pelo contrário, é provavelmente o sistema de arquivos mais avançado que existe. Porém, pros propósitos de máquina do dia a dia de programador é como matar uma mosca com uma bazuca e arriscar atirar a bazuca no seu pé. Outra coisa: todo mundo me pergunta qual distro eu mais gosto. Eu uso Manjaro GNOME no meu dia a dia. Primeiro porque é um Arch Linux mais estável. Ele segura um pouco os pacotes mais novos, pra testar e garantir que nada vai quebrar. Mesmo assim alguns updates ainda quebram de vez em quando, mas é um bom compromisso entre ter coisas novas mais rápido e não ficar parado a cada novo update.</p>
<p>Mais importante: Manjaro já vem pré-configurado pra BTRFS logo na instalação. É só instalar, escolher BTRFS e ele vai se virar e te dar um ambiente estável. Em vez de mostrar Manjaro, vou mostrar Ubuntu 23, que acabou de lançar. Todo o trabalho de configuração que vou mostrar agora não precisa fazer fazer no Manjaro. Começando na instalação, todo mundo escolhe aqui a primeira opção, que vai instalar EXT4 igual toda outra distro. Mas agora ela tem essa segunda opção com ZFS, que é experimental ainda. De novo: não teste numa máquina de trabalho. Teste numa máquina virtual pra aprender tudo primeiro caso queira mesmo adotar ZFS.</p>
<p>Em vez disso vamos nessa terceira opção e particionar o disco manualmente. Não vou explicar cada detalhe. Vamos assumir que você está num PC moderno, dos últimos 10 anos. Provavelmente vem com modo legado na BIOS desligado, já suportando boot com EFI. Falando nisso, não deixe de assistir meu video que explica como o Boot de Linux funciona.</p>
<p>Enfim, vamos primeiro criar uma partição EFI que a BIOS vai procurar primeiro. Uns 200 megabytes costuma ser suficiente, deixe 500 se quiser garantir. Depois precisamos de uma segunda partição com um sistema de arquivos legado fácil da BIOS ler. Em Windows costuma ser FAT32, mas aqui podemos escolher o antigo EXT2 mesmo. Eu deixo 500 mega, mas de novo, pra garantir pro futuro caso a kernel cresça de tamanho, deixa 1 gigabyte. Espaço é barato.</p>
<p>Agora usamos o resto do espaço pra partição primária com BTRFS e montando o root nela. Se for um notebook, sempre recomendo criar uma partição encriptada com LUKS, ou no mínimo ter o subvolume de HOME encriptado com LUKS. Não vou explicar nada disso aqui porque é um saco de configurar, então pesquisem no Wiki do Arch sobre isso. BTRFS funciona mais ou menos como um LVM, um gerenciador de volumes lógico. Ele começa como um pool de subvolumes e por default vai criar um subvolume chamado &ldquo;arroba&rdquo; onde vai montar o &ldquo;/&rdquo; root e outro  &ldquo;@home&rdquo; onde vai montar o &ldquo;/home&rdquo;.</p>
<p>Novamente, pesquise sobre LVM e volumes lógicos. Antigamente uma boa prática era separar duas partições: uma pra root onde fica o sistema operacional e outra pra home, daí montamos essa partição como &ldquo;/home&rdquo;. O problema de usar partições é que eles tem tamanho fixo. Uma vez definido não tem como redimensionar fácil. É como no Windows quando você particiona C: e D:. Mas com volumes lógicos, podemos redimensionar on the fly, sem qualquer problema. E BTRFS vai um passo além, subvolumes servem propósito parecido de volumes lógicos mas nem precisamos nos preocupar em redimensionar, eles são dinâmicos.</p>
<p>Enfim, quando terminar de instalar podemos bootar. Vamos acelerar aqui, três, dois, um e pronto. Ubuntu instalado. É muito fácil instalar distros Linux hoje em dia. Só não instala quem tem muita preguiça ou tem um hardware muito, mas muito ruim. Quando Linux não identifica hardware só tem duas opções: ou ele é muito ruim, ou é muito novo ou recém lançado. Se você não comprou um note que saiu este ano, não tem desculpa, é o hardware que é ruim, porque drivers pra Linux são atualizados constantemente.</p>
<p>Se estiver em dúvida num notebook, e tiver a oportunidade de experimentar antes de comprar, leve um pendrive com um Manjaro ou Ubuntu da vida e faça boot pelo pendrive. Veja se ele consegue identificar tudo: wifi, bluetooth, áudio, placa de video. É o melhor teste que pode fazer. Se alguma coisa não for detectada, desista desse modelo. Vai te dar mais trabalho do que compensa.</p>
<p>Pronto, agora vamos listar o arquivo &ldquo;/etc/fstab&rdquo; que o Ubuntu criou. Super simples. Dispositivos de armazenamento são identificados por um UUID. Abrindo o terminal, basta digitar &ldquo;lsblk -f&rdquo; e vai ter uma coluna com UUID. O arquivo &ldquo;/etc/fstab&rdquo; define quais dispositivos vão montar onde durante o boot. Por exemplo, o dispositivo de UUID que começa com ed022 vai montar como root &ldquo;/&rdquo;. Olhem na listagem, é o dispositivo /dev/vda3. O /dev/vda2 de UUID que começa com 294c8 monta como /boot.</p>
<p>A graça começa quando vemos o ponto de montagem de &ldquo;/home&rdquo;. O Ubuntu, pelo menos, separou corretamente um subvolume de BTRFS chamado &ldquo;@home&rdquo;. É uma boa prática. Mas temos alguns problemas. Do jeito que está aqui, não podemos ativar snapshots, porque seu espaço em disco vai desaparecer rapidamente.</p>
<p>Snapshots são possíveis por causa da funcionalidade de copy on write do BTRFS que expliquei antes. Copy on write funciona mais ou menos como branches num Git. Lembra que mostrei que o Git consegue manter o arquivo original, sem perder o conteúdo, mesmo eu gravando outro por cima? Pra fazer isso, obviamente, precisa manter o conteúdo original gravado em algum lugar, certo? Está gastando espaço extra. Diretórios do Linux como /var/cache, /var/log, /var/tmp estão em operação o tempo todo. Mesma coisa /tmp ou arquivo de swap. Se ficar mantendo versão velha deles em snapshots, o espaço livre do seu sistema vai acabar muito rápido sem você entender porque.</p>
<p>Pra resolver é assim: abre o terminal e faça &ldquo;sudo chattr +C /var/log&rdquo;. Isso é change attribute, estamos mudando o atributo do diretório pra dizer pro BTRFS não considerar arquivos nesse diretório pra snapshot. Esse change attribute só vai afetar novos arquivos criados, não os antigos que já existem, sempre se lembrem disso, mas como acabamos de instalar, acho que só isso deve ser suficiente.</p>
<p>Agora vamos instalar o Timeshift. É um programa gráfico pra fazer snapshots periódicos do sistema. Pra iniciantes, vai ser mais fácil do que lembrar os comandos pro terminal. Basta fazer &ldquo;apt install timeshift&rdquo; ou &ldquo;yay -S timeshift&rdquo; no Arch ou Manjaro. Logo que abrir ele vai oferecer pra configurar pra você. Mesmo se não estiver usando BTRFS, a alternativa é usar Rsync, caso esteja usando ext4.</p>
<p>Rsync, é um programa pra sincronizar dois diretórios. Equivalente no Windows é o novo robocopy ou o antigo xcopy. O jeito força bruta, sem a ajuda do sistema de arquivos, pra ter um backup local, é fazendo sincronização de diretórios. O problema é óbvio: vai ocupar espaço pra cacete no seu sistema, porque periodicamente vai duplicar tudo. Sim, se algo der errado e apagar algum arquivo que não devia, vai ter uma cópia. Com a funcionalidade de copy on write e snapshots do Btrfs temos a mesma coisa, mas sem duplicar nenhum arquivo e sem gastar mais espaço do que realmente precisa no mesmo HD.</p>
<p>Tá complicado de entender? Olha só, vou baixar um arquivo qualquer aqui, escolhi a ISO do Ubuntu. Acelerando e &hellip; pronto, baixado. Agora vou no Timeshift e crio um snapshot manualmente. Com o timeshift configurado posso escolher pra  fazer snapshots de hora em hora, e guardar X snapshots por hora, Y snapshots por dia, Z por semana e assim por diante. Depois dêem uma fuçada, mas criei um pra mostrar agora. Snapshots são baratos, pode fazer toda hora. Não importa se você tem mil arquivos ou um milhão de arquivos, diferente de rsync, o tempo de criação de snapshots é quase instantâneo, porque ele não está duplicando nenhum byte, só metadados.</p>
<p>Simulando um acidente, oops, apaguei essa ISO que acabei de baixar. Que droga, vou ter que esperar um tempão pra baixar de novo. Ou não. Vamos no Timeshift e posso navegar pelos arquivos do snapshot. Olha só, se formos na pasta de downloads, a ISO está lá. Pronto, só copiar de volta pra pasta de downloads corrente e tá na mão: arquivo recuperado.</p>
<p>Não ficou claro? Vou dar outro exemplo. Mas pra isso precisamos terminar de configurar o Timeshift. De novo, siga o artigo que deixei linkado abaixo. Diferente do Manjaro que já me dá isso automaticamente, no Ubuntu precisamos manualmente instalar o grub-btrfs a partir do código fonte. Primeiro precisa instalar os pacotes build-essential pra termos compilador, e o git. Daí fazemos clone do repositório e &ldquo;make install&rdquo;.</p>
<p>Também precisamos instalar o pacote inotify-tools e editar a configuração do grub-btrfs com &ldquo;systemctl edit &ndash;full grub-btrfsd&rdquo; e trocar a opção &ldquo;/.snapshots&rdquo; por &ldquo;&ndash;timeshift-auto&rdquo;. Pronto, agora só iniciar o serviço e habilitar pra iniciar automaticamente depois do boot. E finalmente, mais um programa que precisamos instalar a partir do código fonte, o timeshift autosnap. Mesma coisa, só fazer clone do repositório e rodar &ldquo;make install&rdquo; dentro.</p>
<p>O que tudo isso faz? Se ainda não passou por isso, é só uma questão de tempo. Sabe quando estamos no meio do dia e pensamos, ah, vou fazer uma atualização no sistema. &ldquo;apt update&rdquo; daí &ldquo;apt upgrade&rdquo;, reboota o computador e pau, não boota mais. E vai ser justo naquele dia que você tá sem nenhum pendrive por perto, daí não consegue bootar no LiveCD. Você tá de home office e decidiu ir trabalhar do Starbucks, não tem nenhum amigo com computador por perto pra baixar a ISO pra você. Acabou sua tarde. Vai ter que voltar pra casa, achar um pendrive, baixar a ISO e gastar um tempo no stackoverflow tentando voltar um kernel antigo pra bootar de volta na sua máquina. Quem nunca?</p>
<p>E se eu disser que nunca mais vai precisar fazer isso? Vamos simular de novo. O Timeshift já está configurado pra fazer snapshots periódicos. Com isso, caso um dia lembre, puts, eu queria aquele arquivo que apaguei mês passado e achei que não fosse precisar. Meu HD de backup tá em casa, e agora? Sem problemas, só pegar o snapshot do mês passado, que está local na sua máquina. Mas, e na situação quando fizer um &ldquo;apt upgrade&rdquo; e isso quebrar seu sistema? Sem problemas também, aquele pacote timeshift autosnap vai criar um snapshot automático antes de todo &ldquo;apt upgrade&rdquo;.</p>
<p>Daí aquele pacote grub-btrfs vai listar esses snapshots no menu de boot do grub. Oops, Ubuntu por padrão não mostra o menu do Grub. Mas isso é fácil, só editar o arquivo &ldquo;/etc/default/grub&rdquo; e mudar a opção GRUB_TIMEOUT_STYLE de &ldquo;hidden&rdquo; pra &ldquo;menu&rdquo; e a opção GRUB_TIMEOUT de zero pra uns cinco ou dez segundos. Pra atualizar, só rodar &ldquo;update-grub&rdquo;. Vamos ver como funciona.</p>
<p>Pra simular, vamos criar mais um snapshot temporário chamado &ldquo;teste&rdquo; no Timeshift. Como é um snapshot manual, vou rodar &ldquo;update-grub&rdquo; manualmente, mas num &ldquo;apt upgrade&rdquo; não precisaria. Agora vou abrir a loja do Ubuntu e instalar alguns programas. Vamos instalar aqui o Gimp, vai demorar um bocado. Acelerando &hellip; Agora instalamos o Discord, de novo, vamos acelerar. Pronto. Podemos checar no menu, e estão aqui, Gimp, e Discord. Faz de conta que instalei um monte de coisas mas no final mudei de idéia, queria desinstalar tudo e voltar como estava antes. Como faz?</p>
<p>Fácil, só rebootar. Agora aparece o menu do Grub aqui no boot e tem esta nova opção e olha só os snapshots que estávamos vendo no timeshift. Estão vendo a descrição &ldquo;teste&rdquo;? Basta escolher esse ou um snapshot anterior. Entenderam? Posso bootar direto em um snapshot. Esperamos iniciar, e olha só. Cadê o Gimp? Cadê o Discord? Como o nome diz, timeshift, ele fez um shift no tempo, voltamos ao passado, exatamente como estava antes de instalar aqueles aplicativos. Imagine que em vez de Gimp ou Discord fosse um &ldquo;apt upgrade&rdquo; e veio uma kernel quebrada que fode o boot? Basta rebootar, escolher o snapshot anterior e pronto, vai ser como se nada tivesse acontecido.</p>
<p>Abrindo o Timeshift, pra tornar isso permanente, posso fazer um Restore do snapshot antigo pra passar a ser meu sistema corrente. Só isso. Entenderam? Com o Timeshift temos um backup local que ocupa muito pouco espaço e com isso qualquer acidente pode ser facilmente recuperado sem precisar de HD externo, sem precisar esperar copiar tudo de backup nem nada, muito menos reinstalar tudo do zero. É instantâneo.</p>
<p>Claro, isso é uma conveniência, pense como sendo sua última barreira de defesa. Isso não substitui um backup externo. O Timeshift e BTRFS, pra funcionar, depende que seu SSD esteja funcionando perfeitamente. Se o SSD em si der pau, como mostrei no começo do video, isso não vai te salvar. Aí só um backup externo. O que estou recomendando é ter as três coisas que mostrei até agora: se disciplinar a usar Git em todo projeto, pessoal ou de trabalho. Daí ter BTRFS e Timeshift pra consertar acidentes rápidos. E um backup externo pra evitar catástrofes mais sérias. São três níveis de segurança de dados pra ter dar paz de espírito.</p>
<p>E pra finalizar, tem um detalhe importante no caso de BTRFS. Aqui tem a ver um pouco com a Rinha de Backend de novo. Durante toda minha exploração dos projetos dos participantes, fiquei rodando Docker Compose de cada projeto. Eu já fiz um video explicando containers e Docker em particular, não deixem de assistir. Mas vamos entender o problema.</p>
<p>De dentro do projeto de qualquer um dos participantes, vamos rodar &ldquo;docker compose up&rdquo;. Lendo o arquivo docker-compose.yml, vai estar configurado várias aplicações, por exemplo, nginx, postgres. De onde vem esses aplicativos? O Docker vai baixar a imagem do docker.io. É isso que significa esses &ldquo;pulls&rdquo;. Uma imagem não é um único arquivo, é um conjunto de arquivos binários, que chamamos de overlays. Pense como se fossem commits de Git, mas commits binários. Esses overlays são unificados num único sistema de arquivos usando o Union File System ou UFS.</p>
<p>Se rodar o comando &ldquo;mount&rdquo; olha só aqui no fim, vários pontos de montagem usando overlays. Rode o comando &ldquo;docker image ls&rdquo; e temos a listagem das imagens baixadas e geradas localmente. A imagem do projeto em Node.js tá custando 220MB, o nginx tá ocupando quase 200MB, o postgres mais de 400MB. Só esse projeto, pra rodar, precisou gastar quase 1 Giga aqui no meu HD.</p>
<p>Onde ficam esses arquivos de overlays? Dentro do diretório &ldquo;/var/lib/docker/overlay2&rdquo;. Podemos listar como sudo e temos vários diretórios com o SHA256 de cada camada da imagem. Agora experimente testar docker compose de uns 50 projetos. Coisas como postgres e nginx, se forem da mesma versão, vão ser reusados, mas cada imagem de aplicação custa na faixa de 200 MB pra cima. Então 50 vezes 200 vai dar quase 10 Giga.</p>
<p>De tempos em tempos é recomendado rodar um &ldquo;docker system prune&rdquo; pra apagar imagens que não estão sendo usadas. Mas cuidado, se tiver containers, mesmo desligados, associados a uma imagem, ela não vai ser apagada e vai ficar sobrando ocupando espaço a toa. Use o comando &ldquo;docker container ls -a&rdquo; pra listar e apague os que não precisa. No caso de docker compose, quando acabar de testar o projeto, faça &ldquo;docker compose down rmi &ndash;all&rdquo; pra limpar containers, imagens, volumes e network que não for usar mais.</p>
<p>Mas beleza, 10Gb pra 50 projetos parece pouca coisa. O problema é que eu não excluí o diretório do docker da solução de snapshot. E o timeshift vai ficar criando snapshots periodicamente. Pense assim, eu fui baixando docker compose up, um monte de imagens, ao longo de mais de uma semana. Todo dia o timeshift foi fazendo snapshots. Num dia eu fazia docker compose up. No dia seguinte fazia docker compose down, achando que tava apagando tudo, mas na verdade eles estavam sendo guardados nos snapshots.</p>
<p>Depois eu resolvo testar o projeto de novo, faço um novo docker compose up. Vai baixar 1 giga de imagens outra vez. Só que agora tem 1 giga das imagens antigas no snapshot de ontem, e mais 1 giga duplicado no snapshot de hoje. BTRFS tem opção de deduplicação, mas ele não roda  automaticamente, por padrão vai duplicar tudo. Repita isso em diversos projetos ao longo de duas semanas.</p>
<p>Quando terminei de testar tudo, escrevi o script do último video, gravei, editei, e meu sistema começou a ficar estranho. Tudo foi ficando mais lento. No começo achei que era pau do Microsoft Edge, porque ele começou a crashear do nada. Tentei reinstalar e nada. Ué, que diabos tá acontecendo com meu sistema. Eis que comandos de instalar pacotes como yay começaram a dar problema e aí caiu a ficha. Fui checar quanto de espaço eu tinha com &ldquo;df -h&rdquo; e tava dizendo que tava com 100% ocupado na partição primária.</p>
<p>Puts, as malditas imagens de docker! Tentei dar um docker system prune. Mas nada. Continua ocupando 100% do disco. Puts, os snapshots! As malditas imagens ficaram presas em snapshots do passado. Abri o timeshift e apaguei todos os snapshots do passado. Voltei pra verificar o espaço livre, e nada, continuava ocupando. Ué, que diabos.</p>
<p>BTRFS é conservador, ele não vai apagar nada a menos que você realmente diga pra apagar. Apagar snapshots não libera o espaço, pra isso precisa reclamar esse espaço de volta. Deixa eu demonstrar. Podemos rodar o comando &ldquo;btrfs filesystem df /&rdquo;. Uma das pequenas desvantagens de usar btrfs é que ferramentas normais de Linux como o comando &ldquo;df&rdquo; propriamente dito, podem informar valores incorretos.</p>
<p>No nosso Ubuntu de exemplo, estamos usando um total de 25GB. Isso é somando coisas como metadados do sistema de arquivos e tudo mais. Os bytes dos arquivos em si é esse &ldquo;used&rdquo;, pouco mais de 23GB. Ou seja, pelo menos 2GB são só metadados. Você precisa entender essa diferença. Tamanho de um arquivo não são só os bytes dentro desse arquivo. O nome do arquivo ocupa espaço, a informação de data de criação também, atributos como read only, localização no diretório também. Tudo ocupa espaço. E quanto mais avançado é o sistema de arquivos, mais espaço vai ocupar de metadados.</p>
<p>Enfim, se você não tem noção de quanto um sistema recém instalado ocupa, pode pensar &ldquo;ah, 25 giga, ok, faz sentido&rdquo;. Mas lembra que eu fiz alguns testes de snapshots pra vocês verem na prática? E aquele Gimp, e aquele Discord? Eles desapareceram, mas os bytes deles continuam no snapshot antigo. Vamos abrir o Timeshift e apagar todos os snapshots.</p>
<p>Rodando de novo o comando &ldquo;df&rdquo; do btrfs, não mudou nada. Continua indicando 25GB. Foi o que aconteceu na minha máquina quando acabou espaço. Mesmo apagando os snapshots, continuava indicando falta de espaço. Precisa manualmente reclamar o espaço de volta, pra isso usamos o comando &ldquo;btrfs filesystem balance start /&rdquo;. Esse comando é bem demorado. Pode levar mais de hora numa máquina como a minha, mas pelo menos podemos continuar usando o sistema em paralelo.</p>
<p>Enfim, esperamos acabar e pronto. Vamos ver de novo o comando &ldquo;df&rdquo; e olha só, caiu de 25GB pra 10GB! Tinha 15 GB sendo reservado pelo btrfs seja pros snapshots antigos e outras coisas dele. E isso numa instalação nova, praticamente vazia, onde só instalamos 2 programas. Agora imagina no meu caso, com dezenas de imagens de docker em snapshots. Ocupou meu HD inteiro de 1 terabyte. Pois é, foi muito fácil encher 1 tera em poucos dias só de não ter tomado cuidado.</p>
<p>E como faz pra isso não acontecer? Tem que fazer &ldquo;sudo chattr +C /var/lib/docker&rdquo; ou também &ldquo;sudo chattr +C /var/lib/libvirt&rdquo; no caso de estar usando Libvirt pra máquinas virtuais. E de novo, isso só vai se aplicar a arquivos novos. O correto é primeiro limpar esses diretórios, daí mudar esse atributo. Acho que o certo não é aplicar esse atributo no diretório todo, algumas coisas talvez você queira manter snapshots, como volumes de Docker. Fique só atento com isso.</p>
<p>Essa dica é muito importante, porque se você não entender o funcionamento de snapshots no btrfs e começar a usar como máquina de desenvolvimento, com certeza vai ter imagens de docker, e isso com certeza vai destruir o espaço livre em poucas semanas. Você não vai saber que é, nenhum tutorial de como liberar espaço vai funcionar, você não saberia que precisa rebalancear o sistema, e no final iria desistir e reinstalar tudo do zero de novo.</p>
<p>No momento em que estou escrevendo este script, no meu Manjaro, se rodar &ldquo;df&rdquo; ele me diz que está usando 775 giga do meu NVME de 1TB de novo. De arquivos mesmo são só 164 GB. Bora rodar &ldquo;balance start&rdquo; e ir tomar café. Deixei 1 hora rodando e olha só, agora diz que está usando um total de só 166 giga, que faz mais sentido. Ainda tinha uns snapshots velhos, provavelmente com imagens de docker velhas.</p>
<p>Logo que instalei o Timeshift, mostrei que tem como configurar pra guardar snapshots só de hoje, ou da semana, ou até de meses. Se você tem um bom backup externo, isso não é necessário, só o do dia ou até o da semana deve ser suficiente. Quando ver que está acabando o espaço, sabendo que tem tudo no backup externo, pode apagar todos os snapshots do timeshift, menos o último. Daí rodar o rebalanceamento e pronto.</p>
<p>Segurança é um trade off com performance e espaço. Gastamos mais processamento e gastamos mais espaço, pra garantir paz de espírito, essa é a filosofia. E felizmente chegamos num ponto onde o sistema operacional realmente nos ajuda a não perder nada. A única coisa que nenhum software consegue fazer, é consertar um hardware barato porcaria. Contanto que o hardware seja bom, é quase impossível perder dados hoje em dia. Tão importante quanto não perder dados importante é não perder horas ou dias tentando recuperar seu sistema por causa de uma atualização bugada. Basta não ter preguiça, porque as ferramentas estão à nossa disposição e, como demonstrei, são super fáceis de usar.</p>
<p>O meu NAS Synology usa BTRFS também, mas em configuração de RAID. Ele roda scrubbing a cada 6 meses e faz checagens de checksum pra garantir que nenhum erro físico nos HDs me faça perder dados. Isso é só pra quem escolher ter um NAS: lembre-se de configurar pra rodar scrubbing, é essencial, mas isso vem desligado por padrão. Não basta só instalar e achar que vai tudo funcionar por mágica.</p>
<p>Numa distro Linux como Ubuntu ou Manjaro, tenho o Timeshift. Mas no sistema operacional da Synology, ele tem o Snapshot Replication, que também me permite navegar por snapshots antigos e recuperar coisas rapidamente. Olha só, se der qualquer problema, consigo ver como eram meus arquivos de 4 meses atrás. E não sei se perceberam, mas esse sistema de snapshots me dá outra coisa de graça: proteção contra ransomware.</p>
<p>O que é um ransomware? É um malware que você instala ao abrir um powerpoint ou PDF qualquer que veio de um email desconhecido e na verdade era um programa malicioso. É assim que YouTuber perde o canal deles: clicando em PDF falso que não era um PDF. Parem com essa mania de dar duplo clique em tudo. O malware se instala silenciosamente na sua máquina e começa a encriptar seus arquivos. Pode levar dias e você não vai perceber até precisar abrir um desses arquivos e notar que não abre mais.</p>
<p>No final vai aparecer uma janela parecida com essa dizendo algo como &ldquo;Você se fodeu, encriptamos todos os seus arquivos, podemos te dar a chave pra desencriptar mas você vai nos pagar X bitcoins pra isso. Mande pra este endereço e daí te damos a chave&rdquo;. Isso realmente acontece, e não existe garantia nenhuma de receber essa tal chave, mesmo pagando. Se isso acontecer com você, nunca pague, só aceite que você se fodeu. Reze pra ter seguido minhas recomendações.</p>
<p>Ah, mas eu tenho backup externo, então tô seguro, não? Não. Eu disse que leva dias pra esse processo conseguir encriptar todos os seus arquivos. Durante esse período o que você fez? Plugou o HD externo e deixou fazendo backup dos arquivos encriptados por cima dos arquivos bons do backup. Agora seu backup também foi contaminado, parte dele tá encriptado. Perdeu playboy.</p>
<p>É menos ruim se estiver usando Google Drive ou Dropbox ou OneDrive da vida, porque eles tem suporte de snapshot. Mas eu não confiaria muito. Nunca precisei usar o deles então não fucei o suficiente, mas veja esta minha pasta do Obsidian no Dropbox, que é onde edito meus scripts. Tem essa função de Rewind, que é voltar pra trás. Escolho o dia que quero voltar, e ele me mostra as últimas atividades. Daqui posso escolher voltar pra algum ponto do passado, mais ou menos como commits também. É meio um saco porque eu não vi uma interface que permite voltar meu Dropbox inteiro, acho que tem que ir diretório a diretório, não tenho certeza. Enfim, não achei muito intuitivo de usar, mas em última instância, se não tiver mais pra onde recorrer, talvez isso ajude.</p>
<p>Se um ransomware encriptar todos os seus arquivos, mesmo no Dropbox vai estar tudo encriptado, mas se tiver o Rewind de alguns dias pra trás,  pode tentar recuperar. Isso é ok, mas eu não gosto de depender de um serviço que não tenho controle. Por isso essa mesma pasta de Dropbox está em BTRFS no meu NAS, sendo sincronizado com o aplicativo Cloud Sync da Synology. Eu uso os três, Google Drive, Microsoft OneDrive e Dropbox como backups secundários offsite de alguns dados. E o meu NAS tem snapshots de BTRFS ativado pra até 4 meses pra trás.</p>
<p>Ou seja, isso nunca vai acontecer, mas se por ventura algum malware conseguir passar por mim e encriptar meus arquivos, eu só dou risada, volto alguns snapshots pra trás e mando esse hackerzinho de merda tomar no cú. Não pego malware faz pelo menos uns 20 anos, e sempre tenho múltiplos backups de tudo, tanto local quanto offsite, fora daqui. E os arquivos mais antigos que realmente não quero perder já queimei tudo em Blurays Millenium Disk, feitos de material inorgânico que nunca oxidam. Todos guardados no meu cofre. Vem tentar encriptar meus dados seus hackerzinhos de merda.</p>
<p>Segurança é uma questão de oportunidades. Quanto mais você pensar que nunca vai acontecer com você, maiores as chances de realmente acontecer. Eu penso o contrário, sempre penso que estou 100% inseguro 100% do tempo e faço toda a estratégia em cima disso. Segurança funciona em camadas. Se o plano A falhar, tem o plano B. Se o plano B falhar tem o plano C. Isso se chama contingência. Quem não prepara contingências, sempre vai viver dando desculpas. E eu odeio desculpas. Se desculpas funcionassem, não precisávamos de polícia.</p>
<p>Por fim, um comentário que não coube antes no video é sobre como mantenho meu Manjaro. Uma recomendação que dou é usar muito Docker pros seus projetos. Não precisa instalar coisas como MySQL, Postgres, Redis nem nada local. Use tudo com Docker Compose. Primeiro porque isso garante que estamos rodando a versão certa de cada coisa pra cada projeto. Segundo, porque é menos pacotes instalados na sua máquina que o Manjaro vai precisar ficar atualizando. Quanto mais pacotes instalar, maiores as chances de algum upgrade quebrar alguma coisa.</p>
<p>Pelo mesmo motivo, tudo que é aplicativo como Discord, Zoom, o próprio browser Edge que eu uso, Firefox, e muito mais, eu prefiro instalar via Flatpak. Flatpaks são containers, tipo um Docker light. E dá pra usar coisas como FlatSeal pra garantir que nenhum aplicativo tem mais permissões e acessos do que deveria. Podemos fechar um Discord pra não conseguir enxer nada dos seus arquivos, só deixar ele ver a pasta de Downloads, por exemplo. Depois veja este video explicando sobre Flat Seal.</p>
<p>Eu sei que vai aparecer o povo do NixOS nos comentários, mas eu não pretendo usar e nem preciso. Meu sistema é super estável. Raramente tenho problemas, porque eu instalo o mínimo possível. Pra projetos tá tudo em Docker. Aplicativos tudo em Flatpak ou Snap. E coisas mais arriscadas, como games, ou meus experimentos com LLMs e IAs, estão tudo em máquinas virtuais com QEMU/KVM como mostrei neste outro video. Fazendo tudo que mostrei neste video, não precisei reinstalar meu sistema nenhuma vez e nunca perdi nem dados e nem tempo. Qualquer besteira que eu faço, o Timeshift me salva, qualquer besteira maior e os backups no meu NAS me salvam.</p>
<p>Recapitulando: sempre, sempre, inicie um repositório git local no seu projeto, independente de ter no GitHub ou não. Sempre, sempre, tenha no mínimo um HD externo USB da vida, um pouco maior do que o HD do seu PC, e configure o app de Backup que já vem no seu sistema operacional. Não precisa de nada super fancy nem nada. Só habilita. Qualquer backup é melhor que nenhum backup. Finalmente, se quiser aprender mais de Linux e usar o que há de mais novo, instale Manjaro com suporte a BTRFS e configure o Timeshift como eu expliquei. Isso vai tornar você quase invencível, é praticamente um super poder. Se ficaram com dúvidas, mandem nos comentários abaixo, 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>armazenamento-e-backup</category><category>controle-de-versao</category><category>seguranca</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] #144 - Modelagem de Software é Difícil? | "Ver" vs "Enxergar"</title><link>https://akitaonrails.github.io/2023/08/11/akitando-144-modelagem-de-software-e-dificil-ver-vs-enxergar/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/08/11/akitando-144-modelagem-de-software-e-dificil-ver-vs-enxergar/</guid><pubDate>Fri, 11 Aug 2023 15:57:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/SmYQcaP1Jgw"
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;Um dos assuntos que mais assusta iniciante é esse negócio de &amp;ldquo;modelagem de software&amp;rdquo; ou &amp;ldquo;design de software&amp;rdquo; ou &amp;ldquo;arquitetura de software&amp;rdquo;. Vários nomes que parecem ser a mesma coisa e ao mesmo tempo não serem coisa nenhuma.&lt;/p&gt;
&lt;p&gt;Hoje quero &amp;ldquo;filosofar&amp;rdquo; um pouco pra dar uma perspectiva diferente de como se deve encarar esses assuntos e talvez uma estratégia de como realmente entender como isso funciona.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/SmYQcaP1Jgw"
    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>Um dos assuntos que mais assusta iniciante é esse negócio de &ldquo;modelagem de software&rdquo; ou &ldquo;design de software&rdquo; ou &ldquo;arquitetura de software&rdquo;. Vários nomes que parecem ser a mesma coisa e ao mesmo tempo não serem coisa nenhuma.</p>
<p>Hoje quero &ldquo;filosofar&rdquo; um pouco pra dar uma perspectiva diferente de como se deve encarar esses assuntos e talvez uma estratégia de como realmente entender como isso funciona.</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>02:08 - CAP 1 - Livros de Modelagem e Arquitetura - Devo Estudar Todos?</li>
<li>04:14 - CAP 2 - O Problema dos Tutoriais - Modelando Objetos Reais?</li>
<li>13:07 - CAP 3 - Modelando o Mundo Real - Um Exemplo</li>
<li>16:04 - CAP 4 - A diferença de &ldquo;VER&rdquo; e &ldquo;ENXERGAR&rdquo; - Michelângelo e Arte</li>
<li>21:30 - CAP 5 - Mentoria do Mundo Real - Cuidados</li>
<li>24:44 - CAP 6 - Analisando o Mundo Real - Aprendendo Ecommerce</li>
<li>33:11 - CAP 7 - Teoria e Prática - A História do Magento</li>
<li>41:07 - CAP 8 - Recomendação de Livros - Aprendendo com Especialistas</li>
<li>49:17 - 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/magento/magento2"target="_blank" rel="noopener">Magento 2</a></li>
<li><a href="https://github.com/spree/spree"target="_blank" rel="noopener">Spree</a></li>
<li><a href="https://github.com/morinoko/agile-dev-rails6"target="_blank" rel="noopener">Agile Web Development with Rails - GitHub</a></li>
<li><a href="https://pragprog.com/titles/rails7/agile-web-development-with-rails-7/"target="_blank" rel="noopener">Agile Web Development with Rails</a></li>
<li><a href="https://aosabook.org/en/"target="_blank" rel="noopener">The Architecture of Open Source Applications</a></li>
<li><a href="">Rails Summit 2009 - Ilya Grigorik</a></li>
<li><a href="https://www.akitaonrails.com/2009/11/18/conversando-com-luke-kanies"target="_blank" rel="noopener">Chatting with Luke Kanies</a></li>
<li><a href="https://www.akitaonrails.com/2009/05/08/railsconf-09-entrevistas-exclusivas-em-audio/"target="_blank" rel="noopener">RailsConf 09 - Exclusive Audio Interviews | 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>No último video resolvi falar mais sobre bancos de dados. Já tinha feito dois videos antes sobre isso e em todos fui pelo caminho mais fácil: explicar como funcionam por baixo. As engrenagens que possibilitam existir armazéns de dados. Mas explicar implementação não é tão difícil, porque eles já existem e funcionam bem.</p>
<p>O problema que eu venho me esquivando desde o começo do canal é explicar o que a maioria de nós, programadores, considera o problema mais difícil da programação: dar nomes pras coisas. Isso mesmo, não é piada. Em particular, como modelar um problema de negócios em código? O que é um design de software &ldquo;feito direito&rdquo;? O que significa &ldquo;fazer direito&rdquo;? Significa seguir Domain Driven Development? Significa usar a maioria dos Design Patterns? Significa seguir algum framework? Tem algum livro que se possa ler e aprender o que significa isso de &ldquo;modelar&rdquo;?</p>
<p>Durante toda a vida deste canal eu vim flertando com esse assunto, por exemplo quando falei do Livro The Mythical-Man Month, que afirmei ser o mais importante pra se ler. O video de hoje vai ser uma filosofada super longa e provavelmente super frustrante. Frustrante porque eu não vou dar copy e paste de material que já tem online. É só procurar que você acha tudo de patterns, arquitetura, modelagem. O objetivo do video é explicar porque muitos acham &ldquo;difícil&rdquo;, mesmo tendo todo esse material. E no final vou até recomendar outros materiais, então assistam tudo.</p>
<p>O spoiler é que não existe o &ldquo;jeito certo&rdquo; de se fazer software, mas por outro lado, o processo não é aleatório. Existe um balanço fino entre teoria e prática que é onde se encontra nossa profissão. A razão de existir programadores é justamente porque é nossa função decidir o que é &ldquo;melhor&rdquo; pra determinada circunstância.</p>
<p>Lembram do video de Não Terceirize Suas Decisões? O pior programador é aquele que é incapaz de tomar decisões. O melhor programador não é aquele que toma todas ou a maioria das decisões corretas, mas sim aquele que toma decisões possíveis no momento em que precisam ser tomadas, nem antes, e nem depois. E o que vou tentar fazer hoje é ilustrar esses conceitos. Então vamos lá.</p>
<p>(&hellip;.)</p>
<p>Como primeira opinião controversa do dia, vou afirmar o seguinte. A maioria dos livros de modelagem e design, como o Clean Code do Bob Martin, o Domain Driven Development do Eric Evans, o Design Patterns do Gang of Four, o Patterns of Enterprise Application Architecture do Martin Fowler; todos livros excelentes, mas eu acho que são totalmente inúteis no seu primeiro ano pós faculdade. Entenda: você acabou de se formar, ainda tem pouca ou nenhuma experiência em projetos reais ou em projetos pessoais ou open source. Esses livros são perda de tempo.</p>
<p>Pra ficar muito claro: eu não estou dizendo que os livros são inúteis. Muito pelo contrário, eu acho todos eles excelentes e que precisam ser estudados. Mas todos tem um pré-requisito: que você já tenha quilometragem em código. Tanto em fazer seu próprio código quanto lidar com código dos outros, especialmente código legado.</p>
<p>A estratégia é assim: nenhum desses livros você deve tentar ler de cabo a rabo de uma só vez. Folheie alguns capítulos, os iniciais. Veja os temas de cada um pelo índice, leia a primeira página de cada capítulo. Guarde. Vá trabalhar ou treinar com projetos open source. Volte daqui um ou dois meses. Folheie de novo, avance um capítulo a mais, pule pra um capítulo no meio que tem assunto que chamou sua atenção porque é compatível com o que está mexendo no momento. Guarde de novo.</p>
<p>Depois do primeiro ano trabalhando tempo integral com programação, retorne. Leia com um pouco mais de esforço. Releia os primeiros capítulos. Repita. É um processo que deveria te custar os primeiros 3, talvez 5 anos iniciais de carreira. Se fizer direito, talvez comece a entender o que esses autores queriam dizer quando estiver lá pelo quarto ano.</p>
<p>Um dos sinais que você conseguiu mesmo pular de nível junior pra pleno, é quando começa a entender o que os autores queriam dizer, e não está tentando seguir ao pé da letra o que está escrito, sem saber porque. Mas no primeiro ano, esses livros são inúteis. Você ainda não lidou com problemas suficiente pra entender esses porquês. E o importante é o porquê. Agora deixa eu dar uns passos pra trás e explicar porque.</p>
<p>Qualquer um que já tenha tido curso ou lido livro sobre modelagem de objetos já deve ter esbarrado no famoso exemplo dos animais ou veículos. Por exemplo, ambos cachorro e gato são animais. Animal é uma abstração. Portanto, parece óbvio que deveríamos fazer uma classe abstrata chamada Animal, daí podemos derivar sub-classes Cachorro e Gato que herdem de Animal, ou em linguagens mais modernas, que tenham o &ldquo;trait&rdquo; ou protocolo de Animal.</p>
<p>Ou então temos Carro e Caminhão, ambos são veículos, poderia ser essa a classe pai abstrata. Carro e Caminhão herdam de veículo. O problema com todos esses exemplos, primeiro é que eu nunca vi ninguém fazer uma classe realmente chamada Animal. Talvez quem já trabalhou em projetos de biologia ou zoológico. Mesmo se fizer um sistema de uma fábrica ou concessionária de carros, provavelmente vamos ter uma tabela ou classe mais genérica, mais abstrata ainda, como &ldquo;Produto&rdquo;.</p>
<p>O problema de sistemas de verdade é que praticamente todos os conceitos mais importantes são abstratos, não concretos. Por exemplo, o caso mais óbvio, que são os usuários de um sistema. Usuários não são pessoas? Acho que é por isso que todo iniciante começa um sistema pela tabela de usuários, porque é a única coisa que consegue se identificar, é familiar. Ou seja, tem alguma experiência. Mesmo um iniciante sabe que um usuário deve ter atributos como nome, email, telefone, endereço, RG ou CPF e coisas assim.</p>
<p>Mas rapidamente isso complica. Estamos falando dos dados de identificação de um usuário? Ou estamos falando dos dados de autenticação, como login? Nesse caso precisamos de atributos como &ldquo;username&rdquo; e &ldquo;password&rdquo;. E já começa o primeiro erro. Já expliquei nos episódios de segurança e criptografia que não gravamos senha numa tabela. Precisamos de atributos abstratos como &ldquo;salt&rdquo; e &ldquo;hash&rdquo;. Mas e two-factor authentication? Precisamos de um seed pro algoritmo de Time-based One-Time Password ou TOTP. Só se você estudou segurança vai saber disso.</p>
<p>Dá pra complicar mais. Enviar link pra redefinir uma nova senha, caso tenha esquecido. O ideal é fazer uma tabela secundária, ou no banco de dados, ou num servidor de cache como Redis. Com um ID único que só pode ser usado uma vez, e aí o ideal é logar o endereço IP da pessoa que clicar no link, pra fazermos auditoria e ver se parece suspeito. Novamente, estratégias de segurança.</p>
<p>Se deixarmos segurança de lado. Usuário é usuário, certo? Tendo nome, email e coisas assim, tá tudo certo? Não. Digamos que o cadastro é pra parte de logística de um ecommerce. Logística precisa de um endereço. Ótimo, basta pedir o endereço. Mas cuidado, uma coisa é endereço de entrega, outra coisa é endereço de cobrança. Pra você, que é iniciante, só deve conhecer o conceito que uma pessoa só tem um endereço, certo?</p>
<p>Mas não. Uma coisa é o endereço de cobrança, pra mandar nota fiscal, outra coisa diferente é o endereço de entrega, pra onde enviar o produto. Eu posso ter endereço residencial, mas posso querer que a entrega seja feita no meu endereço comercial. Mas e se você se mudar de residência? E se você mudar de emprego? Ambos vão mudar. Ou, e se eu quiser comprar e pagar no meu cartão de crédito, mas mandar entregar como presente pros meus pais? Então preciso conseguir cadastrar endereço dos meus pais. Eu deveria criar um usuário separado pros pais? Como faz?</p>
<p>À primeira vista, endereço parecia ser só meia dúzia de campos na tabela usuário. Mas agora deve começar a ficar mais claro que pode ser uma tabela separada de endereços que precisa de uma chave-estrangeira apontando pra chave-primária do usuário. Mas não é só isso. E se meu ecommerce oferece coisas como pontos por fidelidade? Quanto mais eu comprar, mais pontos eu ganho e com isso posso usar como desconto nas compras? Pontos do usuário, ficam na tabela de usuários? Não? Então onde coloca?</p>
<p>Num sistema empresarial maior, usuários podem ter vários perfis, várias visões. Usuário pode ser funcionário da empresa, e aí vai precisar de atributos como número de PIS/PASEP pra coisas como fundo de garantia.  Usuário pode ser um fornecedor ou funcionário de um fornecedor. Fornecedor tem um cadastro separado, CNPJ, número interno de fornecedor. Usuário pode ser consumidor mesmo, daí vai ter pedidos, entregas, suporte. Usuário é um conceito muito mais complicado do que se pensa e num sistema de verdade, raramente é só uma tabela. Vai ser uma coleção de tabelas e classes. Talvez um sub-sistema inteiro separado.</p>
<p>Só pra dar outro exemplo. Falei de carro. Se você é um iniciante fazendo um ecommerce pra uma concessionária ou um concorrente de webmotors da vida, poderia pensar em fazer uma tabela chamada &ldquo;Carro&rdquo;. Daí coloca atributos como &ldquo;marca&rdquo; tipo Volks ou Fiat, modelo tipo Nivus ou Corsa. Ano, combustível, chassi e coisas assim. É o que você conhece vendo o carro dos seus pais. Mas claro, isso tá longe de completo.</p>
<p>E se quiser vender caminhonetes, ou motos, ou vãs? Melhor criar uma tabela mais genérica chamada &ldquo;Produto&rdquo; então? Os atributos vão ser mais ou menos os mesmos mas podemos colocar uma coluna de &ldquo;tipo&rdquo; pra dizer se é um carro ou moto ou outra coisa. Daí é só ter colunas como preço e já era, certo? Não tá errado, mas não é só isso nem de longe.</p>
<p>Só pra dar um pequeno exemplo: em ecommerces e varejo em geral, existe o conceito de SKUs. Stock Keeping Unit, que é um item distinto de venda, seja de produto ou serviço. É um código alfanumérico. Esse código é importantíssimo pro gerenciamento de inventário, de estoque. Faz de conta, o produto Volks Nivus, tem a versão Comfortline e Highline. A Highline tem 4 cores especiais, 2 cores metálicas, 3 cores sólidas como Preto Ninja ou Branco Cristal ou Cinza Moonstone.</p>
<p>Então o Volks Nivus Highline 2023 Cinza Moonstone tem um SKU único e diferente do Volks Nivus Highline 2023 Preto Ninja. Entende porque? Se você vai na loja, não quer saber se tem o Nivus, quer saber se tem exatamente o Highline 2023 Branco Cristal. Não adianta ter estoque do Preto Ninja, não é ele que você quer. E não adianta ter o Branco do Comfortline, você quer o Highline. O SKU reflete o exato item que você quer.</p>
<p>Mesma coisa com roupa. Ah, tem a camiseta com estampa do Iron Maiden, mas só tem o tamanho M, mas você queria o G. Camiseta Iron Maiden, tamanho M é um SKU e tamanho G é outro SKU diferente. É assim que se modela produtos numa loja. Então, numa tabela de produtos o item que você cadastra não é &ldquo;Volks Nivus&rdquo; ou &ldquo;Camiseta Iron Maiden&rdquo; e sim &ldquo;Volks Nivus Comfortline 2023 Vermelho Sunset&rdquo; ou &ldquo;Camiseta Iron Maiden tamanho PP&rdquo;.</p>
<p>Mas os problemas só começaram. Porque se você é a Volkswagen, vai ter um sub-sistema separado, a da fábrica, com outro cadastro de Volks Nivus Comfortline 2023 totalmente diferente. Pra manufatura vai ter outros atributos como o BOM ou &ldquo;Bill of Materials&rdquo;. Precisa de quais peças pra montar uma unidade? Transmissão, injeção, computador de bordo, disco de freio, etc. Cada um desses itens tem estoque, tem fornecedor, tem leilão reverso no setor de compras, tem tempo de entrega pra receber essas peças.</p>
<p>Com tudo isso temos um custo de fabricação. Isso pode, ou não, estar ligado ao sistema de vendas. Primeiro, isso influi no estoque e disponibilidade. E também influi no sistema de precificação. Preço não é um valor único na tabela de produtos, como muitos iniciantes podem pensar. É o resultado de diversos cálculos. Custo de fabricação é só um dos fatores. Tem coisas com impostos. ICMS que é o Imposto sobre Operações relativas à Circulação de Mercadorias e sobre Prestações de Serviços de Transporte Interestadual e Intermunicipal e de Comunicação.</p>
<p>Faz diferença onde o carro é fabricado, pra onde precisa ser transportado, especialmente se atravessa estados. Existem impostos federais, estaduais, municipais. Tem todo um cálculo de impostos que influi no preço final. Por isso costumava ser mais barato comprar carro em Minas Gerais do que em São Paulo, por exemplo, não sei se ainda é, mas lembro que me falaram que por isso muito carro de empresa que aluga, tem placa de Minas. O preço varia por região. Mas não é só isso, tem cálculo de custo de oportunidade, depende da estratégia da empresa. O importante é lembrar que preços não são estáticos, tem diversos fatores.</p>
<p>Beleza, temos o carro fabricado, temos o preço pro consumidor final, a venda foi feita, agora o carro precisa transitar entre a fábrica de São Bernardo Campo até a concessionária em Belo Horizonte. Pra trafegar precisa da nota fiscal. Pra colocar o caminhão na rua, precisa de rotas. De onde eu puxo informação pra rota? Google Maps? Maplink? Sistema proprietário, já que eu sou grande como uma Volks?</p>
<p>E não é só isso. Se queremos implementar sistemas de pagamentos, que lidam com dados sensitivos como números de cartão de crédito e coisas assim, nos Estados Unidos, temos o PCI DSS, Payment Card Industry Data Security Standard que dita como empresas devem proteger esse tipo de informação. E estou falando &ldquo;Estados Unidos&rdquo; daí parece que não é seu problema. Mas não se esqueça que hoje empresas são multinacionais. Pode ter empresa que começou no Brasil e agora atua internacionalmente, daí precisa implementar essas normas.</p>
<p>No caso específico de privacidade, segurança de dados de consumidores, na Europa temos o GDPR que é o General Data Protection Regulation da União Européia e no Brasil temos o LGPD, a Lei Geral de Proteção de Dados, que é similar. Enfim, o ponto é que não basta só sair modelando tabelas e jogando em qualquer lugar. Cada indústria tem seus padrões e cada país tem suas regulamentações. Precisa seguir. E pior: não é um checklist simples. Algumas coisas você é obrigado, algumas coisas dá um jeitinho, depende de como lidar com o auditor. Muitas nuances não documentadas.</p>
<p>O mundo real é muito mais complicado do que simplesmente pensar em modelagem de dados da forma acadêmica, onde nos preocupamos com coisas como normalização, se queremos terceira forma normal, ou no caso de DDD se estamos falando de Bounded Context, aggregates, domain events. Ou no caso de Design Patterns, se queremos chain-of-command ou mediator. O que eu falei até agora é parte do que chamamos das tais &ldquo;regras de negócio&rdquo; e patterns, arquiteturas, DDD da vida é a &ldquo;descrição&rdquo; da implementação dessas regras.</p>
<p>O problema é que se você não entende a regra de negócio, não interessa discutir se vai escolher um Adapter ou um Proxy, se vai organizar na terceira ou quarta forma normal. Se a regra estiver errada, a implementação vai estar errada, independente se seguiu o livro do Eric Evans ou pé da letra. E falando no livro de DDD, logo na introdução ele dá um exemplo interessante de um sisteminha de entrega de mercadorias.</p>
<p>Digamos que temos as entidades Voyage e Cargo, tipo viagem e mercadoria, onde uma viagem tem muitas mercadorias. O Eric descreve um sistema que tem responsabilidade de associar cada mercadoria com uma viagem, gravar e rastrear esse relacionamento. Até aqui tudo bem. Em algum lugar do código vamos ter um método como esse:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>public int makeBooking(Cargo cargo, Voyage voyage) {
	int confirmation = orderConfirmationSequence.next();
	voyage.addCargo(cargo, confirmation);
	return confirmation;
}</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 porque existem cancelamentos de última hora, uma prática padrão da indústria de entrega é aceitar mais mercadorias do que é possível numa única viagem. É o que se chama de &ldquo;overbooking&rdquo;. Algumas vezes é usado um percentual simples, como 110% da capacidade, em outros casos tem regras mais complexas.</p>
<p>Isso é uma estratégia básica no domínio de entregas que é conhecido de qualquer empresário dessa indústria, mas normalmente nós, programadores, não sabemos disso. O requerimento que vem pra nós poderia ser algo como &ldquo;Permitir 10% de overbooking&rdquo;, o diagrama de classes poderia ficar parecido com isto e o código seria atualizado da seguinte forma:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>public int makeBooking(Cargo cargo, Voyage voyage) {
	double maxBooking = voyage.capacity() * 1.1;
	if ((voyage.bookedCargoSize() &#43; cargo.size()) &gt; maxBooking)
		return -1;
		
	int confirmation = orderConfirmationSequence.next();
	voyage.addCargo(cargo, confirmation);
	return confirmation;
}</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>Ou seja, no código determinamos quanto é o novo máximo com 10% a mais de overbooking e vemos se a mercadoria que queremos adicionar ultrapassa esse máximo. Mas isso cria um problema: agora temos uma regra de negócio escondida, como uma cláusula de guarda, no meio do método da aplicação.</p>
<p>Segundo o Eric, essa regra de overbooking é uma política. Política é outro nome pra um design pattern conhecido como Strategy. Em resumo, esse novo código poderia ser refatorado pra ter uma classe separada de OverbookingPolicy e no método de adicionar a mercadoria na viagem, chamar o método &ldquo;isAllowed&rdquo; dessa nova classes, e assim separar a política numa entidade separada, que documenta melhor essa regra em vez de deixar misturada e escondida com o código anterior.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>public int makeBooking(Cargo cargo, Voyage voyage) {
	if (!overbookingPolicy.isAllowed(cargo, voyage))
		return -1;
	int confirmation = orderConfirmationSequence.next();
	voyage.addCargo(cargo, confirmation);
	return confirmation;
}

class OverbookingPolicy {
	public static double OVERBOOKING = 1.1;
	public boolean isAllowed(Cargo cargo, Voyage voyage) {
		double maxBooking = voyage.capacity() * OVERBOOKING;
		return ((voyage.bookedCargoSize() &#43; cargo.size()) &lt;= maxBooking;
	}
}</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 que eu descrevi dessa forma, deve ter ficado óbvio. &ldquo;Puts, claro, porque eu não pensei nisso antes&rdquo;. Mas o que quero expor aqui é justamente isso: pra nenhum iniciante é óbvio que isso poderia ser um strategy pattern, provavelmente não era óbvio que isso era uma &ldquo;Política&rdquo;. Todo mundo ia ter implementado do primeiro jeito e nunca ia pensar nisso. Até o dia que alguém pedisse pra mexer na regra e mudar de 10% pra 5% por exemplo. Daí iam gastar o dia inteiro tentando achar &ldquo;onde que tá definido isso no código???&rdquo;</p>
<p>Deixa eu dar outro exemplo pra ilustrar mais esse ponto. Eu gosto de quadrinhos, comics, mangá, até já me arrisquei a desenhar como hobby no passado. Todo mundo que fica empolgado com um One Piece ou Naruto da vida já rascunhou seus personagens preferidos em algum pedaço de papel e, se não for completamente iludido, sabe que saiu uma porcaria. Mas tudo bem, o objetivo era passatempo.</p>
<p>Já percebeu como todo iniciante em arte faz as mesmas coisas? Ou é paisagem de casinha com uma árvore, um sol e uma montanha. Ah sim, com uma chaminé, não pode esquecer a chaminé. Ou é close-up de rosto de anime. Agora, tenta desenhar um quarteirão de uma cidade, com ângulo olhando de cima. Ou tenta desenhar uma personagem de anime mesmo, sentada numa mesa com as pernas cruzadas e tomando um café. Vai sair a coisa mais torta do mundo, se sair.</p>
<p>Desenhar prédios pode parecer fácil, só retângulos, mas tente fazer um. Não vai conseguir. Convivemos a vida toda do lado de prédios, andamos todos os dias na frente de vários, por anos. Mas nunca paramos pra realmente olhar, observar, absorver os detalhes e, mais importante, nunca pensamos porque determinados aspectos de um prédio são tão particulares. Nunca tivemos treinamento formal de urbanismo. Não paramos pra pensar em zoneamento, por exemplo. Certas áreas não podem ter prédios comerciais, certas áreas tem limite na quantidade de andares. Quem mora perto do aeroporto de Congonhas sabe disso.</p>
<p>Por isso quando você tenta desenhar uma cidade, sai parecendo desenho infantil de casinha com sol. Pra ficar realista, precisaríamos ter experiência com urbanismo e arquitetura. Mesma coisa a personagem sentada de pernas cruzadas. A parte que mais chama a atenção de iniciantes são rostos. Por isso perde-se muito tempo só desenhando olhos, e com rostos sempre em posições parecidas, em 3/4.</p>
<p>Pra desenhar um corpo direito precisa estudar anatomia. Todo estudante de medicina no primeiro ano sabe isso, mas pra gente que não é médico, talvez nem fôssemos pensar nisso. Vemos pessoas na nossa frente todos os dias, em todas as posições. Mas quando você parou pra realmente observar uma pessoa? Como o tecido da calça interage com o material da cadeira, como o peso da perna cria dobras, como músculos como o biceps femoral e o soleus se mexem quando dobramos a perna?</p>
<p>Quando falamos de arte, imediatamente pensamos em coisas como a Capela Sistina, o magnum opus de Michelangelo Buonarroti. Contém cenas super detalhadas e difíceis como o Último Julgamento ou a Criação de Adão. É um trabalho de mais de 500 metros quadrados. Sabe quanto tempo levou pra pintar aquele monstro? Quatro anos inteiros, de 1508 a 1512, usando tecnologia do século XVI.</p>
<p>Entenda, quando Michelangelo começou o projeto da Capela Sistina ele tinha 33 anos. Mas começou a estudar arte desde muito cedo, provavelmente pelos 13 anos de idade e já como discípulo de Domenico Ghirlandaio. Depois estudou escultura sob a tutelagem de Lorenzo de&rsquo;Medici. Por anos, estudou e produziu pinturas, esculturas e já tinha entregado nada menos que a Pieta e a escultura de David antes da Capela.</p>
<p>Naquela época, a arte não era estruturada e bem dividida como hoje. A maioria dos grandes nomes como Michelangelo, chamamos de polimatas. Mas eles mesmos não se chamavam de &ldquo;polimatas&rdquo;, isso é um termo moderno. Só hoje que dividimos que anatomia é mais coisa de médico, lentes e ótica é mais coisa de fotógrafo, urbanismo é mais coisa de arquiteto, pincel é mais coisa de artista. Antigamente não existia essa divisão. Existia o problema e que conhecimentos a pessoa achava que precisava pra resolver o problema. Ponto.</p>
<p>Um artista estudava seja lá o que precisava pra melhorar sua arte. Um bom artista é crítico do seu próprio trabalho. Se ele nota que toda escultura que faz não se parece em nada com uma pessoa de verdade, que escolha ele tem? Vamos lá dissecar corpos de pessoas mortas pra investigar como a pele realmente funciona, como os órgãos influenciam o movimento e assim por diante.</p>
<p>Não existe isso que um artista só precisa estudar cores e pincel. Não existe isso que um programador só precisa estudar algoritmos e código. Um bom profissional estuda o que for necessário pra ser a melhor versão dele mesmo. Pra finalizar. Eu estava refletindo sobre animes outro dia. Já notaram como é muito comum ter várias histórias que se passam no colégio? Romance sempre é romance entre estudantes no colégio. Aventuras sempre são com adolescentes.</p>
<p>Tirando o cinismo de achar que tudo é fetiche, a resposta pode ser  mais simples do que isso. É só um chute, mas a maioria dos desenhistas de mangá começa a trabalhar logo depois do colégio, ou até abandona o colégio. E esses desenhistas fazem tudo: inventam a história e desenham. O que é a única experiência de vida que eles tiveram até esse momento? O colégio.</p>
<p>Você, iniciante que ainda não entrou na faculdade, ou mesmo recém formado, é a mesma coisa. O que você conhece sobre a vida e o mundo? Nada. Sua única experiência foi o colégio, seus únicos amigos foram do colégio, os únicos problemas que teve que resolver eram relacionados ao colégio. Festas, trabalhos, tudo. Aos 20 e poucos anos, seu conhecimento sobre o mundo é minúsculo.</p>
<p>Parece coisa de boomer dizer isso, mas é verdade: até começar a pagar seus próprios boletos em dia, você ainda é uma criança. Eu já disse isso em outros videos e vou repetir: é impossível ser um empreendedor que vai inovar o ecommerce aos 20 anos. Você nunca trabalhou num ecommerce. É impossível inovar o setor hospilar com 20 anos, você nunca trabalhou como médico. É impossível inovar o setor agrícola com 20 anos, você nunca sequer levantou uma enxada na vida. É impossível inovar qualquer setor com 20 anos, você nunca produziu nada com 20 anos.</p>
<p>E eu volto a esse ponto porque, pra programadores, temos exatamente esse problema quando discutimos modelagem de bancos de dados, modelagem de orientação a objetos, modelagem de arquiteturas. O que significa modelar? Modelar é representar alguma coisa do mundo real de uma forma abstrata. Mas eis o problema: o que você conhece do mundo real? Quase nada. Não tem como modelar alguma coisa que não se conhece.</p>
<p>Antes que alguém vá nos comentários discordar. Óbvio, eu sei que existem exceções. É por isso que se chamam exceções. Com esse disclaimer feito, vale relembrar porque estou cagando regra nesse assunto. Vamos lá, eu trabalhei em projetos de software de vários tipos de negócios diferentes. Telecomunicações, varejo, manufatura, RH, infraestrutura, agências, grandes corporações, startups. Em 30 anos passei por várias indústrias diferentes e por isso consigo comparar o que cada uma faz de diferente. E mesmo assim não posso dizer que sei tudo. Por isso aviso pra desconfiar de qualquer um que queira ensinar sem nunca ter tido experiências similares.</p>
<p>Professores de faculdade, a maioria, não tiveram essa experiência, mas eles estudaram pra profissão de ensino, fizeram pedagogia, psicologia. Eles foram treinados pra ensinar. Alguns melhores que outros, não importa, mas essa é a carreira. Porém, a grande maioria dos que criam cursos online não tem nem perto da minha experiência e nem perto do treinamento de um professor. Portanto o que tem a oferecer? Quase nada. Eles dependem de caçar material na internet, empacotar como se fossem deles e vender pra você com um discurso que parece convincente e promessas que nunca vão ser cumpridas.</p>
<p>Veja o exemplo do Michelangelo que falei. Seu primeiro mentor foi Domenico Ghirlandaio. Quem foi ele? Foi um pintor renascentista de terceira geração, especializado em frescos e fez diversos trabalhos importantes, embora não do calibre do seu aluno. Porém, ele reconheceu esse talento e foi ele quem recomendou Michelangelo pra academia de Medici. Parece ter sido um bom professor, que teve ele próprio uma boa carreira, suficiente pra catapultar seu aluno prodígio.</p>
<p>O ideal é se fosse possível todo aluno ter a possibilidade de ser mentorado por alguém experiente assim. Infelizmente isso não é possível. Não existem mentores suficientes pra todo mundo. Por isso eu digo que o correto, antes de tentar empreender qualquer coisa por conta própria, é iniciar a carreira em empresas de verdade, que tem problemas e soluções de verdade e aprender as outras áreas que não é possível ensinar numa faculdade.</p>
<p>Eu tive vários mentores que nem sabiam que estavam me mentorando. Quando eu tinha que ajudar a resolver um problema de marketing, tive a oportunidade de trabalhar com pessoas treinadas em marketing e entender o básico de como eles pensam. Quando tive que resolver problemas financeiros, tive a oportunidade de trabalhar com pessoas treinadas em contabilidade e finalmente entender coisas cabeludas como impostos. Quando tive que resolver problemas de varejo, tive a oportunidade de trabalhar com pessoas com experiência real em vendas e ver como de fato fechavam negócios. Nenhum curso ensina essas coisas.</p>
<p>O problema de todo programador é que ele assume que a parte mais difícil e mais importante é só a programação, e que todo mundo que não é programador é mais burro que ele. Por isso tem tanta dificuldade de entregar valor. Óbvio, o cliente deveria ter te explicado melhor. Basta o cara do marketing deixar de ser burro e escrever uma especificação melhor que você, o gênio, não precisa perder tempo pensando e pode só escrever seu maravilhoso código em paz.</p>
<p>Falando em ecommerces. Deixa eu dar um exemplo mais concreto. Vou falar de Ruby on Rails porque ainda é a área que eu lembro de mais referências, mas o mesmo exemplo vale pra qualquer outro framework da vida. Um dos primeiros livros que todo mundo que quer aprender Rails usa  é o Agile Web Development with Rails. Originalmente escrito pelo lendário Dave Thomas, mantido atualmente por Sam Ruby. Aliás, excelente nome pro autor de um livro de Rails.</p>
<p>Enfim, o livro é um grande tutorial pra desenvolver um mini-ecommerce à medida que explica como funciona o framework Rails. No fim, temos uma aplicação web que mais ou menos se parece com um ecommerce. Mas lembrem-se, o livro não é sobre como fazer o melhor ecommerce, só em como dá pra usar Rails pra implementar uma versão simples de um. Resolvi procurar no Github e achei o código do que é mostrado no livro, pro Rails versão 6.</p>
<p>Usando a ferramenta Tokei, podemos ver que no final do livro, seu projeto vai ter mais de 100 arquivos de Ruby, quase 2.700 linhas de código. Além de 17 arquivos de javascript que dá mais de 1.300 linhas de código. Fora outras coisas como stylesheets em Sass e um monte de arquivos JSON. Parece bastante coisa né?</p>
<p>Por acaso, no ecossistema Rails existe uma plataforma aberta de ecommerce chamada Spree Commerce. Muito boa por sinal. Eu trabalhei com ela anos atrás e até conheci o autor original Sean Schofield. Na época era um projeto monolítico, mas em anos recentes dividiram em sub-projetos. Inclusive acho que a interface web agora é feita em Next.js e o projeto principal virou mais um servidor de APIs. Vamos ver só esse repositório principal com o Tokei e olha só a diferença: temos mais de 1.300 arquivos de Ruby, mais de 92 mil linhas de código Ruby.</p>
<p>Comparado com o projetinho do livro de Rails, estamos falando de uma diferença de 34 vezes o volume de código Ruby. E isso porque não tem nenhuma lógica de front-end, só a lógica de backend mesmo. Se pegarmos só um arquivo, o da ordem de venda propriamente dita, que ambos chamam de &ldquo;Order.rb&rdquo;. O do livro é super curto, pouco mais de 50 linhas. No Spree tem mais de 700 linhas e isso porque tem um sub-diretório com outros arquivos que sub-dividem ainda mais a lógica como &ldquo;address_book.rb&rdquo; ou &ldquo;checkout.rb&rdquo; ou &ldquo;payments.rb&rdquo;. É ordens de grandeza mais complexo.</p>
<p>Agora vamos fazer uma conta de padeiro. O livro Agile Web Development with Rails tem mais de 600 páginas. Se consideramos que o produto final dele é 34 vezes menos complexo que o backend do Spree. Pra explicar o Spree do zero, o livro talvez tivesse que ter então 20 mil páginas. Tem idéia do que isso significa? Coloca em perspectiva, os 5 livros de A Song of Ice and Fire, que deu origem ao Game of Thrones, male male tem umas 4 mil páginas. Ou seja, precisaria de 5 vezes o volume de Game of Thrones, uns 20 volumes, pra descrever um ecommerce como o Spree do zero.</p>
<p>Deixa eu complicar mais: um Spree é um ecommerce muito bom, mas nem de longe é o mais completo. Um dos mais antigos e populares ecommerces de código aberto é o Magento. Hoje existe o Magento 2. Ele é feito em PHP e rodando a ferramenta tokei, olha só: são 2 MILHÕES de linhas de código PHP. Mais de 200 mil linhas de Javascript. O Magento sozinho, só no javascript, tem o dobro de linhas de código do que Ruby no Spree que, por sua vez, já era 34 vezes maior do que o ecommerce do livro que ensina Rails.</p>
<p>Se em 600 páginas o livro Agile Web Development with Rails conseguiu explicar uma versão simples de ecommerce que dá 2.700 linhas de código de Ruby, sabemos que PHP e Ruby não são equivalentes, mas só por diversão, o Magento, só em PHP é mais de 700 vezes maior. Seriam 700 livros. Seriam 100 coleções de Game of Thrones pra descrever do zero, no mínimo. Conseguem entender porque eu fico tão puto quando alguém me pergunta se tem algum livro que explica tudo? Não tem, é inviável de ter.</p>
<p>E eu vou piorar a vida de vocês. Mesmo um Magento ainda é pequeno se desse pra comparar os repositórios de código de uma Amazon da vida, com todos os seus sub-sistemas internos. O que eu estou dizendo? Entenda essa verdades: você nunca vai saber tudo. É impossível. E isso que eu descrevi é só ecommerce. Vejam meu episódio sobre SAP e seu ERP que tem meia década de idade e roda no coração de toda grande corporação no S&amp;P 500. Lembram? Só no banco de dados são dezenas de milhares de tabelas. Repetindo, dezenas de milhares de tabelas. É impossível saber tudo.</p>
<p>Se alguém ficou desesperado com esses números deixa eu contar o grande segredo: eu, Fabio Akita, nunca li o código do Spree, muito menos do Magento. Já trabalhei em projetos com eles e com outras plataformas proprietárias. Mas nunca li todo o código deles e esse é o ponto: não precisa ler tudo.</p>
<p>Posso estar enganado, mas me dá impressão que muitos iniciantes pensam que quando fazem um curso ou lêem um livro, ou um tutorial, &ldquo;como fazer seu próprio instagram&rdquo; ou &ldquo;como fazer seu próprio Facebook&rdquo;, acham que o código que sai no final já é quase a mesma coisa que o Instagram ou Facebook de verdade, faltando pouquinha coisa.</p>
<p>Ou, que o ecommerce que fez no fim do curso já é quase o Mercado Livre. Ou seja, muitos tem a noção bem errada que existe um curso ou livro que vai te ensinar 100% do que precisa saber pra fazer um produto de verdade do começo ao fim. Eu estou afirmando que não tem, não existe, e não seria prático ter. Seriam literalmente 700 livros de 600 páginas e nem isso seria tudo. Se você não consegue visualizar 700 livros, se colocados de pé um do lado do outro daria mais de 2 metros. Eu duvido que você vá ler uma fração disso na sua vida inteira.</p>
<p>Durante todos os videos do canal vim tentando explicar esse conceito. Deixa eu tentar repetir. Quando você faz faculdade de ciências da computação, em nenhum momento vai ter nenhum curso explicando como se faz um ecommerce, ou como se faz um rede social, ou como se faz um aplicativo de fintech, ou de edutech, ou de saúde, nada disso. Você aprende matérias esquisitas, como algoritmos e estruturas de dados, álgebra linear, cálculo, estatística e probabilidade. Mas onde que ensina como fazer um carrinho de compras ou lista de produtos?</p>
<p>No outro lado do espectro, você pula tudo isso e faz cursos numa Udemy da vida &ldquo;como fazer seu próprio ecommerce&rdquo; e sai com uma versão parecida com o do livro Agile Web Development with Rails que eu mostrei. Parece que é melhor, afinal, você fez um carrinho de compras e uma lista de produtos. Só que eu acabei de demonstrar que não é uma fração de projetos de código aberto como Spree ou Magento, na verdade, é de 30 a 100 vezes menor.</p>
<p>Mas eu também disse que ecommerces de verdade, como uma Amazon ou Magazine Luiza da vida, são muito diferentes do que tem num Magento. Então, como faz pra saber? Eis o ponto que eu falei sobre mentoria: você só vai saber como um Magazine Luiza funciona trabalhando dentro do Magazine Luiza. Só tem como saber como um iFood funciona, trabalhando dentro do iFood. Não tem outra forma.</p>
<p>Mas aí não é o problema do ovo e da galinha? Como eu vou conseguir trabalhar num Mercado Livre, se nenhum lugar ensina como eles funcionam? É isso que você também não está entendendo. Quando uma empresa dessas oferece emprego, nenhuma espera que você saiba como elas funcionam. Elas esperam sim, que você tenha um mínimo de ferramentas pra conseguir se adaptar ao sistema deles.</p>
<p>Por isso eu fiz videos analisando os códigos que o Twitter liberou, o código vazado do Twitch ou a arquitetura do Ingresso.com. Se não assistiu, depois assistam. Eu mesmo não trabalhei em nenhuma delas, mas é o tipo de raciocínio que é possível fazer gastando algumas poucas horas, tendo o conhecimento de base. No episódio do Ingresso.com eu falei da arquitetura de filas e salas de espera, que empresas como Amazon e outros utilizam. No episódio do Twitter explico como embeddings de álgebra linear ajudam a resolver o problema de relevância e recomendação de tweets.</p>
<p>Com a base de estatística e probabilidade, dá pra interpretar dados de monitoramento e chegar em conclusões corretas sobre comportamento dos usuários no site, baseado nos dados de navegação e compra. Com cálculo, dá pra saber como o sistema deveria se comportar, coisas com taxa e velocidade de resposta quando se aumenta o tráfego e quais estruturas de dados podem beneficiar mais certas partes do sistema. Com entendimento de sistemas operacionais e compiladores, dependendo do tamanho da empresa, dá pra trabalhar numa nova linguagem que nem existia, como já aconteceu até em empresas nacionais, como a Totus.</p>
<p>Com essa base, dá pra começar a derivar o resto do conhecimento. Uma vez dentro do sistema é que começa realmente a viver o dia a dia de operações da empresa. A burocracia fiscal, a correria de fulfillment de pedidos nos centros de distribuição, a logística de entrega, o tempo de resposta pra suporte ao cliente, as regulamentações e mudanças em leis que precisam ser refletidas no software. Com o tempo você vai aprender o que em DDD se chama de &ldquo;domínio&rdquo;.</p>
<p>O Domain, em Domain Driven Design, só faz sentido se você sabe o que compõe esse domínio. No exemplo que usei no começo do video, saber que SKU faz parte do domínio de inventário, ou saber que checkout faz parte do domínio de pagamento. E isso que eu acabei de falar não é absoluto. Em outra empresa, checkout pode fazer parte de um domínio maior chamado &ldquo;vendas&rdquo;, ou o checkout é tão complexo que pode ser um domínio por si só. Domínio, as tais &ldquo;regras de negócio&rdquo;, varia de empresa pra empresa, de projeto pra projeto. Não é uma coisa estática. É uma referência móvel.</p>
<p>Muitos iniciantes se perguntam se deveriam estudar coisas como Design Patterns e DDD logo de cara. E sim, acho que todo mundo deveria ler pelo menos uma vez, por cima, mas com o entendimento que sem experiência real, essa leitura não vai ter muita utilidade. Arquitetura, patterns, domínios, são conhecimentos que emergem naturalmente com experiência. Não é um checklist que dá pra seguir passo a passo em qualquer projeto. Estude uma vez rápido, e mantenha os livros por perto pra consultar pontos específicos, e assuma que seu entendimento inicial vai mudar na terceira vez que ler, na quarta vez que reler.</p>
<p>Se ler só uma vez ou de forma superficial, vai cometer erros que muitos iniciantes cometem. Por exemplo, achar que Bounded Contexts é só microsserviços e resolver tudo com microsserviços. Microsserviço é um estilo de arquitetura, já bounded context é um conceito de design de estratégia em DDD, que delineia as fronteiras onde um model em particular é definido e aplicável. Você pode ter bounded context num monolito simplesmente usando namespaces e módulos, por exemplo.</p>
<p>Ou então achar que DDD é basicamente só Event Sourcing e CQRS ou Command Query Responsability Segregation. Algumas partes do sistema podem realmente se beneficiar de um sistema de eventos imutáveis ou separação de escritas e leituras nos modelos. Em outros lugares vai ser overengineering. Não se aplica tudo de DDD em todos os lugares. Como diria Jack Sparrow, é mais um guideline.</p>
<p>Mesma coisa com Design Patterns ou qualquer outro livro de arquitetura. Não é pra usar tudo que se estudou em todo lugar. Saber onde usar é mais importante do que saber como usar. Uma vez que se descobre onde, o como é fácil. Muito antes de entrar nesse tipo de discussão, comece pelo básico que já falei antes: quantas vezes você já tentou baixar e rodar um Spree ou Magento ou um simples Wordpress?</p>
<p>&ldquo;Ah, mas eu sou de Python, não mexo com PHP&rdquo;. Então você é só um idiota. Porque fazer um Magento rodar não é só um problema de PHP, é subir banco de dados. Um postgres não é específico de PHP, em Python também precisa. Precisa subir um NGINX ou Apache. Numa aplicação web de Python também. Precisa saber onde e como guardar segredos como credenciais. Precisa saber como subir servidores de cache, como Memcache. Nada disso é específico de PHP. Toda aplicação web, em toda linguagem, usa os mesmos componentes. Porque você está querendo discutir patterns e arquiteturas se sequer consegue subir uma aplicação na sua própria máquina?</p>
<p>Apesar de um Magento da vida não se parecer com um Mercado Livre, os componentes e arquitetura geral é familiar. Não as linhas de código específico, mas os componentes gerais de infraestrutura, os conceitos de protocolos, redes, sistemas operacionais, configuração e tudo mais. Não é perda de tempo fuçar projetos como esses e nem mesmo as versões mais simples de tutoriais ou cursos, o meu ponto é garantir que vocês entendam que nunca vão ensinar tudo, mas como primeiro passo, tá mais que bom.</p>
<p>Outra coisa importante: um Magento tem 2 milhões de linhas de código hoje. Graças ao poder do Git, que mantém o histórico de tudo, posso facilmente voltar ao passado, pra versão 0.1.0 alpha 89, de agosto de 2014, quase 10 anos atrás, com o comando &ldquo;git checkout -b teste 0.1.0-alpha89&rdquo;. Daí podemos rodar a ferramenta Tokei de novo e olha a diferença, ele tinha mais ou menos a metade do tamanho de hoje, menos de 1 milhão de linhas de código PHP.</p>
<p>Podemos voltar pros primeiros commits, do final de 2011, começo de 2012. Não conheço toda a história do projeto. Pelo pouco que puxei no Google, parece que o Magento 1 foi criado em 2007. Uns 4 ou 5 anos depois a comunidade começou o Magento 2, por volta de 2011 a 2012. É o que os primeiros commits indicam, e a primeira versão estável só foi sair mesmo em 2015. Pelo que entendi, o Magento 2 foi um esforço homérico pra modernizar a plataforma.</p>
<p>De qualquer forma, o código inicial que serviria de base pro Magento 2 já tinha nada menos que meio milhão de linhas de código PHP. Comparado com hoje, é &ldquo;só&rdquo; um quarto do tamanho. Mas é pra dar uma noção. Levou de 2007 a 2012 pra chegar nesse meio milhão e depois quase 10 anos mais pra chegar nos 2 milhões de linhas de código de hoje. O projeto tem milhares de contribuidores. Não é um projeto que você vai varar um fim de semana e fazer igual.</p>
<p>Mas o importante é entender que todo projeto tem um dia 1 com zero linhas de código. Todo projeto de código aberto, todo grande site como uma Amazon ou Mercado Livre. No dia 1, todo projeto realmente se parece mais com o nível de código que mencionei dos tutoriais. Mas eles não ficam parados. Vão sendo testados no mundo real, feedback de usuários de verdade vão chegando, o código vai sendo moldado às necessidades do mundo real, 10 linhas se tornam 1000, que se tornam 1 milhão de linhas, ao longo de mais de uma década. E até hoje, o Magento não &ldquo;acabou&rdquo;, toda semana tem atualizações, novos commits.</p>
<p>Ao longo desse caminho, o Magento, que foi originalmente criado por uma empresa chamada Varien Inc depois virou uma entidade própria. Por volta de 2011 parece que o eBay entrou em cena e adquiriu uma parte da empresa, com objetivo de integrar com o Paypal. Mas em 2015, eBay e Paypal se tornaram duas entidades separadas e o Magento também foi separado. Daí entra a Adobe em 2018 que adquire a empresa. Hoje ela faz parte do Adobe Experience Cloud e pra contribuir no projeto você precisa assinar um acordo de Licença de Contribuidor.</p>
<p>Eis outro exemplo de como o software foi sendo adaptado pra responder a essas ações externas. Em 2007, ela não foi planejada pra fazer parte da plataforma da Adobe. O cloud da Adobe nem existia nessa época. Ninguém sabia que no meio do caminho ela iria desviar pra dentro do eBay e Paypal e nem que iria se separar poucos anos depois. O software vai se adaptando a tudo isso. A mesma coisa acontece com a plataforma de toda grande empresa. É assim que aos poucos vai virando um repositório de milhões de linhas de código. Nada disso é planejado com antecedência.</p>
<p>Modelagem é uma das respostas a esse tipo de coisa. Em algum momento, a prioridade talvez tenha sido Paypal. Em outro momento a prioridade passou a ser Adobe Cloud. Prioridades de desenvolvimento também ditam a arquitetura e arquitetura dita a modelagem. Modelagens que faziam sentido em 2011 provavelmente não fazem mais sentido em 2018. Entendam isso: software nunca está &ldquo;pronto&rdquo; ou &ldquo;finalizado&rdquo;. Se ele faz sucesso, é um trabalho constante de adaptação e evolução. Modelagem e arquitetura tem que ser respostas a essas evoluções. Elas emergem do software.</p>
<p>Todo livro, tutorial ou curso, vai simplificar isso. Não é um defeito, é uma limitação do pouco tempo. Como ilustrei até aqui, se tentar documentar tudo, precisaria fácil de centenas de volumes de livros. Ninguém ia conseguir estudar tudo isso. Nenhuma das técnicas de nenhum livro de DDD ou Design Patterns tem as respostas do que fazer em cada um desses grandes episódios. Elas servem de base pra adaptação, mas não tem nenhuma resposta pronta pra nenhum problema de negócios.</p>
<p>Do meu ponto de vista, eu diria que livros DDD ou qualquer outro de modelagem e arquitetura servem pra nos dar um vocabulário pra tentarmos nos comunicar. Tudo é abstrato em software. Não temos parâmetros físicos que todos conseguem concordar, como peso, volume, ou qualquer coisa assim. Software se chama software porque não é &ldquo;hard&quot;ware.</p>
<p>Uma simples classe de pagamento pode significar coisas extremamente diferentes pra programadores com experiências diferentes. Então tentamos racionalizar e normalizar esses conhecimentos via um vocabulário de modelagem e arquitetura pra começarmos a dar passos em busca de algum consenso, em algumas partes.</p>
<p>Pra não ficar só na filosofia, agora eu queria falar especificamente de alguns livros que acho que podem ajudar um pouco. Novamente, não são livros pra ler de cabo a rabo de uma só vez, e nem só uma vez. São livros pra se estudar um capitulo que tenha um assunto que te interessa no momento, voltar quando tiver mais experiência nesse assunto, e durante sua vida profissional ou acadêmica, quando esbarrar nos outros assuntos, vá indo um capítulo de cada vez.</p>
<p>Eu mesmo não conhecia esses livros até pouco tempo atrás, quem me recomendou foi meu sócio Talysson, que é responsável pelo programa de mentoria da minha empresa, ele mesmo um excelente programador. O primeiro livro se chama The Architecture of Open Source Applications. Ele é dividido em dois volumes. Cada capítulo é como se fosse um artigo sobre um projeto de código aberto escrito pelo próprio autor ou alguém bem experiente e reconhecido.</p>
<p>Por exemplo, o capítulo 11 do primeiro volume é sobre LLVM, que eu já expliquei em vários videos aqui do canal, como no video da Apple e GPL. E o autor desse capítulo é ninguém menos que Chris Lattner, um dos melhores engenheiros dos últimos 20 anos. LLVM tá por trás de todos os compiladores e transpilers modernos incluindo shaders de GPUs e programação de tensors pra IA.</p>
<p>O capítulo em si é super introdutório. Se você assistiu meu episódio  e outros como o de compiladores versus interpretadores, a maior parte tá lá. Ele não explica tudo, nem explica em detalhes, mas é um bom capítulo pra quem nunca parou pra ler nada sobre o assunto. A intenção é que cada autor explique porque tomou certas decisões de design do software em que trabalhou. Coisas que hoje achamos óbvio, mas que pra época não era tão óbvio assim.</p>
<p>No volume dois, capítulo 14 temos o projeto NGINX, que eu também já falei várias vezes em episódios como Tornando sua App Web mais Rápida. O autor é Andrey Alexeev, um dos co-fundadores do NGINX. E de novo, é um capítulo bem curto, onde ele fala o óbvio: porque alta concorrência é importante, ou porque não usar Apache. Hoje, é óbvio, mas 20 anos atrás não era e ele explica porque o NGINX foi feito da forma como conhecemos hoje.</p>
<p>No capítulo 18 do volume II encontrei um nome conhecido. Luke Kanies, um dos criadores do Puppet. Hoje em dia, quando falamos de infraestrutura como código, lembramos de Kubernetes, Terraform, ou Ansible, mas Puppet foi um dos precursores, quando codificar infra não era óbvio. Eu entrevistei ele pro meu blog em 2009, vou deixar o link nas descrições.</p>
<p>Já no capítulo 15 do volume 1, outro nome conhecido, Francesco Cesarini. Todo mundo da comunidade Erlang e Elixir conhece esse nome, um excelente educador e evangelista. E foi num workshop dele que eu tive meu primeiro contato com Erlang, também em 2009. Você também pode ter seu primeiro contato com ele nesse capítulo. Enfim, todos os autores desses livros são excepcionais. Eu seria feliz de ser uma fração de qualquer um deles.</p>
<p>O terceiro livro dessa série é o The Performance of Open Source Applications. É o mesmo formato, cada capítulo com um autor que é reconhecido no assunto, mas focado na parte de performance. Logo no primeiro capítulo temos Ilya Grigorik falando sobre alta performance de rede no Chrome. Ilya é outro conhecido de longa data da comunidade Ruby, que eu trouxe pra RailsConf Brasil 2009. Daí ele foi pro Google, hoje está no Shopify e ensinou nossa comunidade sobre métricas, como medir, as melhores ferramentas, quais as prioridades de otimização, e ajudou a definir muito dessa área de performance junto com a W3C. Todo desenvolvedor web deveria ler alguns dos artigos dele sobre performance.</p>
<p>O quarto e último livro é um pouco mais prático e pode servir pra vocês se exercitarem em linguagens diferentes. Se chama &ldquo;500 Lines or Less&rdquo;, cujo objetivo é desenvolver uma mini aplicação em 500 linhas ou menos. Por exemplo, no capítulo 19, Audrey Tang nos ensina a fazer uma mini planilha, tipo Excel, com Angular.js. Ou Allison Kaptur que ensina a fazer um mini interpretador de Python, em Python. Se estava procurando exercícios, vale explorar esses capítulos. De novo, nenhum deles é uma aplicação completa de verdade, só os princípios pra quem nunca parou pra pensar &ldquo;hm, como eu faria um servidor web simples do zero?&rdquo;, esse é o capítulo 22.</p>
<p>Eu não procurei mas acho que nenhum desses livros tem em português. Mas neste site, que vou deixar o link na descrição abaixo, tem tudo gratuito online. Basta abrir seu navegador e começar a explorar. Eu deixaria uma aba permanente só pra ela, pra não se esquecerem de voltar de tempos em tempos. Como disse antes, não é pra ler tudo de uma só vez. Em particular, o &ldquo;500 Lines or Less&rdquo;, é pra abrir um editor de texto e realmente tentar codificar, senão não vai servir pra nada. Alguns capítulos vão usar linguagens que talvez nunca tenham tentado, como Clojure. É uma boa desculpa pra experimentar. E mesmo se não gostar, outro bom exercício é repetir o mesmo exercício em outra linguagem.</p>
<p>Todos os livros eu diria que são adequados pro nível junior até pleno. De pleno pra senior, diria que são textos fáceis. E mesmo assim talvez tenha assuntos que vocês não tenham parado pra estudar ainda. Por exemplo, no volume 1 do The Architecture of Open Source Applications, o Russel Bryant fala do lendário Asterisk. Se nunca trabalhou em projetos de voz sobre IP, antes de existir coisas como Zoom ou Google Meet, talvez nunca tenha ouvido falar. Mas muita gente ainda usa. É tipo um PBX digital. Eu mesmo nunca mexi pessoalmente, mas trabalhei próximo de projetos que usavam, então é um pouco familiar pra mim. Só porque uma pessoa é considerada sênior numa coisa, não quer dizer que é sênior em tudo. Em Asterisk eu não sou mais que um junior.</p>
<p>O maior problema no estágio inicial é que você não sabe o que não sabe. Livros como os que recomendei e projetos de código aberto, como um Magento da vida, são ótimas portas de entrada pra molhar os pés e começar a se familiarizar com código mais complexo e mais real do que tutoriais ou cursos. Não tem nenhum problema aprender via cursos, mas não fique obcecado em achar que quantidade de tutoriais ou cursos vai determinar sua qualidade. Porque não vai. Depois de um certo ponto, sem experiência, você vai estagnar ou pior, só ter a ilusão que está aprendendo. E experiência não é só no trabalho. Você nunca vai parar de estudar e nunca vai saber tudo. Essa é a carreira.</p>
<p>Pra saber se aprendeu mesmo, precisa contribuir código em projetos. Primeiro em exercícios sozinho na sua própria máquina, depois, se possível, em projetos de código aberto mais simples, ou já direto em projetos fechados de empresas. Se puder escolher, escolha empresas que tem desenvolvedores sêniors em quem se espelhar, idealmente que podem te mentorar. Mentorar não é professor e aluno, onde você como aluno fica passivo só esperando alguém te ensinar alguma coisa. É se virar pra fazer e alguém te dar orientação e feedback. A melhor coisa é alguém apontar o que você fez de errado quando achava que não tinha nada errado. Ou seja, apontar o que você não sabia que não sabia.</p>
<p>E tire da cabeça que só tem valor conseguir emprego numa empresa de nome grande como Amazon. Não. Qualquer empresa que tenha software é oportunidade de aprendizado. Não importa se o software é &ldquo;legado&rdquo;, antigo. Você nunca programou nada real. Não importa se é na lojinha da esquina ou no Magazine Luiza. Todo software é software. Basta não ficar 20 anos no mesmo lugar, fazendo a mesma coisa. Mas também, menos de 1 ano não é proveitoso. Tem que ter um período pra aprender tudo que puder aprender, em particular, tudo que você ainda não sabe que não sabe.</p>
<p>Como disse lá no começo, esse episódio poderia ser um pouco frustrante. Porque não tem uma resposta simples pra dizer como aprender a modelar, ou decidir arquiteturas, ou fazer um bom design de software. Sua versão 1.0 de tudo vai ser sempre ruim, e isso não é um defeito, é simples falta de experiência. À medida que se trabalhar em empresas ou participar de projetos de código aberto e se exercitar mais, os gaps de conhecimento vão ficando aparentes. E você vai começar a finalmente entender o que não sabe, pra conseguir ir atrás de saber.</p>
<p>A idéia não era tentar dar uma resposta definitiva, só abrir os conceitos pros iniciantes terem uma noção melhor do que tem pela frente. E o conceito principal é que você nunca vai saber o que é um &ldquo;bom software&rdquo; sem antes fazer muito mau software e lidar com mau software dos outros. Não existe um checklist que define todas as características de um bom software. Bom é relativo ao que se espera daquele software. E tudo que chamamos de modelagem ou design, são meros vocabulários pra tentar descrever essa abstração que chamamos de software. 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ê, até mais.</p>
]]></content:encoded><category>arquitetura-de-software</category><category>engenharia-de-software</category><category>aprendizado</category><category>akitando</category></item><item><title>[Akitando] #143 - Discutindo sobre Banco de Dados - Dos primórdios a Big Data</title><link>https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/07/22/akitando-143-discutindo-sobre-banco-de-dados-dos-primordios-a-big-data/</guid><pubDate>Sat, 22 Jul 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
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 se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.&lt;/p&gt;
&lt;p&gt;Este video complementa meu video anterior &amp;ldquo;Fiz um Servidor de SQL?&amp;rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Bfm3Ms2cTg0"
    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 se pode ser um bom programador sem entender de verdade como bancos de dados funcionam. E não se pode entender banco de dados sem entender algoritmos e estruturas de dados. E hoje vou mostrar como dos primeiros bancos de dados dos anos 80 chegamos aos SQL e NoSQL atuais.</p>
<p>Este video complementa meu video anterior &ldquo;Fiz um Servidor de SQL?&rdquo; e outros videos que vou referenciando ao longo deste. Anote tudo e assistam depois.</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:15 - CAP 01 - O que é um Banco de Dados? A era do dBase</li>
<li>00:10:04 - CAP 02 - Procurando com Índices - NDX e B-Tree</li>
<li>00:18:53 - CAP 03 - Do DBF ao SQLite3 - O banco mais usado do Mundo</li>
<li>00:23:42 - CAP 04 - Tudo são Árvores B - A estrutura mais importante da computaçãot</li>
<li>00:28:51 - CAP 05 - JSON no SQL?? Você não está entendendo</li>
<li>00:31:45 - CAP 06 - Proteção e Consistência - As garantias dos bancos</li>
<li>00:35:38 - CAP 07 - Do xBase a Cliente-Servidor - As 3 Camadas</li>
<li>00:38:59 - CAP 08 - Programando Errado - Você não sabe usar o servidor</li>
<li>00:45:26 - CAP 09 - Constraints - Usando do jeito certo</li>
<li>00:48:13 - CAP 10 - Monitorando - Sempre monitore tudo!</li>
<li>00:50:23 - CAP 11 - Big Data - Virando gente grande</li>
<li>00:53:40 - CAP 12 - Dividindo pra Conquistar - Acelerando seu App Web</li>
<li>00:56:28 - CAP 13 - Big Data Parte 2 - Por que NoSQL?</li>
<li>01:00:39 - CAP 14 - Teorema CAP - As partes importantes</li>
<li>01:07:12 - CAP 15 - Estratégias de Chaves Primárias - Chaves Surrogadas</li>
<li>01:11:25 - CAP 16 - Hashing Consistente - Sharding</li>
<li>01:18:42 - CAP 17 - O Problema de Cloud - Calculando Precificação</li>
<li>01:24:51 - CAP 18 - Recomendação pra Iniciantes - Arroz com Feijão</li>
<li>01:27:39 - 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://www.heroku.com/postgres"target="_blank" rel="noopener">Heroku</a></li>
<li><a href="https://aws.amazon.com/elasticache/"target="_blank" rel="noopener">Amazon ElastiCache</a></li>
<li><a href="https://aws.amazon.com/rds/"target="_blank" rel="noopener">Amazon RDS</a></li>
<li><a href="https://aws.amazon.com/pt/rds/aurora/"target="_blank" rel="noopener">Amazon Aurora</a></li>
<li><a href="https://aws.amazon.com/s3/storage-classes/glacier/"target="_blank" rel="noopener">Amazon S3 Glacier Storage Classes | AWS</a></li>
<li><a href="https://www.datastax.com/products/datastax-astra"target="_blank" rel="noopener">Astra DB | DataStax</a></li>
<li><a href="https://www.scylladb.com/product/scylla-cloud/"target="_blank" rel="noopener">Scylla Cloud</a></li>
<li><a href="https://www.sap.com/products/technology-platform/hana.html"target="_blank" rel="noopener">SAP HANA Cloud</a></li>
<li><a href="https://www.oracle.com/big-data/big-data-sql/"target="_blank" rel="noopener">Big Data SQL Cloud Service | Oracle</a></li>
<li><a href="https://azure.microsoft.com/en-us/products/azure-sql/database"target="_blank" rel="noopener">Azure SQL Database</a></li>
<li><a href="https://cloud.google.com/sql"target="_blank" rel="noopener">Google Cloud</a></li>
<li><a href="https://www.mongodb.com/pricing"target="_blank" rel="noopener">Pricing | MongoDB</a></li>
<li><a href="https://valentina-db.com/en/valentina-sqlite-server"target="_blank" rel="noopener">Valentina SQLite Server</a></li>
<li><a href="https://calculator.aws/#/addService/DynamoDB"target="_blank" rel="noopener">Configure Amazon DynamoDB (calculator.aws)</a></li>
<li><a href="https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/"target="_blank" rel="noopener">CAP Twelve Years Later: How the &ldquo;Rules&rdquo; Have Changed (infoq.com)</a></li>
<li>[Dynamo: Amazon’s highly available key-value store - Amazon Science](<a href="https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store"target="_blank" rel="noopener">https://www.amazon.science/publications/dynamo-amazons-highly-available-key-value-store</a></li>
<li><a href="https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-json/"target="_blank" rel="noopener">PostgreSQL JSON Tutorial (postgresqltutorial.com)</a></li>
<li><a href="https://github.com/azadkuh/sqlite-amalgamation"target="_blank" rel="noopener">GitHub - azadkuh/sqlite-amalgamation: The SQLite amalgamation mirror with cmake</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>Desde que comecei o canal, banco de dados é um dos temas que eu mais queria discutir, mas vim postergando pra poder explicar fundamentos antes. O objetivo não é ensinar SQL ou MongoDB nem nenhum outro em detalhes, é dar perspectiva pra quem está iniciando entender porque diabos existem tantos tipos diferentes de bancos de dados e quando se deve considerar usar um em vez do outro. Vai ser um video mais alto nível, sem tanto código em si, mas sim dar insights pra vocês pesquisarem nas respectivas documentações depois. Vou começar nos primórdios e só na metade vamos começar a cair em SQL e NoSQL. Então, vamos lá.</p>
<p>(&hellip;)</p>
<p>Infelizmente, se você estava esperando um video sobre modelagem, modelo entidade-relacional, ORMs, design patterns e coisas assim, ainda não é este video. Objetivo é falar mais o que acontece por baixo dos panos, as tecnologias que formam um banco de dados. Eu acho que modelagem só é efetiva se você sabe como a plataforma por baixo realmente funciona, senão modelagem vira só superstição e você só acha que sua modelagem é boa sem realmente saber o que ela causa por baixo. Tudo que você não sabe como realmente funciona vira superstição.</p>
<p>Sempre falo que uma das vantagens que eu tive, foi conseguir testemunhar as versões 1.0 de quase tudo que surgiu nos últimos 40 anos. Se você pegar um banco de dados moderno, como um DynamoDB da AWS, ou um Postgres, eles tem dezenas de funcionalidades que não são imediatamente óbvios pra que foram criados. Sem saber isso, você acaba tentando usar nos lugares errados ou acha que são ruins, ou acha que você que é burro e não consegue usar.</p>
<p>Pense por um segundo, o que é um banco de dados? À primeira vista poderíamos dizer que é um programa que facilita e gerencia o armazenamento de dados estruturados. Pense um armário com gavetas, cada gaveta com um tipo de formulário. As gavetas seriam nossas tabelas e o armário seria o banco de dados. Acho que é parecido com o que a maioria de vocês deve imaginar.</p>
<p>Pra iniciantes, acho que vale a pena retornar aos fundamentos. Por isso sempre falo tanto que é importante aprender a fundação, como algoritmos e estruturas de dados. O problema é que a estrutura mais simples de aprender e por causa disso comum de usar são listas. Essas seriam as gavetas. Em particular listas ligadas ou arrays puros. E se escolher partir desse ponto, vai ter problemas. Vamos entender.</p>
<p>O banco de dados mais simples de todos, um armário só com uma gaveta, é um mero array em memória com registros, como um struct ou uma instância de uma classe, um objeto. Lembram como um array é estruturado em memória? Um array de inteiros com 10 elementos, provavelmente tem 10 vezes 64 bits ou 8 bytes, ocupando 80 bytes no total. Todo array tem elementos de mesmo tamanho fixo, sempre.</p>
<p>Se eu quiser pegar o 5o elemento desse array, basta contar que tenho 4 elementos antes, vezes 8 bytes, então 32 bytes pra frente, vou achar o 5o elemento. Se eu souber qual posição do array quero encontrar, independe se está no começo ou no fim, o tempo de acesso é o mesmo, tempo constante. O array pode ter 10 elementos ou 10 mil elementos. Leva mais ou menos o mesmo tempo pra acessar qualquer posição.</p>
<p>Quando todos os meus dados cabem na memória RAM, toda operação é simples. Mesmo se eu não souber a posição, na pior das hipóteses faço força bruta via um loop do primeiro até o último elemento, até encontrar. E isso seria de longe, o jeito mais tosco de se procurar alguma coisa numa lista, mas pra conjuntos de dados pequenos, quase ninguém nota a diferença. Mas deixa eu repetir: procurar dados numa lista com um grande loop e if dentro é o jeito mais absurdamente horrendo e inaceitável. Vou repetir de novo pra ficar claro: se você está fazendo um loop pra procurar itens numa lista, você é insano!</p>
<p>Isso porque todo programador, especialmente iniciantes, sempre lidam com poucos dados. É muito difícil entender complexidade de algoritmos e diferença nos tempos de processamentos se só tem cem, mil elementos. Só quando começar a lidar com centenas de milhares, milhões de elementos, que vai começar a entender a diferença. Já vou chegar nisso.</p>
<p>Enfim. Imagine que lá no começo da computação, todo mundo lida com poucos dados comparado com hoje. Por isso as primeiras soluções são simples. Um array com elementos de tamanho fixo é a forma mais fácil de pensar. E quando acaba a memória? Digamos, uma lista de produtos ou lista de clientes num ecommerce? Obviamente não cabe tudo em RAM. Daí temos que pensar em gravar esses dados em disco. E a forma mais simples de fazer isso é o bom e velho arquivo append only, onde vamos adicionando novos elementos no fim do arquivo.</p>
<p>É fácil. Pense num registro como uma struct de C, ou uma classe de Javascript ou Python, só que garantindo que todos os elementos tenham tamanho fixo. Determino uma struct com o primeiro campo sendo o nome completo, com umas 100 letras, onde cada uma é representada por um código UTF-8, de 8 bits,  100 bytes no total. Depois temos um número de telefone, com DDD de 3 caracteres, um prefixo de 3 números como era antigamente e um sufixo de 4 números, mais parênteses e hífen, total de 13 letras ou 13 bytes.</p>
<p>Temos um total de 100 bytes mais 13, ou 113 bytes. O primeiro registro vai ocupar o começo do arquivo começando no endereço zero e indo até o endereço 112 bytes. O segundo registro começa no byte 113 e vai até 225 e assim por diante. Posso abrir o arquivo, ir até o final e gravar mais um registro. E assim fica fácil sair cadastrando novos registros.</p>
<p>De curiosidade, queria aproveitar pra mostrar a segunda linguagem e ambiente de programação que aprendi depois do Basic, quando eu era pré-adolescente. Acho que era 1990 e tinha uns 13 ou 14 anos. Meu pai tinha pedido pro meu tio trazer um PC do Paraguai, era um PC-AT 286 com 1 megabyte de RAM, 10 ou 20 mega de HD. Veio instalado com MS-DOS 3.0 e meu tio tinha deixado também o programa que ele usava pra trabalhar: o dBase III Plus, ainda da antiga empresa Ashton-Tate.</p>
<p>E era só isso que tinha instalado. Não tinha internet naquela época. Eu não tinha livro nem nada. Por acaso um outro primo meu tava estudando isso e me emprestou uma apostila do curso que  tava fazendo. Foi meu primeiro PC, então só de ver ligado e ter um teclado que podia usar, em vez de uma máquina mecânica de datilografia, já achava incrível. Minha primeira ideia foi copiar a apostila do meu primo. Eu redigitei tudo do zero acho que no Wordstar.</p>
<p>Este é o Wordstar. Ele tem um aspecto esquisito porque tá configurado no modo de 50 linhas que o VGA permite, em vez de 25 linhas de CGA. Essa versão é uma das últimas que saiu pra DOS, acho que a 7.0, daí tem ajudas que as versões iniciais não tinham, com um menu completo com todas as opções. Senão a única forma de usar era lembrar dos atalhos como control mais P e B pra iniciar modo negrito, daí repetir control P e B pra voltar pro modo normal. Control P e Y pra itálico e assim por diante.</p>
<p>Era tão moderno que tinha até um Preview em modo gráfico pra mostrar como ficaria na impressora. Essa época estávamos na transição do modo texto de DOS pro modo gráfico de Windows 3. Muitos editores de texto atuais como o próprio VS Code ou Obsidian da vida tem modo de ficar focado que deixa o texto no centro e esconde toda distração. São inspirados em editores antigos como o Wordstar. Esse é o editor que o George R. R. Martin diz que usa pra escrever Game of Thrones.</p>
<p>Enfim, pequena tangente pra mostrar como eu copiei a apostila de dBase. Mais pra frente consegui comprar livros de verdade. Acho que nenhuma linguagem comprei tantos livros pra aprender quanto dBase e seu sucessor, o Clipper. Olha só os que ainda guardei. A razão de tanto livro é que antigamente não tinha Google nem Stackoverflow, lembrem-se disso. Depois que surgiu a Web, já não precisei de tanto livro assim.</p>
<p>Abrindo o dBase, tem um REPL, um interpretador de comandos, que nem quando se digita <code>node</code> ou <code>python</code> e dá pra começar a digitar comandos. Mas também tem um sistema de menus aqui pra facilitar. Olha só, posso escolher a opção de criar um novo banco de dados. Digito o campo &ldquo;nome&rdquo;, tipo &ldquo;character&rdquo; com 100 letras. Depois o campo &ldquo;telefone&rdquo;, tipo &ldquo;character&rdquo; também, com 13 letras. Vou aproveitar e adicionar um campo de &ldquo;empresa&rdquo; com 100 letras. Pronto. Daí quando salvo já me deixa começar a cadastrar.</p>
<p>Vamos lá, quero me cadastrar aqui, &ldquo;Fabio Akita&rdquo;, telefone bla bla bla, empresa Codeminer 42. Agora vou cadastrar &ldquo;Cecil Wayne&rdquo;, o criador do dBase que depois se tornaria vice-presidente da Ashton-Tate. Finalmente, vamos cadastrar Brian Russell, que criou o compilador Clipper, pra Nantucket Corporation. Nenhuma dessas empresas existe mais.</p>
<p>A Ashton Tate foi adquirida pela Borland que depois viraria Inprise, depois Embarcadero e a divisão CodeGear que ainda mantém o Delphi, se não me engano. A Nantucket iria pra CA ou Computer Associates, que desde 2018 faz parte da Broadcom. Todo o sucesso que eles tiveram nos anos 80 e 90 desapareceram ao ponto que hoje ninguém mais lembra. É um bom lembrete que nada dura pra sempre.</p>
<p>Enfim. Terminei de cadastrar meus contatos. Posso salvar e sair com control + end. Agora estou na tal linha de comando do interpretador. Posso abrir o arquivo do banco fazendo &ldquo;use contatos&rdquo;, digitar o comando LIST e olha só, lista o que acabei de registrar. Esse é o avô de todos os CRUDs, o CRUD original no mundo dos microcomputadores. Não o primeiro, mas possivelmente um dos mais populares.</p>
<p>O formato de arquivo que Cecil inventou foi o famoso DBF, literalmente database file. Quando eu era novo, antes de pular pra tecnologias mais novas como SQL Server da Microsoft, na minha cabeça &ldquo;banco de dados&rdquo; é o que você chama de tabela. O dBase gravava uma tabela por arquivo e chamava isso de banco de dados. Hoje, banco de dados é um servidor com conjuntos de tabelas num único arquivão. No fim dos anos 80, banco de dados, pra mim, era um arquivo com uma tabela.</p>
<p>Esse arquivo tem um espaço reservado nos primeiros endereços pra metadados, informações como o total de registros e coisas assim. Mas os registros em si tem todos o mesmo tamanho fixo. No exemplo dos contatos, são 100 bytes pro nome, 13 pro telefone e 100 pra empresa, então cada registro sempre vai ocupar 213 bytes. Basta ir pulando de 213 em 213 bytes pra ir avançando pro próximo registro.</p>
<p>Como programador você precisa entender essa estrutura. Digamos que eu queira encontrar o telefone do Brian. Como faz? Como é anos 80, vamos começar do jeito mais simples, ou seja, tosco. Podemos escrever um programinha besta, que vou chamar de &ldquo;list.prg&rdquo;. Abro o arquivo com &ldquo;USE&rdquo;, limpo a tela e começo a fazer um loop checando que não estamos no fim do arquivo, end-of-file ou EOF como tá aqui.</p>
<p>Daí fico comparando o campo nome do registro atual com &ldquo;Brian&rdquo;. E não acharia nada. Isso porque preciso procurar pelo nome exato, então &ldquo;Brian Russell&rdquo;. Claro, posso só pesquisar as primeiras letras também, mas é uma operação a mais, por agora vou simplificar. Da linha de comando do DOS podemos chamar o dbase passando esse programa.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>USE contatos
CLEAR
DO WHILE .NOT. EOF()
	IF NOME = &#39;Brian Russell&#39;
		@ 10,10 SAY &#39;Empresa:&#39; GET EMPRESA
		EXIT
	ENDIF
	SKIP
ENDDO</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>Nesse exemplo idiota, começa pesquisando o primeiro registro, que é Fabio. Não achou e não é fim de arquivo, então vai pro próximo com o comando <code>SKIP</code>, que é Cecil, ainda não é fim de arquivo, então vai pro próximo e acha o Brian, daí é só pegar o campo empresa, imprimir na tela com esse comando <code>SAY</code> e sair do loop com <code>EXIT</code>.</p>
<p>Esse é o pior caso: onde o que quero achar é o último registro. Imagine um arquivo muito maior, com mil registros. Precisaria ir um a um mil vezes. Todas as vezes. Por isso, esse é jeito mais tosco e mais primitivo de procura num banco de dados. Agora que entendeu isso, prometa que nunca vai fazer desse jeito.</p>
<p>Num computador moderno, esse programa vasculhando milhares de registros ia demorar menos de um segundo. Mas nos anos 80 poderia levar alguns segundos, dependendo de quantos registros tenho. Mas claro, nem nos anos 80 a gente era ruim de fazer desse jeito tosco.</p>
<p>Em dBase mesmo dava pra criar um segundo arquivo, um índice. Basta fazer &ldquo;INDEX ON NOME TO INDNOME&rdquo; e vai criar um arquivo separado de índice com extensão &ldquo;NDX&rdquo;. E podemos abrir o banco de dados com &ldquo;USE contatos INDEX INDNOME&rdquo;. Como já tá aberto podemos só fazer &ldquo;SET INDEX TO INDNOME&rdquo;. É assim que podemos ficar mudando de índices dependendo de que campo queremos procurar.</p>
<p>Eu digitei errado o nome da empresa do Cecil, vamos usar <code>SEEK 'Cecil Wayne'</code> e fazer <code>REPLACE EMPRESA WITH 'Ashton Tate'</code>. Pronto, é assim que procuramos e depois modificamos um registro. O comando Seek vai usar o índice, em vez de fazer o loop com comparação como fiz antes. Aliás, esse comando REPLACE serve pra mudar dados dos campos do registro atual. Com SEEK podemos encontrar um registro, mas pra criar um novo, teríamos que fazer &ldquo;APPEND BLANK&rdquo; antes, pra &ldquo;apendar&rdquo;, anexar um registro vazio no fim do arquivo, daí o REPLACE gravaria os dados nesse registro novo. Esse é o básico.</p>
<p>Esse tipo de índice eu já expliquei no episódio de Árvores e também nos de armazenamento. É uma variante de uma B-Tree. Lembram que falei que árvores são as estruturas mais importantes na computação? O arquivo DBF de banco de dados é mais ou menos como se fosse um array de structs em disco, mas um arquivo NDX de índice é uma estrutura de árvore B. Daí, em vez do tempo de procura ser linear em força bruta com loop um a um, com o índice passa a ser logarítmico, que é uma ordem de grandeza mais rápido e eficiente.</p>
<p>Ou seja, no meu programa de procura tosca via loop e comparação, se cada comparação e skip levasse, digamos, 1 milissegundo, e eu tivesse 10 mil registros e o que eu procuro estivesse no último registro, então no pior caso levaria 10 mil milissegundos, 10 segundo inteiro. Mas se eu usar um índice em B-Tree, com complexidade logarítmica, levaria talvez pouco mais de 10 milissegundos.</p>
<p>Mesmo se eu dobrasse a quantidade de registros, o tempo de pesquisa não iria aumentar muito mais que isso, dado a natureza logarítmica. Dependendo do que estou fazendo, o ganho de performance poderia ser 100 vezes ou mais usando o índice correto. Por isso falamos tanto no mundo de bancos pra sempre garantir que tem um índice correto preparado.</p>
<p>Mas e se eu quisesse agora procurar pelo telefone? Ou pelo nome da empresa? Bom, podemos criar um índice pra cada campo, como <code>INDEX ON TELEFONE TO INDFONE</code> e <code>INDEX ON EMPRESA TO INDEMPR</code>. Note como o nome do índice eu tive que truncar, porque em MS-DOS nomes de arquivos podiam ter no máximo 8 letras. Era uma limitação do FAT16 antigo. O sistema de arquivos FAT que você usa pra formatar SD Cards pra câmera é ou FAT32 ou o mais moderno ExFAT, e não o FAT16 antigo. São compatíveis, mas com limites muito maiores, por isso dá pra ter nomes de arquivos longos normalmente.</p>
<p>Voltando pro DOS podemos listar os arquivos criados. Como disse antes, cada registro vai ocupar aproximadamente 213 bytes. Tenho 3 registros, então teríamos pelo menos 639 bytes. No caso o formato DBF grava mais coisas como cabeçalho de metadados, por isso faz sentido ser 986 bytes. Mas veja cada arquivo de índice. 1 kilobyte cada. Acho que é o tamanho mínimo pra um índice.</p>
<p>Eu fiz um programa besta pra adicionar mais registros falsos só pra aumentar o tamanho dos arquivos. Olha a listagem aqui. A chatice de uma linguagem tão rudimentar quanto dBase III é que ele sequer tem função pra gerar números aleatórios nem tem coisas triviais como calcular o módulo de um número. Então tive que fazer um gerador de números usando só segundos. Não tem nem função pra pegar milissegundos então tenho muitas letras repetidas, mas tudo bem. Olha só como tive que fazer pra calcular o módulo na mão. Daí vou pegando letras da tabela ASCII e concatenando. Super rudimentar.</p>
<p>Gerando 40 novos registros e reindexando, vamos ver o tamanho dos arquivos. O banco de dados, a tabela de contatos em si, com 43 registros agora ficou com quase 10 kilobytes, Pouco mais que 240 bytes por registro, em média. Já os índices, o de nome ficou com quase 8 kilobytes e telefone que é um campo menor, ficou com pouco mais de 2 kilobytes. Empresa é o mesmo tamanho de Nome então ficou com uns 8 kilobytes também.</p>
<p>Pense por um segundo. Os arquivos de índice estão ocupando quase o dobro do espaço da tabela em si. Muita gente pode não entender isso, mas índices ocupam espaço, e não é pouco espaço. Mais importante que isso. Num banco moderno, digamos que a operação de inserção na tabela de um novo registro leve 1 milissegundo. Mas agora digamos que temos esses 3 índices. E atualizar cada um dos índices leve mais 1 milissegundo cada. Agora é um total de 4 milissegundos pra inserir um novo registro e atualizar os índices. O tempo de inserção ficaria 4 vezes maior.</p>
<p>Índices é uma faca de dois gumes. Por um lado podemos acelerar a pesquisa em 100 vezes, talvez mais, mas por outro lado, operações como inserção, atualização ou deleção vão demorar duas vezes mais, quatro vezes mais, dependendo da quantidade de índices que tivermos. Entenderam?</p>
<p>Aliás, índices em dBase, ou mesmo em Clipper, eram super instáveis. Vira e mexe ficavam desatualizados e por isso todo programa que fazíamos naquela época colocávamos no menu uma opção de manutenção. Ensinávamos o operador do cliente rodar de vez em quando. Era a operação pra reindexar tudo do zero, pra garantir que a pesquisa ia funcionar. Quanto maior a tabela, mais demorado ficava reindexar. Felizmente bancos de dados modernos são bem mais estáveis pra atualizar os índices e fazem isso sem precisar de intervenção manual, na maior parte do tempo.</p>
<p>Mas não é só isso. Até agora só falei sobre adicionar novos registros nesse arquivo. Isso é fácil, é só ir apendando, concatenando os bytes do registro no fim do arquivo. Atualizar, também não é tão difícil. Só dar &ldquo;SEEK&rdquo; pra encontrar o registro pelo índice e dar &ldquo;REPLACE&rdquo; pra substituir com o dado novo por cima do campo desejado. Mas e pra apagar? O que acontece?</p>
<p>Pensa um Array em memória. É uma sequência contínua. Se você quiser deletar um elemento no meio, não tem outro jeito. Precisa criar um novo array e copiar todos os elementos de novo, pra não ficar buraco no meio. Por isso que em memória usamos outras estruturas como listas ligadas. Daí é só mudar o ponteiro do elemento anterior pra apontar pro elemento seguinte. Mas listas ligadas são super ineficientes comparados com arrays, são mais difíceis de navegar, porque não tem endereços contínuos, então não tem como só calcular o offset e ir direto pro elemento no fim, tem que ir navegando ponteiro a ponteiro. É muito lento.</p>
<p>Um arquivo DBF é parecido com um array: não tem como apagar um registro no meio. O que acontece é que cada registro tem um bit separado só pra dizer se é válido ou não. Quando manda apagar, ele só grava esse bit de zero pra um, daí quando tentamos dar SEEK ou navegar pra frente com SKIP, ele vai ignorar se esse bit estiver marcado como apagado. Isso torna a operação simples com um trade-off: o espaço no disco vai continuar sendo ocupado, o arquivo não diminui de tamanho.</p>
<p>Por isso que além de reindexação, que é regerar os arquivos de índice, na tal opção de manutenção, também tinha outro comando: &ldquo;PACK&rdquo;. O que o PACK vai fazer é a mesma coisa que com um array: gerar um novo arquivo DBF do zero e copiar todos os registros não apagados pra lá, e no final apagar o arquivo antigo. Esse é o único jeito de recuperar espaço do disco. Mas pra isso precisava ter quase o tamanho do arquivo original de espaço livre sobrando pra fazer a operação.</p>
<p>Isso existe até hoje em bancos de dados modernos. Quem já mexeu com SQLite3 ou Postgres deve ter esbarrado no comando &ldquo;VACUUM&rdquo;, tem até a opção de AUTO VACUUM pra ele fazer isso sozinho sem termos que enviar o comando manualmente, mas em essência é a mesma ideia do comando PACK de dBase: recuperar espaço em disco e desfragmentar os dados no disco pra ficar mais eficiente e ocupando menos espaço.</p>
<p>Falando em SQLite3, ele seria o sucessor espiritual do dBase e Clipper. É provavelmente o banco de dados relacional mais usado no mundo. Está em todo lugar. Se você tem dispositivos smart com Android da vida, os aplicativos rodando dentro certamente usam SQLite3. O roteador da sua casa, sua Smart TV, sua Alexa, seu smart watch, tudo tem SQLite3. Qualquer aplicativo que precise armazenar qualquer tipo de dados, normalmente usa SQLite3.</p>
<p>A razão disso é por ser extremamente pequeno, altamente eficiente, flexível e portável. O código fonte dele é em C super limpo, que pode ser facilmente portado pra rodar em qualquer sistema operacional, seja Linux, seja Mac e pra qualquer arquitetura, seja x86 no seu PC, seja ARM na sua SmartTV ou console de videogame. Cabe tudo num único arquivo de C com poucos page down e oferece uma linguagem compatível com SQL. É muito bem feito, um dos melhores e mais úteis projetos de código aberto de todos os tempos. Se você é estudante de C, vale a pena explorar o código fonte, não é tão difícil de entender.</p>
<p>Isso me leva ao episódio onde mostrei o que seria uma introdução de como fazer uma linguagem similar a SQL e eu usei justamente um trecho da gramática de SQL do SQLite3. Só que por baixo fiz ele acessar uma estrutura em memória de Javascript. Poderia ser um arquivo DBF como de dBase e Clipper que acabei de mostrar. A diferença é que em vez de comandos como APPEND, REPLACE, USE, SKIP que mostrei até agora, poderíamos oferecer uma versão com SELECT, INSERT ou UPDATE.</p>
<p>O formato DBF é usado até hoje em muitos lugares. Um Pandas de Python tem capacidade de carregar e manipular DBF. A linguagem R pra cálculo numérico também consegue entender DBF. Linguagens como Javascript, Java, C#, todos tem bibliotecas que conseguem carregar DBF. É um formato legado que ainda é usado, por ser super simples, por ter sistemas legados em indústrias antigas. Obviamente, ninguém deveria iniciar um novo sistema dependente desse formato, pra isso existem coisas mais modernas, como o próprio SQLite3.</p>
<p>SQLite3, como dBase ou Clipper, não depende de ter um servidor de rede pra servir os dados, nem clientes complicados. Ele abre o arquivo e você manipula diretamente. Por isso é tão pequeno e eficiente. Existem opções pra oferecer SQLite3 em rede, mas o foco dele sempre foi rodar localmente. Acho que exige menos de 1 megabyte de RAM pra rodar, talvez faixa de meio megabyte, o que é ridiculamente pequeno. Muitos programas de Clipper dos anos 90 precisava mais que isso.</p>
<p>dBase, Clipper, FoxPro é o que chamamos da família de linguagens xBase, que suportam o formato DBF. Existe uma versão mais moderna de Clipper de código aberto que é o Harbour e seu fork, o xHarbour. Não sei se alguém usa, mas serviria pra portar antigos programas xBase pra rodar em plataformas mais modernas além de adicionar funcionalidades modernas como orientação a objetos e capacidade de se conectar em bancos SQL modernos. De novo, não recomendo começar nenhum novo projeto neles. Qualquer Python da vida, conectando num SQLite3 ou Postgres vai ser melhor.</p>
<p>Tecnologias dos anos 80 eram limitados pelos poucos recursos que a gente tinha. Os processadores eram super lentos comparados com hoje. Estamos falando de milhares de vezes mais lento. Os programas precisavam ser super eficientes, porque qualquer operação fora do lugar roubava recursos escassos, seja de CPU, seja de RAM. Eles precisavam rodar em menos de 640 kilobytes de RAM, em 15 megahertz ou menos. Qualquer processador porcaria de hoje em dia tem 2 ou mais núcleos, cada um com pelo menos 1 gigahertz. Olha a diferença.</p>
<p>Eu mencionei que o sistema de arquivos FAT16 do MS-DOS da época só suportava arquivos de 8 letras mais 3 de extensão. Não tinha memória suficiente pra suportar estruturas de dados maiores que isso. Mais importante: não tinha processamento sobrando pra garantir a integridade dos dados. Coisas como calcular o checksum ou fazer hash dos arquivos. Pros anos 80 seria muito pesado. Por isso disquetes em FAT16 costumavam falhar, arquivos ficavam corrompidos, dados se perdiam.</p>
<p>Mesma coisa pro formato DBF. Era comum esses arquivos se corromperem. Backup era essencial. E backup naquela época era mandar fazer uma cópia num disquete no fim do dia. Toda lojinha da esquina, que tinha um programa em Clipper pra controlar estoque, fornecedores, contas a pagar e a receber e coisas assim, sabia que precisava fazer isso. Fiz alguns programas desse tipo no começo dos anos 90.</p>
<p>No SQLite3, o formato de arquivos reflete essa diferença. Pra começar não é um arquivo append only como DBF, onde vai simplesmente gravando um registro na frente do outro e navegando via offset de endereços. Ele adota uma estrutura de árvore B, uma B-Tree. Não só no índice mas no banco de dados em si. Aliás, no caso do SQLite3, tem um único arquivo que contém várias tabelas, em vez de cada tabela ser um arquivo separado.</p>
<p>Internamente não vai alocando registros e sim páginas. Páginas são como ranges, ou intervalos de endereços. Por exemplo, a página 1 pode ser do endereço 1 até 500, página 2 do endereço 501 até 1001. Páginas contém registros, ou linhas de uma tabela. As páginas são organizadas numa árvore B e os registros são nós folhas, leaf nodes, dentro das páginas.</p>
<p>Nos episódios de árvore, de sistemas de arquivos e de SQL eu falei sobre B-Trees e suas variantes como B+Trees, que sistemas de arquivos modernos como ZFS ou BTRFS utilizam. Tudo no seu sistema operacional é alguma variante de árvores, em particular árvores B, por isso falei que era importante saber como funcionam. No caso do SQLite3 tanto o banco de dados propriamente dito, quanto seus índices, são variantes de árvores B. Elas são muito eficientes. Vamos recapitular.</p>
<p>Primeiro de tudo, o B não é de binário. Árvore binária é um tipo de árvore mas não é b-tree. O B não tem uma definição definitiva mas pense como &ldquo;balanceada&rdquo;. Uma característica importante é se auto-balancear. Todos os nós folha tem a mesma profundidade. Isso significa que não importa qual chave tá procurando, leva mais ou menos o mesmo tempo pra acessar qualquer nó. Isso é importante porque o tempo de &ldquo;seek&rdquo; ou procura num HD mecânico custa tempo. Uma árvore B minimiza a quantidade de operações de procura.</p>
<p>Árvores B tem alto fan-out, ou seja, são feitas pra cada nó interno poder ter um número grande de nós filhos, que é a ordem da árvore. Isso ajuda armazenamento em disco porque permite uma grande porção dos dados ser escrito tudo numa única página. O jeito antigo do DBF de ir alocando um registro de cada vez no fim é bem ineficiente e gera muita fragmentação. Alto fan-out significa que muitas chaves podem ser acessadas de uma só vez numa única operação de procura em disco. É melhor pegar uma chave que não precisava junto do que ficar procurando aleatoriamente por todo o disco.</p>
<p>Operações de inserção e deleção são muito eficientes. O algoritmo que mantém a árvore balanceada, e eu mostro uma versão disso no video de árvore, é super eficiente. Quando uma nova chave é inserida ou apagada a árvore é re-balanceada, pra garantir a propriedade dos nós folha terem sempre a mesma profundidade. E é muito estável mesmo com a árvore crescendo muito. Um banco de dados de SQLite3, que é super pequeno, aguenta um arquivão que pode teoricamente atingir até 280 terabytes.</p>
<p>Operações que pegam vários registros de uma só vez, tipo um SELECT WHERE da vida ficam mais eficientes, pela propriedade de vários registros estarem juntos numa mesma página. Com poucas operações de disco dá pra pegar todas elas ordenadas de uma só vez. Então, coisas como listagens e montar relatórios, tende a ser muito mais eficiente do que num DBF antigo. Tudo que você pedir que seja ordenado e sequencial, tende a ser eficiente pelas propriedades de páginas que expliquei.</p>
<p>Eu sempre ouvi que ninguém deveria tentar fazer um banco de dados do zero sem ter visto como um de verdade funciona por baixo, e esse é um dos motivos: operações em disco, especialmente mecânico, custam caro. Hoje custam caro, mas nos anos 80 ou 90 eram bem mais rudimentares. Um HD moderno, como meu IronWolf de 12 terabytes vem embutido com 256 megabytes de memória RAM. Não tinha PC nos anos 90 com tudo isso de RAM. Sem cache, as operações são mais custosas, porque realmente precisa colocar a cabeça em cima do disco de verdade.</p>
<p>Aqui estou especulando porque realmente não me lembro se era esse o caso, mas por exemplo, eu reservaria espaço no começo do disco pra colocar coisas como metadados, índices e os dados em si começariam a ser gravados depois. Como se eu tivesse uma partição C:, mais perto do centro do disco físico, onde as operações são mais rápidas do que perto das bordas dos discos. Daí numa partição D: ficariam os dados em si.</p>
<p>Aliás, acho que é por isso que antigamente a gente particionava o HD, no C: ficava o sistema operacional, pra garantir que no boot o disco trabalhe menos e seja mais rápido, daí os dados e programas ficam no D:, mais longe do centro do disco. Mesma razão de hoje em dia deixarmos o boot num SSD ou NVME, que é muito mais rápido, mas sua biblioteca do Steam colocamos num HD mecânico separado. Sempre é um trade off entre mais performance ou mais espaço.</p>
<p>E não só SQLite3, claro, qualquer banco de dados moderno, como Postgres, Oracle, SQL Server, e até bancos não relacionais, como MongoDB, Firebase, Dynamo, Cassandra. E não só bancos de dados como sistemas de arquivos como ZFS ou BTRFS que já mencionei, todos usam alguma variante de árvore B, exatamente por todas essas propriedades. Tudo que lida com gerenciamento de dados, usa árvores B.</p>
<p>Porra Akita, mas um MongoDB não grava tudo em JSON? Eu mando JSON e ele me devolve JSON, portanto JSON deve ser o formato mais eficiente, não? Sério, se você for bem iniciante eu deixo isso passar, mas se algum programador pensa assim, realmente não entende nada de programação. JSON é um formato de consumo, não um formato de armazenamento. É um formato de tradução pra facilitar a vida mas é altamente ineficiente. Eu isso no episódio que falo do código do Twitter.</p>
<p>Primeiro, não é JSON, internamente os nós filhos são gravados em BSON que ele chama de Binary JSON, é um formato binário, como Protobufs do Google. Segundo, é internamente organizado numa B+Tree, ou árvore B+, onde os valores propriamente ditos são gravados direto nos nós folha no fim da árvore, diferente de uma árvore B pura que só tem os ponteiros.</p>
<p>O objetivo todo do episódio onde eu fiz um SQL em cima de um array de javascript, foi pra demonstrar que ter SQL não descreve nada de como é o banco de dados por baixo. Mesma coisa no MongoDB: a linguagem e formato de dados, como JSON, que se usa por cima, não diz nada sobre como é a arquitetura por baixo. Tanto que um Postgres, via SQL, pode devolver os dados em formato JSON se você quiser. É só uma tradução. JSON em si não significa nada pra nenhum banco de dados. Eles só precisam escolher exportar ou importar dados nesse formato. É só um formato, como XML ou CSV ou Excel.</p>
<p>Todo formato de dados de consumo como esse vai exigir um passo de importação pro formato interno binário de verdade, ou exportação pra esse formato. Então se você faz um &ldquo;SELECT *&rdquo;
da vida num banco, primeiro ele vai puxar todos os registros no formato binário dele. Isso vai ocupar um X de memória. Daí você escolhe que quer em formato JSON, então ele vai precisar de mais um tanto Y de memória, maior do que X, pra fazer essa tradução. É um processo de serialização e desserialização, e sempre vai exigir o dobro de memória pra ter essa conveniência.</p>
<p>Por isso que comunicação de um serviço com outro serviço, deveria ser sempre utilizando protocolos binários compatíveis entre eles, por exemplo, protobufs. Evitando ao máximo fazer tradução de dados, especialmente em formato texto. O formato de dados do SQLite3 não tem nenhuma relação com ele ter capacidade de receber comandos em SQL. Não existe um banco de dados &ldquo;SQL&rdquo;, existem bancos de dados que entendem a linguagem SQL, só isso. Internamente todos são implementados de formas diferentes pra determinados cenários e casos de uso.</p>
<p>Um SQLite3 é ótimo pra usar numa SmartTV, já um Postgres ou MongoDB da vida, nem tanto. Eles foram feitos pra rodar em servidores. Existem até projetos pra rodar SQLite3 em servidores, mas de novo, não foi feito pra isso. Use a ferramenta certa no lugar certo. Só porque você consegue arrancar um parafuso da parede com um alicate, não quer dizer que deveria, acha a porra da chave correta.</p>
<p>Internamente, todo banco de dados vai ter um formato de arquivos de armazenamento como um DBF, ou mais corretamente como o formato de um SQLite3, e formato de arquivos de índice. Todos vão ser alguma variante de árvore B ou árvore B+. Além disso temos mais coisas modernas, no caso do SQLite3 vai ter um outro arquivo com nome terminando em &ldquo;wal&rdquo;, de write ahead log. Isso é outra coisa que sistemas de arquivos como FAT não tinham, mas todo sistema moderno como NTFS ou ext4 tem: um journal.</p>
<p>Pra economizar recursos, aquele comando REPLACE que eu mostrei lá no começo com dBase, escreve por cima do registro antigo no arquivo DBF diretamente. Isso é péssimo. Porque digamos que estamos fazendo uma atualização em massa em todos os registros do arquivo. Colocando o prefixo 9 em todo telefone que é de São Paulo, lá em 2012. Agora digamos que no meio dessa atualização acabou a luz e o computador reboota. Fodeu.</p>
<p>Isso era comum antigamente e significava que muito provavelmente o arquivo ficou num estado corrompido e eu perdi algum dado. Hoje em dia não se faz mais isso. Existem diversas estratégias, mas o SQLite3 escolheu um log de escrita pra frente ou write ahead log. Ele primeiro escreve as modificações nesse arquivo separado de log, depois que termina, troca um pelo outro. Bem simplificado é como se eu tivesse um arquivo chamado &ldquo;contatos&rdquo; daí tenho uma cópia dele chamado &ldquo;contatos-wal&rdquo; e as atualizações eu faço nesse segundo arquivo. Se tudo correu bem até o fim, agora troco um arquivo pelo outro.</p>
<p>É um pouco mais avançado que isso, mas só pra ter na cabeça a idéia. Se acabar a luz e o segundo arquivo estragar, ainda tem o original intacto. Além disso, se precisar fazer pesquisas, posso pesquisar no original enquanto a transação não acaba no segundo arquivo, em vez de ficar travado tendo que esperar até todas as operações acabarem. Todo banco de dados tem algum mecanismo desse tipo, mais sofisticado pra ter o menor tempo de trava quanto possível e garantir que nunca nenhum dado vai ser corrompido.</p>
<p>É um tipo de mecanismo de COW ou Copy on Write, como o sistema de arquivos BTRFS também tem em Linux. Por isso que é bem incomum hoje em dia perder dados. Mesmo o Windows tem NTFS, que protege razoavelmente bem. Qualquer Linux mais vagabundo usa pelo menos ext4, que tem suporte a journaling. Pra perder dados hoje, você precisa estar usando um hardware muito do vagabundo, como um SD card de câmera.</p>
<p>SD card é um hardware de armazenamento que foi feito pra ter grande capacidade e ser super barato. Ele foi feito pra tirar fotos, gravar videos e logo em seguida transferir pra um computador. Não foi feito pra deixar lá guardado pra sempre. Não é confiável e podemos perder dados do nada, especialmente nos modelos mais baratos. SSDs baratos de Aliexpress, mesma coisa, mas nesse caso porque são fraudes mesmo, fabricados pra falhar. Cuidado com isso.</p>
<p>Outra característica que bancos relacionais trouxeram foi esse conceito de transação e ACID, de atomicidade na operações. Ou todas acabam com sucesso ou nenhuma acaba. De consistência, por exemplo, regras de unicidade, chaves estrangeiras, e tudo mais. Os dados precisam sempre seguir essas regras, sem exceção. Isolamento, ou seja, se um conjunto de transações roda em paralelo, o resultado tem que ser o mesmo se eles tivessem rodado sequencialmente. O banco não pode ficar num estado inconsistente. E durabilidade, se a transação diz que gravou o dado, podemos acreditar que realmente está fisicamente no disco.</p>
<p>Caso um erro aconteça, como a famosa falta de luz, o banco precisa voltar pro último estado consistente conhecido. Nunca pode continuar operando num estado inconsistente. Cada banco implementa isso de formas diferentes, mas no geral significa que podemos confiar nos dados num banco de dados relacional. Seja um MySQL ou Posgres ou SQL Server, no geral, implementam bem essas características. Mesmo o pequeno SQLite3 faz isso. Um dos jeitos mais toscos é simplesmente bloquear o arquivo todo pra cada transação, esperar completar com sucesso e só aí liberar pra outras operações. Mas claro, isso seria altamente ineficiente.</p>
<p>Mas era assim que bancos de dados xBase, DBF funcionavam em rede nos anos 90 quando começamos a usar placas de rede em MS-DOS e fazer pequenas redes usando ferramentas da antiga Novell ou o Windows 3.1 for Workgroups ou o venerado OS/2 da IBM. Na prática, um computador era elegido como um servidor de arquivos, esses arquivos DBF e NDX eram compartilhados em rede, e todos os outros computadores na rede enxergavam os mesmos arquivos.</p>
<p>Cada computador tinha a mesma versão do programa em dBase ou Clipper ou FoxPro instalado. Se algum tivesse uma versão mais antiga, era caos. A consistência, as regras, dependiam totalmente da versão do programa instalado em cada máquina. A atomicidade era mais ou menos garantida por um sistema de lock. Quando um programa quisesse inserir um registro novo, ele travava o acesso àquele arquivo. Se um segundo computador tentasse ler, recebia um erro que o arquivo tava ocupado, e precisava esperar.</p>
<p>Isso nem sempre funcionava. E se o arquivo DBF, sozinho numa única máquina tendia a se corromper, porque não existia nenhum sistema de proteçõ como journals, imagine em rede. Corromper arquivos era rotina. Experimenta esquecer de fazer backup no fim do dia, certeza que no dia seguinte ia dar pau e corromper alguma coisa. Sistemas de arquivos em rede como Novell era uma puta gambiarra feita em cima do DOS, com tecnologias super imaturas.</p>
<p>Hoje usamos protocolos de internet como TCP/IP, mas naquela época usávamos protocolos mais rudimentares como IPX/SPX e NetBIOS da Microsoft. Verdadeiras porcarias. E mesmo a tecnologia de redes em si, hoje usamos Ethernet de 1 gigabit pra cima, antigamente eram cabos coaxiais que nem de TV a cabo, que male male, faziam 10 megabits. Perdiam pacotes, corrompiam dados e assim vai.</p>
<p>Com a migração de MS-DOS pra Windows e da arquitetura de 16 bits pra 32 bits, ganhamos até 4 gigabytes de RAM pra trabalhar, sem precisar fazer gambiarras de memória em DOS como eu explico no episódio dos 640 kilobytes. E com isso, em vez de cada programa em cada máquina da rede, tentar acessar diretamente os arquivos de dados, eles passaram a conversar com um software servidor. Dessa forma, só um software tinha acesso direto aos arquivos e todos os outros, programas clientes, conversavam com esse servidor usando algum protocolo, com alguma linguagem, no caso o SQL.</p>
<p>Essa foi a era de programas feitos em Visual Basic ou Delphi, conversando com bancos de dados servidores mais modernos como SQL Server, que tinha T-SQL, ou Oracle, que tinha PL/SQL ou alguns mais de nicho como o Interbase da Borland, que era o padrão pra quem usava Delphi. Em vez de cada programa cliente ter que controlar a integridade dos arquivos, melhor terceirizar isso pra um servidor. Os clientes só falam &ldquo;ow, grava isso pra mim&rdquo; ou &ldquo;ow, pesquisa isso pra mim&rdquo; e o servidor se vira. Isso aumentou muito não só a escalabilidade mas também a estabilidade. Se um cliente manda uma operação não suportada, o servidor pode só recusar, em vez de tentar executar e corromper arquivos.</p>
<p>SQLite3, oficialmente não tem um servidor. Mas tem gente tentando fazer um sei lá porque. Tem um projeto chamado ValentinaDB que faz isso. Não vejo nenhuma vantagem. Se você quer um banco de dados SQL, formato cliente-servidor, use um de verdade, que é maduro, como MySQL ou Postgres. Use SQLite3 pra programas que precisam de dados localmente, como seu roteador de internet que já dei de exemplo.</p>
<p>Quando temos o formato de um servidor, temos algumas vantagens e algumas desvantagens. A primeira é que a velocidade vai depender da qualidade da rede. Tudo que roda localmente sempre é mais rápido. Em rede você precisa pegar o resultado, quebrar em pacotes, rotear esses pacotes, ir recebendo, processando um buffer, e só aí vai ter os dados.</p>
<p>Podemos escolher ir recebendo e já processando os dados localmente, ou podemos escolher esperar receber a resposta toda e só depois fazer alguma coisa. É a diferença de receber tudo de uma vez ou usar técnicas de streaming. E aí depende da sua pesquisa. Se for tosca e mal feita, e mandar tipo um &ldquo;SELECT *&rdquo; pra uma tabela de 10 gigabytes, sem usar streaming, boa sorte, vai ocupar toda a memória da sua máquina e mais o swap em disco.</p>
<p>Isso é o que iniciantes tem mais dificuldade de entender: os dados que pedem precisam ir pra algum lugar. Nada é de graça em computação. O trabalho principal de um programador é justamente administrar os recursos da máquina da maneira mais eficiente possível. Por exemplo, digamos que preciso montar um relatório em PDF de todas as vendas do mês. Vou fazer tipo um &ldquo;SELECT * FROM ORDERS WHERE created_at &gt; date() - 30&rdquo;. Essa é uma pesquisa rudimentar que filtra todas as ordens criadas nos últimos 30 dias. Faz de conta que vai devolver 500 ordens.</p>
<p>Digamos que cada uma dessas linhas consuma 500 bytes, só meio kilobyte. Se eu puxar tudo de uma vez do banco e colocar num array em memória, vai ocupar 250 megabytes. É bastante coisa. Daí abrimos uma biblioteca qualquer pra montar PDF e converter essas linhas em um documento PDF em memória. Pra simplificar o exemplo, digamos que cada linha no PDF também use 500 bytes. Significa que no fim da operação, vou estar consumindo 500 megabytes na memória, meio gigabyte, metade com os dados que vieram do banco e metade com o PDF.</p>
<p>Qual o jeito certo? Depende, talvez seja esse mesmo. Existem técnicas se quiser otimizar. Um dos jeitos é escolhendo uma biblioteca de PDF que suporte escrever direto pro disco em vez de acumular tudo em memória. Por exemplo, em Java tem a biblioteca PDFBox da Apache. Podemos ir salvando incrementalmente uma página de cada vez pro disco. Digamos que cada página tenha 20 ordens, então só vou precisar de aproximadamente 10 megabytes na memória por vez.</p>
<p>Além disso podemos ir recebendo uma linha de cada vez do banco, e montar uma linha no PDF e descartar essa memória, daí recebo a próxima linha e faço a mesma coisa, sem acumular tudo. Em resumo, significa que de memória de trabalho precisaria, no máximo, ter uns 10 megabytes pra montar uma página de PDF e mais meio megabyte por linha que recebo do servidor de banco de dados, um total de 10 mega e meio.</p>
<p>Aí não importa se o relatório vai ter 100 ordens ou 1000 ordens, sempre vou usar o máximo de 10 mega e meio de cada vez, e não 500 megabytes pra 500 ordens ou 1 gigabyte se for mês de natal e tiver 1000 ordens. Esse é seu trabalho como programador: encontrar o teto máximo que realmente precisa e limitar o uso de recursos. Só assim é possível escalar. O jeito amador é ir usando cada vez mais memória à medida que os dados vão aumentando de volume. Sempre pense em jeitos de usar um teto fixo de memória e processamento, independente de quantos dados vai processar.</p>
<p>Quando falamos que é importante entender SQL direito, primeiro é pra aprender a fazer queries ou pesquisas, SELECTs que devolvam a menor quantidade de dados quanto possível, porque cada dado extra que vier e não usarmos, vai desperdiçando memória e processamento. Mas também precisamos entender algumas noções como, se vamos escrever ou atualizar dados, é melhor mandar os comandos todos de uma só vez do que mandar um de cada vez, esperar um por um terminar e confirmar antes de mandar o próximo.</p>
<p>Aquele exemplo que falei de adicionar o número 9 na frente de todo número de telefone de São Paulo em 2012. Digamos que tenha 100 mil usuários na minha tabela. O jeito mais tosco possível é fazer um programa que conecta no banco e primeiro faz um &ldquo;SELECT *&rdquo; e devolve todo mundo pro seu programa. De novo, se cada registro ocupa meio kilobyte e você deu asterisco, mandou voltar todos os campos da tabela, são 50 mil megabytes, 50 gigabytes de dados. Estão entendendo porque isso de puxar tudo pra memória é ruim? É o jeito mais fácil de pensar primeiro, mas é o pior jeito.</p>
<p>Digamos que você tá num servidor parrudo na Amazon, ou num PC igual o meu com 64 gigabytes, e depois de um bom tempo,  carregou a tabela inteira em memória. Agora você faz um loop nessa lista e manda dar um UPDATE no servidor pra cada registro. Um UPDATE em uma transação separada, 100 mil vezes. São 100 mil updates, 100 mil alterações individuais de índice, 100 mil commits, e 100 mil esperas de retorno OK do servidor. Lá se vão mais algumas horas esperando.</p>
<p>Qual o jeito certo? Se não ficou óbvio, não precisa dar SELECT de nada. Só fazer um único comando de UPDATE. Algo como &ldquo;UPDATE SET TELEFONE = &ldquo;9&rdquo; + TELEFONE WHERE CITY = &lsquo;Sao Paulo&rsquo; AND bla bla&rdquo; . Um único comando que vai mexer em todos os milhares de registros tudo de uma só vez. Com uma única conexão de rede, um único commit, uma única espera. Não precisa puxar todo mundo pra memória, depois fazer update um a um separado. Entenda esse conceito. A melhor operação trás zero dados do servidor e opera tudo lá.</p>
<p>Do jeito tosco, o programa cliente, digamos sua aplicação web ou mobile, mandou individualmente um comando de update pra cada usuários, esperou a resposta e mandou o próximo, 100 mil vezes. Do jeito certo, seu programa só se conectou uma vez e só mandou um comando, e afetou os mesmos 100 mil registros. A diferença de tempo aqui vai ser na ordem de centenas ou milhares de vezes mais rápido e usando ordens de grandeza menos recursos de memória e processamento. Isso é entender SQL.</p>
<p>Programadores iniciantes aprendendo hoje não tem desculpa. O pessoal das antigas que fazia dBase, até consigo entender, porque naquele formato rudimentar de DBF, não tinha outra forma. Tinha que iniciar um loop &ldquo;DO WHILE&rdquo; fazer uma condição com &ldquo;IF&rdquo; e aí fazer &ldquo;REPLACE&rdquo; no registro atual, dar &ldquo;SKIP&rdquo; pra pular pro próximo e repetir 100 mil vezes. Não tinha um servidor que fazia isso sozinho pra gente.</p>
<p>Mas pelo menos naquela época estava tudo rodando localmente na mesma máquina, modificando diretamente o arquivo. É o que o servidor de SQL vai ter que fazer no lado dele. Mas um cliente de banco de dados não tem que fazer loop nenhum se comunicando com servidor registro a registro, quase nunca. Se você tem um loop pra processar dados vindos do servidor de banco de dados, provavelmente está errado. No mínimo é um red flag que precisa ser revisto. Pelo menos lembre disso como regra: se tem um loop, está errado, pense de novo.</p>
<p>Outra coisa que o formato antigo não permitia fazer e você tem que aprender como fazer no SQL são constraints. As tais regras de consistência. Por exemplo, um campo que nunca pode ter valor vazio. Ou um campo que é uma chave estrangeira, um campo numa tabela com o ID primário de outro registro em outra tabela. Chaves estrangeiras garantem que você só está escrevendo IDs que realmente existem. No formato DBF não existia isso, você como programador precisava garantir essas regras manualmente, no código, com um monte de IFs pra lá e pra cá. Em SQL, se tem um monte de IFs, provavelmente deveriam ser constraints.</p>
<p>No mundo moderno de cliente e servidor, significa que muitas vezes essas regras vão ser duplicadas, no cliente pra poder mostrar pro usuário que ele digitou dados inválidos, por exemplo, e no servidor, pra garantir que nas tabelas só tenha dados realmente corretos. Muita gente acha essa redundância ruim, mas eu acho o contrário. Não existe nada pior que dado inconsistente. É melhor checar duas vezes do que não checar. Checagens de consistência que só existem no front-end, e no lado do banco de dados aceita tudo e não checa nada, é a maior porcaria de todas as porcarias. Sempre esses bancos de dados estão em estado inconsistente e sempre vai dar problema.</p>
<p>Crie a tabela no schema com as constraints e validações todas, validação de tipos de dados, validação de tamanho, validação se pode ser vazio ou não, validação de chaves, tudo. Daí faça uma classe do lado cliente que reflete as tabelas usando um ORM, como Hibernate em Java ou um model em Sequelize no Javascript ou model de Django em Python, não importa, repita as mesmas validações. Daí faça um componente em React pro front-end.</p>
<p>Repita as mesmas validações. Puuuts, mas vai repetir 3 vezes? Sim, repita 3 vezes, e faça testes unitários pra cada uma delas e teste de integração que cheque todas ao mesmo tempo. Esse é o seu trabalho. É que nem médico achar que lavar mão toda hora antes da cirurgia é perda de tempo. &ldquo;Ah, de novo? Eu já lavei de manhã, não precisa de novo&rdquo;. Lave de novo.</p>
<p>Beleza, tem que estudar SQL. Não só saber sintaxe de SQL, mas o  uso de recursos e processamento no servidor. E como otimizar. Nenhum ORM, biblioteca ou framework vai fazer isso pra você. Na verdade, é muito fácil fazer o jeito errado assumindo que o framework vai cuidar de tudo sozinho. Muitos tutoriais, no intuito de serem didáticos, vão te ensinar o jeito menos eficiente. Porque o jeito mais eficiente costuma ser mais difícil de explicar e demonstrar.</p>
<p>E quando você testa com poucos registros, 10, 100, mil, não faz  nenhuma diferença. Só quando realmente coloca em produção que vai entender o impacto. Essa é a diferença de crianças e adultos: quem já teve a experiência de perder dados de cliente ou ficar tudo lento que a loja pára de funcionar. Só aí que entende &ldquo;ahhhh, eu tava entendendo errado&rdquo;. Pois é. Tente evitar ter que chegar nesse ponto. Nem todo mundo precisa enfiar o dedo na tomada pra saber que toma choque.</p>
<p>Num sistema grande de verdade, numa equipe com vários desenvolvedores, é difícil de checar se todo código foi bem feito ou não. E mesmo sendo experiente, na pressão alguém deixa passar um código que vai usar memória sem precisar ou que vai deixar tudo lento porque botou um loop num lugar que não deveria. Por isso que todo sistema web em produção precisa ser monitorado. Existem diversas ferramentas de monitoramento, seja usando os antigos Nagios ou Zabbix ou serviços mais modernos como Splunk, Datadog, AppOptics, AppDynamics, Dynatrace, Sentry.io, Instana, Azure Monitor e vários outros. Não tem desculpa não usar pelo menos um.</p>
<p>Honestamente eu não sei dizer qual é o melhor hoje em dia, mas na dúvida, sempre usei e continuo recomendando no mínimo instalar o New Relic RPM. Alguns deles são passivos, com os antigos Nagios ou Logtail, porque medem coisas fora da aplicação, como uso de CPU, memória, análise dos arquivos de log. Mas os melhores exigem que você instale uma biblioteca que age como um agente dentro da aplicação. Como tem acesso à memória da aplicação, dá pra fazer análises muito melhores.</p>
<p>Sem entrar em detalhes, podemos avaliar logo depois de um deployment em produção, e ver se uso de memória aumentou e fica constante, por exemplo, ou se continua lentamente subindo até uma hora crashear. Ou podemos monitorar quando uma campanha de marketing entra no ar e vem um volume maior de tráfego. O aumento do uso de recursos está proporcional? Mais importante, podemos avaliar quais partes do código costumam consumir mais recursos.</p>
<p>Com isso podemos focar em melhorias bem mais precisas só nas partes que realmente precisa. Em vez de ficar no achismo de mexer no código sem saber quais reais impactos está causando. Depois que uma aplicação entra em produção, toda manutenção deve ser feita ao redor de métricas. Sempre medir o antes e o depois. Se o objetivo for consumir menos infraestrutura, sempre atacar os principais ofensores segundo as métricas. Se você não consegue medir, certamente não consegue consertar.</p>
<p>No lado do banco de dados, nenhum consegue crescer infinitamente. Como disse, pro caso geral de empresas pequenas ou médias, leva muito tempo pra começar a entrar no território de Big Data. Dezenas ou centenas de terabytes de dados. Um banco de dados relacional tradicional costuma ter os seguintes problemas de escalabilidade:</p>
<p>Primeiro, as tabelas simplesmente estão grandes demais. E quando eu digo grandes, quero dizer centenas de gigabytes, ou terabytes. Por mais que você tenha índices, lembra o que mostrei no caso do DBF e seus índices NDX? O arquivo de índice vai crescendo também. Quanto mais dados for inserindo, maior vai ficando os seus índices também. E vai chegar uma hora que pesquisar índices tão pesados vai ser lento.</p>
<p>A primeira estratégia pra lidar com esse problema se chama &ldquo;arquivamento&rdquo;. O certo é ter ou um segundo servidor de banco de dados usado só pra pesquisas e relatórios, mas fora do acesso pra usuários. Digamos que você tenha um ecommerce. Se tiver sucesso e vender muito, sua tabela de ordens vai ter milhões de vendas. Mas pensa: pra que serve dados de ordens antigas? Ordens de usuários que cancelaram contas ou que não entram faz muito tempo? Mova pra um segundo banco de dados.</p>
<p>Assim você tem um banco de dados com dados que realmente seus usuários precisam, mas sem os dados que ninguém nunca acessa. Digamos que sua empresa é uma multi-nacional com dezenas de filiais, dezenas de milhares de funcionários e seu sistema controla tudo de RH: folha de pagamento, benefícios, férias, documentação, e tudo mais. Pra que tem dados de funcionários de 5 anos atrás? Arquive. Mova pra um segundo banco de dados, ou mova pra um backup frio mesmo, como fita ou pra um AWS Glacier da vida, que é tipo um S3 mais barato.</p>
<p>Em qualquer empresa média ou grande, com sistemas de controle internos, vai ter tabelas com dados antigos que não precisam estar lá. É uma prática que de tempos em tempos, todo ano, talvez todo semestre, se faça essa manutenção de limpeza: arquivar, mover dados mortos, pra outro lugar que se acessa menos. Não é apagar. É mover. Nunca é uma boa idéia apagar nada.</p>
<p>Nunca se sabe quando vai precisar, especialmente sua área jurídica. Mantenha dados no mínimo em backup, por no mínimo 5 anos, por causa da lei. A grande maioria das pequenas empresas peca em sequer ter um backup que funciona, quanto mais se preocupar com arquivamento. De novo, não é pra pequenos isso. Pequenos tem que se preocupar com o básico: ter um backup, pra começar.</p>
<p>Mas aí começamos a entrar no território de unicórnios. Uber, Netflix, iFood, MercadoLivre, e várias tech startups que geram toneladas de dados. Digamos que é um iFood da vida. Não só vai ter informações básicas como cadastro do usuário e tabela de pedidos simples, mas vai ter mais dados como quem foi o motoqueiro que fez cada entrega, que rotas usou, que contatos foram realizados durante o percurso, o usuário abriu chat, o que ele falou? É uma granularidade e volume muito maior de dados. Cada pequeno pedido gera megabytes de dados pra depois serem usados pra inteligência, métricas de como melhorar a experiência e tudo mais.</p>
<p>Empresas assim geram gigabytes de dados por dia. O problema não é só o volume de dados, mas a velocidade em que são gerados. Aqui é o território de Data Warehouses, Data Lakes e coisas assim. Em nenhuma dessas empresas vai ter um único servidor de banco de dados com tudo. Sempre vai ter múltiplos bancos de dados, e certamente de vários tipos diferentes pra cada caso de uso diferente.</p>
<p>Simplificando bastante. Do lado do usuários que usa o app, faz pedidos, acompanha a entrega e tudo mais, cada usuário vai gerar dezenas de requisições pros servidores de um iFood ou Uber da vida. As pesquisas sendo feitas precisam ser rápidas. Estamos falando de centenas de milhares de usuários acessando a plataforma ao mesmo tempo. Por mais parrudo que seja um servidor, nenhum aguentaria. Precisa distribuir essa carga em múltiplos servidores.</p>
<p>Quando se instala múltiplos servidores de aplicação, eles não podem todos acessar um único servidor de banco de dados. Iria gerar uma fila enorme e muita espera, muita latência entre requisições. O problema de bancos de dados relacionais como Posgres ou SQL Server é que foram originalmente feitos pra funcionar num único servidor parrudo. A gente escalava o serviço melhorando o hardware da máquina, colocando mais RAM ou colocando uma CPU maior. Eles não foram pensados pra funcionar em múltiplos servidores.</p>
<p>Hoje em dia fazemos um meio do caminho. O maior volume de operações costuma ser pesquisa. Como pesquisa de cardápio. Enquanto faz isso não está gravando nada, só pesquisando. Todos os serviços de bancos de dados SaaS modernos conseguem criar servidores réplica, cópia da original, automaticamente. Assim os servidores de aplicação web conseguem fazer a pesquisa em qualquer uma das réplicas, evitando que se crie gargalo só em um.</p>
<p>Mais do que isso, hoje em dia, um servidor de aplicação vai primeiro fazer a pesquisa num servidor de cache, como um Memcache ou Redis. Se usar uma AWS temos Amazon ElastiCache, que é basicamente Memcache ou Redis como serviço. A aplicação primeiro checa se tem o cardápio do restaurante no cache, se não tiver, aí pesquisa via SQL no banco de dados, grava no cache e serve de volta pro usuário. Muito da carga de pesquisa cai em cima de alguma solução de cache. É impossível criar serviços grandes como um iFood ou MercadoLivre sem ter um volume grande de cache.</p>
<p>Temos dois problemas com isso. O primeiro é que apesar de ser razoavelmente simples de criar servidores de réplica, é difícil de ter múltiplos servidores que aguentem escritas. Operações como INSERT, UPDATE ou DELETE acontecem num único servidor principal. Todas as outras réplicas costumam ser read-only, somente de leitura. Existe um pequeno delay, uma pequena latência, entre a escrita completar no servidor principal e as réplicas serem atualizadas, mas isso costuma valer a pena.</p>
<p>Todo ecommerce da vida, tem essa arquitetura de servidor de banco de dados mestre principal pra escritas, várias réplicas somente de leitura e muito cache, aliado à uma estratégia de jobs assíncronos, com Apache Kafka da vida. Estratégias que já expliquei no episódio do Ingresso.com, do Twitch e do Twitter, depois revejam. Mas mesmo assim, ainda tem casos especiais.</p>
<p>Digamos que temos um serviço de analytics, como um Google Analytics, capturando cada visualização, cada clique, toda operação que usuários estão fazendo em diversos apps, diversos sites, pra gerar monitoramento em tempo real, relatórios periódicos com informações de cada usuário.</p>
<p>Ou imagine que fazemos o software que captura informações vindas de Apple Watch ou Galaxy Watch. Informações de batimentos cardíacos, que cada usuário te manda 1 vez a cada 2 minutos. Apple e Samsung tem milhões de usuários, consegue imaginar esse volume de dados?</p>
<p>Ou imagine uma corretora de criptomoedas como uma Binance, com milhares de transações acontecendo a cada minuto, milhares de pessoas comprando e vendendo sem parar. Ou imagine um servidor de games com Call of Duty ou Fortnite, onde se captura informações de cada ação de cada jogador, cada movimento, cada tiro, cada pontuação. Jogadores do mundo inteiro.</p>
<p>Estamos falando de gigabytes, sendo gerados por minuto. É muito dado. E em cima desses dados, queremos gerar compilados, como o relatório da sua saúde na última hora, ou nas últimas 24 horas, ou do mês. Pra isso tem que ordenar, agrupar, processar e sumarizar gigabytes de dados recebidos por cada usuário. É muito pesado. E não por acaso essa discussão começou a surgir no fim dos anos 2000. Justamente quando redes sociais como Facebook, Twitter, estavam atingindo proporções que nenhuma outra plataforma nunca tinha atingido até aquele ponto. Sem contar o advento de Mobile e da popularização de games online.</p>
<p>Nessas circunstâncias, de fato, o esquema de bancos de dados relacionais atinge seus limites. As pesquisas ficam lentas pelo volume absurdo de dados. Pior, as escritas ficam absurdamente lentas, em parte por causa das garantias que estávamos acostumados a ter, como durabilidade. A idéia de que se mandamos o servidor gravar uma linha e ele diz &ldquo;ok&rdquo; significa que aquela linha garantidamente está no disco. Nesses cenários de Big Data, essa garantia se torna um problema.</p>
<p>Quando falamos em Big Data, precisamos lidar com premissas diferentes. Num banco, se sumir uma linha do seu extrato, é um grande problema. Literalmente a conta não vai fechar. Vai faltar um depósito. Vai faltar um pagamento. Dinheiro vai sumir. Pior, se uma linha for duplicada. Mesma coisa num ecommerce. Mesma coisa num sistema com da Receita Federal. Todos precisam de garantias na consistência e integridade dos dados. Por isso existe e sempre vai existir um lugar pra bancos de dados com características que um Oracle, SQL Server ou Postgres oferecem hoje.</p>
<p>Mas pra certos tipos de dados, como analytics, como dados de batimentos cardíacos, como dados de jogos online, mesmo se você perder ou duplicar uma linha dessas, qual o impacto? É baixo ou, no caso geral, nenhum impacto. Estando dentro de uma margem de erro, no fim do dia, você não tá interessado em cada clique individual no seu site, estamos interessados na quantidade geral por dia, por semana, por mês. Não estamos interessados no batimento exato de 5 minutos atrás, mas sim na média por hora, na média durante um exercício ou na média durante o sono. E assim por diante.</p>
<p>É por isso que a partir do começo dos anos 2010 começaram a surgir bancos de dados não relacionais, os NoSQL. Bancos de dados criados pra responder situações diferentes, em particular de Big Data, onde queremos lidar com séries de dados consolidados. Onde velocidade de escrita e baixa latência tem prioridade sobre consistência. Queremos poder escrever em múltiplos servidores ao mesmo tempo, mesmo que durante algum período de tempo, os dados fiquem ligeiramente inconsistentes.</p>
<p>O problema é como distribuir a carga, tanto de processamento pra pesquisa, quanto pra escrita, e como dividir os dados entre múltiplos servidores diferentes, pra permitir escalabilidade? Recapitulando: bancos de dados relacionais funcionam muito bem num único servidor, oferecendo as garantias ACID de atomicidade, consistência, isolamento e durabilidade que todos nós gostamos. Tem opções pra escalar via réplicas somente de leitura e, até certo ponto, replicação bi-direcional, que é sempre complicado. Mas pra situações de Big Data e volumes gigantes de escritas, começa a se tornar uma tarefa complicada, chata e com poucas opções.</p>
<p>Não tem como falar de NoSQL sem mencionar o famoso teorema CAP, proposto por Eric Brewer no ano 2000. Foi um divisor de águas no estudo de sistemas distribuídos e provavelmente você vai aprender no curso de ciências da computação, mas em resumo ele propõe que bancos de dados verdadeiramente distribuídos, ou seja, que rodam em múltiplos servidores, ou múltiplos nós de um cluster, no máximo satisfazem duas de três propriedades: consistência, availability ou disponibilidade e partition tolerance ou tolerância a partição.</p>
<p>Ou temos consistência e durabilidade, mas o sistema não tolera partições, ou seja, alguns nós do cluster podem ficar offline do nada. Isso não é legal porque não é impossível um servidor dar problema, ou mesmo termos que tirar um deles do ar pra manutenção. Então queremos consistência e tolerância a partição, mas aí temos baixa disponibilidade, ou vamos ter o ideal, disponibilidade e tolerância a partição, mas teremos baixa consistência, ou o que chamamos de &ldquo;consistência eventual&rdquo;, onde um servidor, por um ligeiro período de tempo, pode não ter os mesmos dados que os outros servidores.</p>
<p>CAP é um teorema e não uma teoria, ou seja, não é uma lei. Quando estudar vai encontrar que nem todo mundo concorda com todos os argumentos, em particular, porque os termos consistência, disponibilidade e tolerância a partição podem ser facilmente mal interpretados. Não tem uma definição muito exata pra cada coisa, e todo mundo sabe o que acontece quando termos ficam abertos à interpretação: ninguém se entende. E isso acontece com frequência, porque na minha cabeça eu quero falar de consistência mas você tá entendendo como integridade. E uma coisa é diferente da outra.</p>
<p>Daí tem o problema da natureza binária. É um dos poucos lugares que de fato não podemos levar ao pé da letra que só podemos escolher 2 propriedades das 3. No caso de termos disponibilidade e tolerância, não significa que não vamos ter nenhuma consistência. Depende se precisamos de consistência forte, onde é obrigatório que todas as réplicas sempre tenham exatamente o mesmo dado correto, ou se podemos ter consistência fraca, eventual, onde durante alguns segundos, os dados diferem entre os servidores. De novo, num banco, não podemos ter um saldo, recarregar o navegador e aparecer outro saldo, mas no exemplo dos batimentos cardíacos, se faltar um batimento durante 1 minuto, não vai ser um problema, não quer dizer que você teve uma parada cardíaca nem nada.</p>
<p>A outra coisa é tolerância a partição. De novo, pense num data center, onde um grande serviço pode ter um banco distribuído em múltiplos data centers, uma dúzia de servidores em Nova Iorque, outra dúzia de servidores em Londres, todos sincronizados, mas aí temos uma pane na internet entreeles. Um link submarino deu pane, caiu a fibra ótica. É raro acontecer, mas acontece. E o sistema tem que saber o que fazer quando acontecer, ou seja, ser tolerante a essa partição da rede.</p>
<p>O problema é que isso realmente é muito raro de acontecer. Nossa infraestrutura dos anos 2020 é mil vezes melhor que nos anos 2000, hardware, redes, infra em geral é muito mais estável. Então nossos sistemas não precisam ser tão agressivos na preparação pra uma situação tão rara. Talvez seja melhor ser otimizado considerando o caso que não vai acontecer, e colocar estratégias de degradação graciosa caso aconteça, como simplesmente uma página de &ldquo;aguarde alguns segundos&rdquo;. Mesmo se tiver pane na rede, hoje em dia, se recupera em poucos minutos. Antigamente poderia levar horas ou dias.</p>
<p>Essas são algumas das considerações ao se discutir o teorema CAP. Na prática vamos lidar com alguma estratégia de sharding e alguma estratégia de replicação de dados. Replicação é como acontece com um banco de dados relacional, onde podemos adicionar nós na rede, e eles vão receber uma cópia dos dados, daí cada servidor vai ficar idêntico ao outro, fornecendo redundância e possibilitando dividir a carga dos usuários pra mais servidores.</p>
<p>No caso de banco de dados relacional, por causa da característica de consistência forte, ficamos limitados na quantidade de servidores que podemos ter pra escrita. Num NoSQL, com consistência eventual, qualquer dos nós pode ser usado pra escrita, mas vai levar um tempo pra replicar os dados pros outros servidores. A consistência vai existir, mas vai ser eventual, ou seja, eventualmente vai estar tudo consistente.</p>
<p>Na real, bancos de dados NoSQL como Cassandra ou Dynamo tem como configurar que nível de consistência queremos ter. Mais forte ou mais fraco. Podemos configurar isso dependendo das necessidades. Uma coisa é um pequeno cluster de 3 servidores de Cassandra tudo no mesmo data center, na mesma rede. Outra coisa são múltiplos clusters de dúzias de servidores em vários de data center diferentes, como é a operação do tamanho de uma Netflix.</p>
<p>Bancos como Cassandra ou Dynamo foram feitos mais pra situações onde precisamos funcionar em múltiplos data centers. Dividido em regiões geográficas diferentes. Um no Japão, outro na Europa, outra na América e todos os servidores de todos os clusters sincronizando entre si. Não é uma situação que vamos encontrar todo dia.</p>
<p>Pra maioria das empresas médias, em vez de lidar com uma infraestrutura dessas do zero, o mais prático seria usar um DynamoDB da AWS. Como tudo da AWS, eles gerenciam a maior parte dessa complexidade e podemos só usar. Claro que, por causa disso, o custo por transação é bem mais caro. Mas é esse o trade-off que precisa ser estudado. Ou vamos ter que investir em contratar pessoal especializado em infraestrutura, pra ficar 24 por 7 lidando com hardware, bare metal, num data center.</p>
<p>Ambos Cassandra e Dynamo oferecem capacidades de particionamento e replicação. MongoDB também tem isso, mas ele chama de sharding e replica sets. O que buscamos é a capacidade de conseguir particionar os dados em múltiplos servidores de forma balanceada, e de ter replicação entre alguns desses servidores pra ter redundância. Se um deles tiver uma pane e cair, tem outra cópia idêntica pra continuar servindo os mesmos dados. É muito complexo pra tentar detalhar em um video, mas eu queria que vocês pelo menos soubessem que isso existe pra saber onde procurar depois.</p>
<p>Pra agora é interessante conhecer um último conceito: IDs. Voltando pro exemplo lá do começo, a tabela de contatos feito em DBF com dBase. Lembram que falei que mesmo se eu fizer um índice de nome, precisaria procurar pelo nome completo pra achar? Hoje em dia, em qualquer banco de dados, todos oferecem alguma forma de pesquisar parcialmente ou palavras que soam parecido. É o que um Elasticsearch, Solr ou os recursos de full-text search de um Postgres e outros bancos de dados oferecem. É um índice diferente pra esse tipo de pesquisa em particular. Expliquei um isso no episódio do Twitter, vejam lá depois.</p>
<p>Antigamente, nos anos 80, não tínhamos o poder de processamento pra fazer coisas desse tipo. Em vez de tentar lembrar se &ldquo;Brian Russell&rdquo; tem dois &ldquo;S&rdquo; ou dois &ldquo;L&rdquo;, em muitos casos era mais fácil lembrar o código dele, o ID. Por isso toda tabela a gente se acostumou a colocar esse código. Assim como todo pedido de compra tem um código. Assim como todo indivíduo nesse país é um CPF. Toda linha numa tabela é um código numérico, que usamos como uma chave primária ou, no caso de bancos relacionais, usamos pra fazer uma linha em uma tabela referenciar uma linha em outra tabela, e chamamos esses IDs de chaves estrangeiras, porque ela é estrangeira à tabela.</p>
<p>O jeito considerado errado hoje, principalmente em códigos que vamos divulgar pros usuários, é fazer a chave primária ser um número auto-incremental. Ou seja, começando de 1 e incrementando, próxima linha sendo 2, linha seguinte sendo 3 e assim por diante. Um erro de segurança que foi muito comum antigamente era ter uma aplicação web onde a URL mostra esse código, por exemplo, digamos que tivesse <code>mercadolivre.com/users/1</code> que é a URL pra página com os dados cadastrais de um usuário.</p>
<p>Num sistema bem feito, quando o usuário faz login, eu guardo o ID dele na sessão. Quando ele navega pra outras páginas privadas, checamos com o ID na sessão antes de mostrar a informação. Se ele tentar acessar a URL que seria de outro usuário, conseguimos invalidar o pedido e devolver um erro 403 não permitido. Esse seria o certo, mas num sistema grande, alguém pode deixar passar um bug e não checar.</p>
<p>Daí alguém de fora, percebendo que estão usando IDs auto incrementais, e o dele é ID 1000, óbvio que significa que tem usuários abaixo de 1000 e alguns acima, e ele vai começar a tentar forçar IDs até alguma página abrir na conta de outro usuário. O certo é não ter esse bug. Mas também não é legal oferecer informação de graça pra estranhos, como a existência de outros usuários com IDs fáceis de achar.</p>
<p>O ideal é usar um número aleatório gigante. Um campo de inteiro de 32 bits pode conter números de 0 até mais de 4 bilhões. Mas se for um inteiro de 64 bits, estamos na ordem de 18 milhões de trilhões. É muito número. Por isso não precisamos usar números sequenciais auto incrementais, use números aleatórios. Vai ser perto de impossível tentar adivinhar o ID de alguém.</p>
<p>Pra isso usamos funções geradores de números como UUIDs. Nem precisamos usar o número inteiro que ele gera, basta truncar e pegar um pedaço e as chances de colisão, ou seja, de escolher um número que já existe na sua tabela, vai ser hiper baixo. E como o certo é criar campos de chave primária com constraint de unicidade, ele não vai deixar inserir um ID duplicado. Existem dezenas de estratégias pra gerar números aleatórios pra IDs, vale a pena parar pra pesquisar e evitar números sequenciais. Chamamos isso de chaves surrogadas. Não é nenhuma lei, mas é uma boa prática. Pra tabelas internas, cujos IDs você nunca vai mostrar em público, aí tanto faz.</p>
<p>Expliquei isso porque em sistemas distribuídos, temos o problema de balancear dados entre diferentes servidores. Quando replicamos dados, estamos fazendo uma cópia dos dados de um servidor pra outro, pra servir de redundância, mas num sistema grande distribuído, queremos dividir o processamento, e isso exige que alguns servidores vão ter dados que outros servidores não vão ter. Deixa eu dar um exemplo besta.</p>
<p>Digamos que trabalho na Receita Federal. Eca. Fazer um único banco de dados com todos os impostos de renda, de todas as pessoas e empresas, num único banco de dados, é bem pesado. Ainda mais se tiver a informação histórica de todo mundo das últimas décadas. Um jeito ingênuo de lidar com isso é manualmente dividir os dados. Por exemplo, posso fazer um servidor com dados da região Sudeste, outro servidor só pra região Sul, outro só pra região Nordeste e assim por diante.</p>
<p>Problema que um servidor vai continuar tendo mais volume de dados que outros. Não vai ser balanceado. Rapidamente o da região Sudeste vai precisar dividir. Daí teríamos que fazer um servidor só pra São Paulo separado. Mas mesmo assim, o volume só de São Paulo sempre vai ser muito maior que tudo, vamos dividir pelo menos a cidade de São Paulo num único servidor sozinho. Tá vendo o trampo? Ficar tentando dividir os dados assim manualmente vai ser um parto. Pior ainda pro software. Teríamos que fazer algo como &ldquo;if sao paulo, procura no servidor X&rdquo;, ou &ldquo;if regiao nordeste, procura no servidor Y&rdquo;. Isso rapidamente fica caótico.</p>
<p>Existe uma solução melhor. Antes, vamos ver um outro jeito ingênuo de balanceamento. Alguém poderia pensar &ldquo;hum, e se não olharmos pros dados, esquece isso de região ou estado, vamos olhar só pro ID&rdquo;. Nesse caso digamos que os IDs são numéricos e sequenciais, exatamente como eu disse pra não fazer. Daí temos 3 servidores que vamos chamar de A, B e C e 12 itens na tabela, com ID de 1 a 12, pra simplificar. Como eu divido esses 12 itens entre os 3 servidores?</p>
<p>Fácil, que tal usarmos o módulo do ID? Lembra a idéia de módulo? No caso faríamos módulo 3. Módulo é quando você pega o restante da divisão de um número por outro. Por exemplo. 1 módulo de 3 seria 1, 2 módulo de 3 seria 2, 3 módulo de 3 é 0, por que 3 dividido por 3 dá um e sobra zero. Interessante é acima disso. 4 dividido por 3 é 1 e sobra 1, então o módulo é 1.</p>
<p>5 dividido por 3 é 1 e sobra 2, então o módulo é 2, mas 6 dividido por 3 é exato 2 e sobra zero, o módulo é zero. Qualquer número módulo 3 vai ser um número de 0 a 2. Sempre vai rotacionando. Estabelecendo arbitrariamente que o servidor A é zero, B é 1 e C é 2, sabemos como dividir cada uma das 12 linhas em cada um dos 3 servidores, sacaram?</p>
<p>Então no servidor A teremos os itens com IDs, 1, 4, 7 e 10. No servidor B teremos os itens com IDs 2, 5, 8 e 11 e no servidor C teremos os itens com IDs 3, 6, 9 e 12, todos os múltiplos de 3. E toda vez que quisermos inserir uma nova linha, fazemos o módulo do ID e vemos pra qual servidor mandar. Isso garante que todos  vão estar balanceados, ou seja, todos vão ter exatamente o mesmo número de linhas. Resolvido então?</p>
<p>Mas é a Receita Federal, eles crescem infinitamente. Muito em breve só 3 servidores não vai dar conta. Precisamos adicionar mais um novo servidor. Agora fodeu. A conta passa de módulo 3 pra módulo 4. Ou seja, precisamos rebalancear todos os dados em todos os servidores. Na nova configuração, cada ID vai ficar num servidor diferente do que tava antes. Essa estratégia funciona pra balancear se nunca mexermos na quantidade de servidores, mas  não escala se precisarmos adicionar novos, porque precisa mover todos os dados de lugar pra rebalancear.</p>
<p>Por isso foi inventado o algoritmo de Consistent Hashing em computação distribuída. É um esquema de organização de IDs que permite adicionar ou remover servidores sem precisar mover todos os dados de lugar toda vez. Ele consegue isso organizando todos os IDs num anel, um círculo de hashes. Pra entender hashes recomendo que assistam o primeiro episódio de criptografia.</p>
<p>Em resumo bem resumido, hash é uma função que pega qualquer informação, por exemplo uma string, e devolve um número de tamanho fixo. Mas é uma função não reversível, ou seja, a partir do número não tem como encontrar a string original, porque diversas strings podem chegar no mesmo número. A função só garante que se você passar a mesma string, vai chegar no mesmo número.</p>
<p>Dependendo da função ela devolve um número até um determinado tamanho, por exemplo, um SHA512, vai devolver um número de até 512 bits. O número máximo de 512 bits é um absurdo maior que a quantidade de átomos no universo visível. Obviamente é exagerado demais pra um campo de ID. Um Cassandra usa outra função de hash não criptográfico, ou seja, que não deve ser usado pra criptografia, mas é bom o suficiente pra IDs, o Murmur3 que devolve hashes de 64 bits, onde o maior número é da ordem de milhões de trilhões, que ainda é um número gigante, mas mais gerenciável.</p>
<p>Enfim, o principal é que no sistema de consistent hashing consideramos um anel que vai de 0 até esse número máximo de 64 bits. Daí fazemos o hash dos IDs dos servidores. Digamos que temos 4 servidores. Eles se distribuem pelo anel. O lance de uma função de hash é que ela tende a ter uma distribuição uniforme, o oposto de sequencial. Podemos gerar o hash dos dados do registro, com a combinação de diversos dados como o endereço IP do usuário mais o horário, ou qualquer coisa assim.</p>
<p>Por exemplo, imagine um anel onde cada ponto desse anel começa em 0 no topo e tem todos os números até 2 elevado a 64, no final do anel, encostado no zero, pra dar uma volta. Entenderam, todos os IDs possíveis estão no anel. Daí aplicamos a função de hash pros nomes ou endereços IP de cada servidor, e aí eles caem arbitrariamente pelo anel, só pro exemplo, o servidor A lá em cima, depois o B mais pra direita lá embaixo e o C mais pra esquerda embaixo.</p>
<p>Vamos inserir alguns dados, digamos que é o hash do nome do registro. O primeiro registro tem o nome de John e digamos que o hash gerado caia entre o Nó A e o Nó B. A regra é pra ele escolher o servidor com o hash maior ou igual ao do ID olhando na direção de relógio, da esquerda pra direita. Nesse caso vai ser o Nó B. O próximo registro tem nome de Fred e o hash por acaso tem ponto aqui entre o B e o C, então ele cai no Nó C. E o último registro com nome de Susan cai aqui entre o C e o A, então vai no A. Sempre na direção de relógio.</p>
<p>Beleza e qual a vantagem disso? Bom, vamos voltar pro cenário onde queremos adicionar um novo servidor, o nó D e o hash dele vai aqui entre o C e o A, por acaso. Mas ele cai depois da Susan. O que acontece é que todos os dados com IDs depois do C que estavam caindo no Nó A, agora precisam ser movidos pro Nó D. É assim que rebalanceia. Mas prestem atenção, no exemplo de usar a função simples de módulo, precisaria mover todos os dados de todos os servidores quase. Aqui, os dados dos servidores B e C não precisam ser mexidos, só os do A que precisam ser divididos. Os registros com IDs maiores que D e menores que A, continuam no A.</p>
<p>Digamos que o servidor A tenha uma pane qualquer e precise ser removido. E agora? Todo registro que antes cairia no A, pela regra de olhar o próximo servidor em sentido de relógio no anel, então vai passar a cair no B. Só isso. E nenhum dado de nenhum outro servidor precisa ser mexido. Falando simplificadamente é assim que garantimos ter a menor quantidade possível de dados sendo movidos quando a infraestrutura muda. A idéia é mover a menor quantidade de dados quanto possível quando precisamos adicionar novos servidores.</p>
<p>Tem várias nuances nessa estratégia. Por exemplo, quando da remoção de um Nó, pode acontecer de outro Nó acabar acumulando dados demais. Pra evitar isso, cada Nó pode ter múltiplos hashes aleatórios que o colocariam em múltiplas posições no anel, daí os dados poderiam ser melhor rebalanceados. Se ficou interessado, depois leia a documentação a respeito em sites como do Cassandra ou no paper do DynamoDB da Amazon.</p>
<p>Dizendo assim parece fácil, mas em DynamoDB por exemplo, você tem que entender o conceito de chave de partição e chave opcional de ordenamento. Dados com a mesma chave de partição caem na mesma partição, obviamente. O problema é se quisermos fazer uma pesquisa que precisaria buscar dados em múltiplas partições. Pra isso tem comandos com o &ldquo;Scan&rdquo;, mas isso é uma operação bem mais custosa do que pesquisar dentro de uma mesma partição.</p>
<p>Portanto a estratégia de partição precisa estar muito bem definida no começo, senão vai ter obstáculos de não conseguir fazer pesquisas rápidas quando precisar. As coisas não são automáticas, trocamos conveniência por performance. Num banco relacional, onde normalmente fica tudo junto, é só rodar um comando SQL e temos o que queremos rápido. Num sistema distribuído as coisas estão, duh, distribuídas, e precisamos levar isso em consideração.</p>
<p>No DynamoDB, que é oferecido com um serviço da AWS, precisamos entender bem o comportamento da nossa aplicação, não só pra não dificultar operações depois, mas porque a cobrança também não é simples. Não se paga por gigabyte armazenado, por exemplo. Precisa calcular a quantidade de escritas por segundo. A quantidade de escritas por segundo durante um pico. A duração de cada operação de leitura, porcentagem de capacidade de leitura que quer deixar reservado, o nível de consistência das leituras, quantas quer que sejam fortemente consistemente, quantas fracamente consistentes.</p>
<p>Por exemplo, na calculadora online deles, o preço por gigabyte, na região US east, é de meros 25 centavos por mês. Sucesso então? Não, com 10 escritas não transacionais, 100 escritas por segundo em horário fora de pico, 400 escritas por segundo em horário de pico, 72 horas operando em pico durante o mês, com capacidade reservada de escrita pra um ano, pode somar mais 23 dólares por mês e 150 dólares que precisa pagar a vista logo no começo pra reserva.</p>
<p>Daí continuando com os valores padrão pra leitura, só pra não entediar, mais 3 dólares por mês e mais 30 dólares upfront à vista. Então, por mês nessa configuração vai pagar uns 26 dólares, mais 180 dólares à vista na entrada. E essa configuração é a mais basiquinha, pra um app pequeno.</p>
<p>Na categoria de bancos de dados NoSQL oferecidos como serviço, ou seja, que não precisamos saber como configurar e dar manutenção na infraestrutura, tem diversas outras opções, como MongoDB Atlas ou Google Firebase. Firebase é bem popular pra aplicações pequenas, porque até 1 gigabyte de mensagens, até 1 gigabyte de armazenamento, 5 gigabytes de cloud storage, o custo é gratuito. Já um MongoDB Atlas você paga até que &ldquo;barato&rdquo; a 10 centavos por milhão de operações de leitura, ou o recomendado 57 dólares por mês pra solução dedicada de 10 giga até 4 terabytes de armazenamento.</p>
<p>Não é simples calcular o real valor que vai pagar, tem dezenas de parâmetros pra considerar, como quantidade de invocação de funções e gigabytes por segundo ou minutos de cloud build ou gigabytes transferidos pela rede e mais. O preço vai variar de acordo com o sucesso ou fracasso da sua aplicação. Quanto mais &ldquo;barato&rdquo; ficar, mais fracassado é seu app, quanto mais sucesso fizer, mais caro vai ficar. No começo parece uma grande vantagem, mas justo quando fizer sucesso e achar que vai ganhar bastante, é também quando eles vão te cobrar mais caro. Essa é a faca de dois gumes de serviços que você não gerencia. Uma hora a conta chega.</p>
<p>Isso sem contar o custo do desenvolvimento e manutenção da sua aplicação. Quanto mais exótico for um banco de dados, mais difícil vai ser encontrar profissionais qualificados ou treinar em casa. Só porque um banco é exótico, não significa que resolve seus problemas. Em muitos casos, alguém pode escolher o Google Firebase pelo hype mas na realidade o desenvolvimento teria ficado mais barato se tivesse escolhido, digamos um MongoDB Atlas.</p>
<p>Ambos Firebase e MongoDB tem a flexibilidade de lidar com documentos estilo JSON, sem precisar se prender à estrutura mais rígida de um schema de banco de dados relacional. Mas só dizer JSON não significa que são iguais. MongoDB permite lidar com documentos mais complexos, embedded arrays, sub-documentos. O modelo do Firebase é bem mais simples.</p>
<p>Mas o Firebase tem atualizações em tempo real, que é importante pra quem desenvolve aplicações mobile. Mudanças no banco podem ser empurradas como notificações direto pros usuários em tempo real. MongoDB não tem isso nativo. Dá pra colocar mas dá mais trabalho, portanto é mais custo de desenvolvimento.</p>
<p>Por outro lado, se quisermos fazer pesquisas mais complexas, MongoDB tem uma linguagem de queries mais avançada, incluindo coisas como joins e índices secundários. Firebase tem capacidades de query, mas limitadas. Da mesma forma, transações. Todo mundo foge de banco relacional, mas todos adoram ter transações e dados fortemente consistentes. MongoDB tem mais suporte a transações do que Firebase. De novo, se quiser algo semelhante a transações no Firebase, o custo de desenvolvimento sobe. E se transações é realmente importante, talvez um banco relacional como AWS RDS tivesse sido melhor, em primeiro lugar.</p>
<p>Mas a vantagem de serem &ldquo;as a service&rdquo; quer dizer que tanto MongoDB Atlas quanto o Filestore do Firebase tem escalabilidade e replicação automáticas em múltiplas regiões pra alta disponibilidade. Ou seja, se throughput de operações e garantir que nunca fique fora do ar seja primordial, ambos podem ajudar com isso. Mas você vai pagar por isso, e não vai ser barato. Como expliquei, o modelo de precificação é complexo. Precisa seriamente gastar tempo pesquisando e avaliando muito bem o que acontece não só agora, que sua aplicação tá no começo, mas daqui seis meses se fizer sucesso.</p>
<p>Não tem como eu explicar todos os bancos de dados oferecidos como serviços hoje. A Amazon é a mais famosa. Todo mundo escolhe o AWS RDS pra soluções de MySQL ou Postgres como serviço, mas o Google também tem o Cloud SQL como concorrente. Vai depende de precificação e funcionalidades que pra você podem ser importantes. Além deles tem o Azure SQL Database e acho que até a Oracle oferece o banco de dados SQL deles como serviço.</p>
<p>Pra complicar a vida, a própria Amazon oferece produtos similares na mesma plataforma, como o Apache Aurora que parece com RDS. Aurora é um produto superior de banco de dados MariaDB, Postgres, SQL Server, Oracle, tem maior garantia de disponibilidade, dobro da performance no mesmo perfil de hardware, mais facilidade de escalabilidade, opções de backup, replicação, porém, obviamente custa mais caro. Ou seja, tem SQL pra todos as necessidades e capacidade de pagamento.</p>
<p>Digamos que você é um apologista de NoSQL, não quer mesmo SQL. Daí tem o MongoDB Atlas e Google Firestore que já falei, mas temos mais opções. Pro mundo enterprise, a SAP oferece o HEC que é o HANA Enterprise Cloud. Tem a ScyllaDB que oferece uma versão de Cassandra deles e tem o AstraDB da DataStax que também é Cassandra como serviço. Isso sem contar o Heroku que oferece Postgres como serviço, Redis como serviço. E em termos de cache, Memcache e Redis, a AWS tem o Elasticache como já falei. Tem serviços pra todo mundo, é só conseguir pagar.</p>
<p>Mas, pra você que é iniciante em programação, não se deixe enganar por hype de NoSQL. A gente vem discutindo esse assunto desde pelo menos 2008. E eu posso te garantir algumas coisas: não existe consenso, pelo simples fato que cada banco de dados tem pontos fortes e pontos fracos, cada um serve pra situações diferentes. Pra aplicações bem pequenas, meio que tanto faz se vai escolher um banco relacional SQL como um MariaDB ou Postgres ou se vai direto pra um FireStore da vida. Você tem poucos dados, tem poucos problemas pra lidar.</p>
<p>A primeira versão de qualquer coisa que fizer sempre vai ser isso: versão 1.0. Só quando tiver usuários de verdade, dados de verdade, receita e faturamento de verdade, só aí vai realmente entender o que precisa. E se ficar limitado a usar só uma ferramenta pra tudo, em breve vai ter tanto problema, e vai ignorar que existem já soluções pra cada problema, que corre o risco de fracassar por ignorância.</p>
<p>Na dúvida, a solução mais comum é a mesma: AWS RDS com Postgres ou MariaDB, que é o que a grande maioria dos frameworks web usam, como Ruby on Rails. Django, Laravel, Spring. Daí uma solução de cache usando Redis ou Memcache, e pra isso tem AWS ElastiCache. E finalmente jobs assíncronos, seja com Kafka, seja com AWS SQS. Isso resolve 90% dos seus problemas. Se precisar de alguma coisa de documentos schemaless em JSON, um Postgres já suporta isso nativamente. Quer só fazer um protótipo, vai de Firebase.</p>
<p>Se por acaso estiver na rara categoria de Big Data, significa que você não é uma micro empresa, ou um freelancer. Já é uma empresa média. Aí é outra categoria, já vai olhar pra Aurora, DataStax, e soluções pra Data Science como Apache Spark, Hadoop e muita coisa customizada. Vai ter profissionais experientes de infraestrutura de devops. Essa categoria não é pra amadores. Por isso, se for iniciante, não se preocupe com isso tão cedo. Você não é o Netflix.</p>
<p>No fim do dia, entender bancos de dados, sejam relacionais, sejam NoSQL, significa entender o problema que se quer resolver primeiro. Daí aplicar fundamentos de ciências da computação. Árvores, estruturas de dados, redes, sistemas de arquivos, sistemas operacionais, fundamentos de criptografia. Ficar acompanhando discussões inúteis em fios de Twitter da bolha dev, só vai te confundir, nunca tem nada de útil e você vai ser mais um maria-vai-com-as-outras. Se atenha a pesquisar documentação oficial de cada coisa.</p>
<p>A grande maioria dos programadores iniciantes hoje tem o mesmo problema de ignorar fundamentos, ignorar os problemas que se quer resolver, e escolher baseado em likes. Influencer nenhum sabe o seu cenário particular. Óbvio que vai dar ruim. 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ê, até mais.</p>
]]></content:encoded><category>bancos-de-dados</category><category>sistemas-distribuidos</category><category>cloud</category><category>akitando</category></item><item><title>[Akitando] #142 - Entendendo COMO ChatGPT Funciona - Rodando sua Própria IA</title><link>https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</guid><pubDate>Mon, 19 Jun 2023 17:47:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/O68y0yRZL1Y"
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, AGI não está próximo de acontecer.&lt;/p&gt;
&lt;p&gt;Quais são os limites da atual tecnologia de transformadores generativos pré-treinados, vulgo GPT?
O que são os tais &amp;ldquo;modelos&amp;rdquo; que eles usam?
O que são os tais &amp;ldquo;parâmetros&amp;rdquo;.
Porque as IAs parecem humanas nas respostas?
IAs vão substituir programadores??
Como é possível conseguir rodar um clone de ChatGPT na sua própria máquina, totalmente offline?&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/O68y0yRZL1Y"
    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, AGI não está próximo de acontecer.</p>
<p>Quais são os limites da atual tecnologia de transformadores generativos pré-treinados, vulgo GPT?
O que são os tais &ldquo;modelos&rdquo; que eles usam?
O que são os tais &ldquo;parâmetros&rdquo;.
Porque as IAs parecem humanas nas respostas?
IAs vão substituir programadores??
Como é possível conseguir rodar um clone de ChatGPT na sua própria máquina, totalmente offline?</p>
<p>Vamos responder tudo isso e muito mais.</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:01:07 - CAP 01 - Minha IA PESSOAL e OFFLINE - text-generation-webui</li>
<li>00:06:37 - CAP 02 - Introdução a MODELOS - Como IA Aprende?</li>
<li>00:10:09 - CAP 03 - Auto-Corretor de Teclado de Celular - Cadeias de Markov</li>
<li>00:15:34 - CAP 04 - 170 TRILHÕES de parâmetros - Maior que nosso cérebro?</li>
<li>00:20:28 - CAP 05 - De Markov a IA - O que são Tensors?</li>
<li>00:30:53 - CAP 06 - A Revolução da Auto-Atenção - O que são Transformers?</li>
<li>00:35:24 - CAP 07 - Hardware pra Treinar IA - NVIDIA</li>
<li>00:42:09 - CAP 08 - Parâmetros e Sinapses são equivalentes? - Quantização</li>
<li>00:49:42 - CAP 09 - Estamos próximos de AGI? IAs são Inteligentes?</li>
<li>00:52:10 - CAP 10 - Controlando Criatividade de IAs - Temperatura</li>
<li>01:00:27 - CAP 11 - A Avó do ChatGPT - Eliza</li>
<li>01:04:29 - CAP 12 - IAs que fazem Imagens - Diffusion</li>
<li>01:09:31 - CAP 13 - Discutindo PROMPTs - LangChain</li>
<li>01:13:08 - CAP 14 - IAs vão matar programadores? Quais os Limites?</li>
<li>01:22:15 - CAP 15 - Virando &ldquo;Programador 10x&rdquo; com auxílio de IAs - Copilot</li>
<li>01:23:47 - CAP 16 - CONCLUSÃO - O começo da BOLHA</li>
<li>01:28:18 - 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://ai.facebook.com/blog/large-language-model-llama-meta-ai/"target="_blank" rel="noopener">Introducing LLaMA: A foundational, 65-billion-parameter language model (facebook.com)</a></li>
<li><a href="https://github.com/NVIDIA/Megatron-LM"target="_blank" rel="noopener">NVIDIA/Megatron-LM</a></li>
<li><a href="https://github.com/oobabooga/text-generation-webui"target="_blank" rel="noopener">oobabooga/text-generation-webui</a></li>
<li><a href="https://huggingface.co/TheBloke/Wizard-Vicuna-30B-Uncensored-GPTQ"target="_blank" rel="noopener">TheBloke/Wizard-Vicuna-30B-Uncensored-GPTQ</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi"target="_blank" rel="noopener">Neural networks - YouTube</a></li>
<li><a href="https://towardsdatascience.com/gpt-4-will-have-100-trillion-parameters-500x-the-size-of-gpt-3-582b98d82253"target="_blank" rel="noopener">GPT-4 Will Have 100 Trillion Parameters — 500x the Size of GPT-3 | by Alberto Romero | Towards Data Science</a></li>
<li><a href="http://introtodeeplearning.com/"target="_blank" rel="noopener">MIT Deep Learning 6.S191 (introtodeeplearning.com)</a></li>
<li><a href="https://www.youtube.com/watch?v=i9ikne_9iEI&amp;t=1646s"target="_blank" rel="noopener">DF Direct Special: Starfield Tech Breakdown - 30FPS, Visuals, Rendering Tech + Game Impressions - YouTube</a></li>
<li><a href="https://www.youtube.com/watch?v=It9D08W8Z7o&amp;t=102s"target="_blank" rel="noopener">NVIDIA Made a CPU.. I’m Holding It. - YouTube</a></li>
<li><a href="https://twitter.com/miolini/status/1634982361757790209"target="_blank" rel="noopener">(2) Artem Andreenko 🇺🇦 on Twitter: &ldquo;I&rsquo;ve sucefully runned LLaMA 7B model on my 4GB RAM Raspberry Pi 4. It&rsquo;s super slow about 10sec/token. But it looks we can run powerful cognitive pipelines on a cheap hardware. https://t.co/XDbvM2U5GY&rdquo; / Twitter</a></li>
<li><a href="http://scihi.org/emile-borel-infinite-monkey-problem/"target="_blank" rel="noopener">Émile Borel and the Infinite Monkey Problem | SciHi Blog</a></li>
<li><a href="https://github.com/codeanticode/eliza"target="_blank" rel="noopener">GitHub - codeanticode/eliza: The classic Eliza psychologist program, implemented as a Processing library.</a></li>
<li><a href="https://www.instagram.com/stories/highlights/18020038894467644/"target="_blank" rel="noopener">Stories • Instagram</a></li>
<li><a href="https://huggingface.co/TheBloke"target="_blank" rel="noopener">TheBloke (Tom Jobbins) (huggingface.co)</a></li>
<li><a href="https://python.langchain.com/docs/get_started/introduction.html"target="_blank" rel="noopener">Introduction | 🦜️🔗 Langchain</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>Este é um episódio que eu tava tentando evitar fazer. Primeiro porque acho que vai ser um dos temas onde parte do video vai acabar ficando obsoleto meio rápido, já que as tecnologias de IA estão acelerando e mudando bastante ainda. Segundo, porque eu mesmo nunca trabalhei com IA. Maioria das coisas que explico neste canal, já me envolvi em projetos reais de alguma forma, por isso não é só teoria, é experiência. Mas IA, eu brinquei, mas nunca trabalhei nem fiz pesquisa acadêmica, nem nada disso, por isso nunca me senti adequado pra explicar.</p>
<p>Mas considerando que quase a totalidade de videos feitos sobre o assunto hoje são de pessoas com menos conhecimento ainda, fazendo afirmações cada vez mais absurdas; até eu certamente consigo fazer muito melhor. Pra variar, os oportunistas já saíram lançando cursos e afins. No fim do video vou explicar porque todos são uma droga e você deve evitar. Também vou explicar de novo porque ela não vai substituir programadores. Hoje vou explicar o que de fato é um ChatGPT e onde estamos quando se fala em IA. Então vamos lá.</p>
<p>(&hellip;)</p>
<p>Esses dias resolvi brincar de IA, o objetivo era ter o meu próprio ChatGPT, rodando offline, totalmente local, na minha máquina, sem conectar com nenhuma API de terceiros como da OpenAI. Isso ficou fácil porque agora existem diversos esforços da comunidade de código aberto, em particular projetos de Open LLM, ou Large Language Models, que é a categoria de IA onde se encontra um GPT.</p>
<p>Esses esforços ganharam força quando a Meta decidiu abrir seu modelo Llama em fevereiro de 2023. As big tech tem diversos modelos prontos, cada um pra algum uso específico, desde respostas gerais ou mais focados em código e assuntos específicos. Por exemplo, a OpenAI tem os modelos GPT 3.5, GPT 4, Codex. A Microsoft tem o Zero, ou Megatron em conjunto com a NVIDIA. O Google tem modelos como o BERT, Palm, LaMDA, Minerva e outros.</p>
<p>A Meta, de novo, tem o OPT, Galactica, METALM, LLaMA. Alguns desses modelos são citados em papers mas são fechados, como o novo BARD do Google ou o próprio GPT 4 da OpenAI. Mas o lançamento do LLaMA ao público foi um evento importante e a Meta parece que liberou vários outros como o OPT e Galactica. A primeira parte do video de hoje vai ser entender o que diabos são esses modelos.</p>
<p>Isso foi em fevereiro, este video tá saindo em junho, só 4 meses. E de lá pra cá a comunidade usou o LLaMA pra criar derivados mais otimizados e menores pra rodar em computadores menos parrudos. Surgiram diversas variantes como Alpaca, Vicuna, GPT4ALL, Koala, Dolly e dezenas de outros. Literalmente dezenas. O site HuggingFace, que é um repositório de modelos e ferramentas pra inteligência artificial, tem listado uns 200 modelos diferentes.</p>
<p>O que dá pra fazer com esses modelos abertos? Deixa eu mostrar. Eu queria um ChatGPT pessoal, offline, que ninguém sabe o que tô conversando com ele, sem filtros, sem controle, sem nada. Só eu e a ferramenta. Existem vários projetos abertos que implementam interface web similar ao ChatGPT, onde posso digitar perguntas, ver as respostas da IA, e ficar conversando com ela, sem precisar estar conectado na internet. Olha só esse exemplo.</p>
<p>(&hellip; screencast text-generator &hellip;)</p>
<p>Se assistiram meu episódio sobre games em máquina virtual, sabem que tenho uma máquina que alguns consideram &ldquo;parruda&rdquo;, um AMD 5950X de 16 cores, 32 threads, rodando a uns 4 gigahertz por core. Mais 64 gigabytes de RAM DDR4. Uma GPU NVIDIA RTX 3090 com 24 gigabytes de memória GDDR5. Fora meu NAS Synology de 60 terabytes, conectado em rede 10 gigabits. Meus testes foram feitos numa máquina virtual QEMU rodando Ubuntu normal, com passthrough de PCI pra ter acesso direto à GPU NVIDIA. Todos os detalhes sobre essa QEMU eu mostrei no video de Games em Máquina Virtual.</p>
<p>Sem nenhum motivo em particular, escolhi o projeto aberto &ldquo;text-generation-webui&rdquo; que é uma aplicação web escrita em Python. Ela simula a interface web de chat do ChatGPT. Por baixo, carrega bibliotecas como bitsandbytes, pytorch e outros que vou explicar depois. Daí podemos baixar modelos e fazer ele carregar um deles. No caso estou rodando Vicuna 30b quantizado. De novo, já vou explicar o que isso significa. Dizem que tem qualidade parecida com GPT 4 mas pra mim pareceu mais um GPT 3. As respostas que consigo no GPT 4 ainda são mais completas do que nesse Vicuna.</p>
<p>Mas mesmo assim é impressionante. Estão vendo? Estou conversando de boas. E não está conectando com nenhum serviço online de ninguém, nem da Microsoft, nem da OpenAI, nem do Google. Tudo rodando offline, local, dentro da minha máquina virtual. Vou repetir, porque quando postei sobre isso no Instagram, muita gente ficou confusa. Sim: dá pra rodar um programa similar ao serviço de ChatGPT da OpenAI num computador normal. E não se trata de uma demonstração hello world, realmente funciona. Não precisa de um monte de servidores parrudos pra conseguir isso. Ué, mas eu achava que precisava ter o tamanho de uma Microsoft ou Google pra fazer isso, o que mudou?</p>
<p>A exigência de hardware depende da complexidade do modelo. Como falei, estou usando Vicuna de 30 bilhões de parâmetros, mas existem modelos menores, como o próprio Vicuna de 7 bilhões de parâmetros, ou a Ada da Microsoft, de só 350 milhões. Se o modelo for pequeno o suficiente, é possível até rodar num bom smartphone Android ou um Raspberry Pi com um upgrade de RAM. O tamanho do modelo é um dos fatores que pode afetar a qualidade das respostas, portanto quanto menor o modelo, mais simples seriam as respostas. Simplifiquei sobre &ldquo;tamanho&rdquo;, guardem essa informação que já vou explicar mais.</p>
<p>Deixa recapitular um pouco. O que diabos é isso de modelo? Honestamente, eu mesmo não sei dizer em todos os detalhes, pra isso precisaria ter estudo e treinamento em inteligência artificial, em particular redes neurais. Muita gente aprendeu isso em optativas ou iniciação científica na faculdade de ciências da computação. Não vou tentar dar explicação acadêmica, mas sim dar uma intuição pra maioria de vocês conseguirem ter uma imagem na cabeça. Acadêmicos, sejam compreensivos, e se quiserem complementar, sintam-se a vontade nos comentários abaixo.</p>
<p>Pense em redes neurais como uma simulação do aprendizado que acontece no nosso cérebro. Sabemos que temos neurônios. Aprendizado e memória acontecem quando temos sinapses, conexões desses neurônios, ou comunicação, ou melhor &ldquo;ativação de um neurônio&rdquo;, que é como se ele escolhesse um caminho dentre muitos. Um neurônio pode ter milhares de conexões, dizem que umas 7 mil. De novo, explicação grosseira. Mas é mais ou menos isso que temos em redes neurais. Assista os videos do canal 3Blue1Brown sobre redes neurais pra entender em mais detalhes.</p>
<p>No caso específico de texto, poderíamos pensar que a forma de fazer computadores aprenderem a interpretar e gerar texto seria cadastrando regras gramaticais, ortográficas, vocabulário, dicionários, e assim ele conseguiria construir frases gramaticalmente corretas. Mais ou menos como você pensa que é o jeito certo de aprender uma língua nova como inglês ou francês num curso qualquer. Esse parece ser o jeito intuitivo, certo?</p>
<p>Mas se assistiu meu video de Como eu Aprendi Inglês e a minha live com o Matt sobre aprender japonês, já sabemos que não é assim. Não se trata de decorar dúzias de regras. Nem no aprendizado em geral, nem em inteligência artificial. Não existem &ldquo;regras&rdquo; como um monte de &ldquo;ifs&rdquo;. Pense por dois segundos: quando foi a última vez que você escreveu um texto 100% gramaticalmente e formalmente correto? Concorda que um texto assim seria super estranho? Sem gírias, sem maneirismos, sem coloquialismos, com palavras consideradas rebuscadas. Exatamente o que associamos com um robô falando.</p>
<p>Já falei isso nesses outros videos, mas vou repetir: como você aprendeu português? Foi lendo um livro do tamanho da Bíblia, lotado de regras gramaticais? Quando tinha 1 ou 2 anos de idade? Como um bebê que nasceu nos Estados Unidos aprendeu inglês? Como um bebê que nasceu na China aprendeu chinês? Nenhum deles usou nenhum livro, nenhum curso, nenhuma regra.  Simplesmente passaram um tempão ouvindo os pais e pessoas ao redor e começaram a repetir o que ouviam. Bem errado no começo.</p>
<p>Todo mundo vai dando feedback. Quando o bebê tinha uma intenção, sei lá, dizer que está com fome. Ele tentava juntar palavras que já tinha ouvido antes e que parecia descrever o que queria. Se os adultos ao redor dessem comida, é o feedback que o que falou parece que faz sentido. E assim ele vai associando as combinações de palavras com comportamentos. Vai refinando seu aprendizado. Fazendo novas sinapses, novas conexões. Pouco a pouco melhorando a comunicação e se fazendo entender melhor. </p>
<p>A grosso modo é como seria o que chamamos de treinamento supervisionado, no mundo de inteligência artificial. Nós não programamos regras gramaticais nem cadastramos palavras num banco de dados manualmente e ficamos fazendo &ldquo;ifs&rdquo; pra montar frases. Em vez disso começamos com um corpo de dados gigante. Por exemplo, todos os artigos da Wikipedia, todos os códigos abertos disponíveis no GitHub, todos os papers acadêmicos disponíveis publicamente, todos os livros digitalizados num Google Books. Bastante texto. Dezenas ou centenas de gigabytes de texto puro.</p>
<p>Pra entender isso, deixa eu fazer uma tangente e explicar um conceito relacionado que não é, em si só, inteligência artificial, mas faz parte da matéria. Na faculdade de ciência da computação, se aprende sobre processos estocásticos, que estuda aleatoriedade, probabilidades, ou melhor, a evolução de um sistema ou fenômeno ao longo do tempo de forma probabilística. Ele descreve o comportamento de um sistema ou quantidades que mudam aleatoriamente ao longo do tempo. Em particular quero falar de Cadeias de Markov.</p>
<p>Não estou falando que o ChatGPT não é uma Cadeia de Markov, é só pra ilustrar um ponto. Cadeias de Markov é uma das formas de representar e analisar sequências de eventos, ou estados, onde a probabilidade de transicionar de um estado pra outro depende somente do estado atual, é um sistema sem memória ou backtracking, sem considerar a sequência de todos os estados anteriores, só o último. Em resumo é um conjunto de estados e probabilidades de transição. Se pareceu grego, vamos ver um exemplo prático.</p>
<p>Digamos que em vez de ter gigabytes de textos como descrevi antes, nosso corpo de treinamento sejam só 3 frases em português: &ldquo;Eu gosto de comer maçãs.&rdquo;, daí &ldquo;Ela gosta de jogar tênis.&rdquo; e finalmente &ldquo;Ele prefere ler livros.&rdquo;. Podemos construir um modelo baseado nessas frases, onde os estados são palavras, ou tokens, e as transições entre estados representam a probabilidade de mover de um token pra outro. Vamos construir esse modelo simplificado.</p>
<p>Os tais estados podem ser só as palavras, tokenizadas ou quebradas a partir dessas frases, daí teríamos essa lista de palavras, &ldquo;Eu&rdquo;, &ldquo;gosto&rdquo;, &ldquo;de&rdquo; etc. </p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>1. Eu
2. gosto
3. de
4. comer
5. maçãs
6. Ela
7. gosta
8. jogar
9. tênis
10. Ele
11. prefere
12. ler
13. livros</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>As transições são as probabilidades. Esses são exemplos, mas digamos que a transição do token, ou estado &ldquo;Eu&rdquo; pro estado &ldquo;gosto&rdquo; é probabilidade 1, ou seja, 100%. Mas a transição do estado &ldquo;de&rdquo; pra &ldquo;comer&rdquo; é 0.5 ou 50% porque poderia ser pra &ldquo;jogar&rdquo; que é 0.5 também. Pra ficar claro, na primeira frase temos &ldquo;de comer&rdquo;, mas na segunda frase tempos &ldquo;de jogar&rdquo;, por isso a partir do token &ldquo;de&rdquo; temos duas possibilidades, 50% de chance pra cada.</p>
<p>Como temos poucas frases de treino, as transições são quase 100% de uma palavra pra outra, porque esse modelo só conhece 3 frases. Num treinamento de verdade com gigabytes de textos, teríamos trilhões de possibilidades diferentes e probabilidades pequenas e fracionadas, como 0.001234 bla bla. </p>
<p>Finalmente, digamos que começamos a digitar um texto e queremos que esse modelo continue completando a frase pra gente. Podemos usar o modelo da cadeia de Markov pra prever o que seria a palavra mais provável baseado nas probabilidades de transição que vimos na lista anterior. Por exemplo, começo digitando &ldquo;Eu&rdquo; e a probabilidade da próxima palavra ser &ldquo;gosto&rdquo; é 100%, então é isso que ele dá de previsão. Se digitar &ldquo;Ele&rdquo;, a probabilidade segundo a lista é 100% pra &ldquo;prefere&rdquo;.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>- De &#34;Eu&#34;: gosto (1.0)
- De &#34;gosto&#34;: de (1.0)
- De &#34;de&#34;: comer (0.5), jogar (0.5)
- De &#34;comer&#34;: maçãs (1.0)
- De &#34;maçãs&#34;: Eu (1.0)
- De &#34;Ela&#34;: gosta (1.0)
- De &#34;gosta&#34;: de (1.0)
- De &#34;de&#34;: jogar (0.5), ler (0.5)
- De &#34;jogar&#34;: tênis (1.0)
- De &#34;tênis&#34;: Ela (1.0)
- De &#34;Ele&#34;: prefere (1.0)
- De &#34;prefere&#34;: ler (1.0)
- De &#34;ler&#34;: livros (1.0)
- De &#34;livros&#34;: Ele (1.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>Por causa de smartphones, todo mundo já viu isso em ação de verdade. É a funcionalidade de auto-correção que tem em todo teclado. Olhe neste exemplo, começo digitando uma palavra e o teclado sozinho vai sugerindo a próxima palavra, e podemos só aceitar a sugestão. E ele vai sozinho completando a frase. Claro, se ficar fazendo só assim, a frase vai ficando meio sem sentido nenhum, mas ele consegue gerar uma frase que mais ou menos parece um humano que escreveu, não acha?</p>
<p>Vou repetir: isso é uma explicação simplificada, tem várias outras técnicas em cima de cadeias de Markov, mesmo pra um tecladinho simples de iOS ou Android. Mas em linhas gerais, pense que em vez de 3 frases, o modelo desses teclados foi pré-treinado com milhares de frases. O modelo é essa lista de combinações de palavras e as probabilidade da próxima palavra, dada uma palavra anterior. Essas probabilidades é o que chamamos de &ldquo;pesos&rdquo;. E mais importante: em nenhum momento usamos quaisquer regras hard-coded de gramática ou ortografia ou ifs ou templates. Ele vai completando a frase puramente usando esses pesos, aprendidos no treinamento, nada mais.</p>
<p>Agora vamos voltar pro ChatGPT ou pra minha versão local do text-generation com Vicuna. Vocês nunca acharam estranho que as respostas sempre demoram e ele vai escrevendo uma palavra de cada vez? Alguns poderiam achar que é só uma animação arbitrária pra fazer parecer que o ChatGPT é uma pessoa digitando. Mas deixa eu rodar uma versão fora da interface Web, na linha de comando mesmo. Prestem atenção.</p>
<p>(&hellip; screencast command line fastchat &hellip;)</p>
<p>Pra ficar mais claro, vou colocar do lado o monitoramento da minha GPU, a ferramenta da nvidia chamado &ldquo;nvidia-smi&rdquo; que faz o monitoramento dos recursos sendo usados na GPU. Notem que durante a composição da resposta, a GPU está em uso constante, processando alguma coisa, sem parar. Não sei porque usa só 50% do processamento disponível, mas de qualquer forma, a resposta não é instantânea. Não é uma animação feita só pra fazer graça, é que ele demora isso mesmo palavra a palavra. Cada palavra nova que vai aparecendo está gastando processamento da GPU. Se levou 5 segundos pra dar a resposta, foi 5 segundos que a minha GPU ficou processando sem parar.</p>
<p>Conseguem ver as similaridades entre o auto-corretor do seu tecladinho de celular e o processo de resposta do ChatGPT? Internamente ele está fazendo algo similar a procurar probabilidades na cadeia de Markov. Mas claro, o modelo de GPT, LLaMA, Vicuna, Bard, e outros, é mais complicado que um mero modelo de Markov. Vamos entender.</p>
<p>Quando as primeiras notícias anunciando o ChatGPT saíram, gerou muita confusão, que persiste até hoje. Por exemplo, quando o ChatGPT 4 foi anunciado, eles mencionam, &ldquo;uau, o ChatGPT 3.5 tinha 175 bilhões de parâmetros mas o ChatGPT 4 tem incríveis 100 a 170 TRILHÕES de parâmetros&rdquo;. A única coisa que os jornalistas e vocês entenderam foi &ldquo;uau, bilhões pra trilhões de &hellip; whatever &hellip; trilhões é absurdamente maior que bilhões, então o GPT novo é milhões de vezes melhor e o GPT 5 vai ser mais milhões de vezes melhor&rdquo;. E é assim que todo mundo mente e se auto-engana com números, sem saber o que significa.</p>
<p>Esse meu Vicuna, rodando localmente na minha máquina, tem meros 30 bilhões de parâmetros. Puuuts, quer dizer que ele deve ser pelo menos 5 vezes pior que o ChatGPT antigo, né? Nem chega aos pés do GPT 4. Só que se olhar alguns artigos que descrevem o Vicuna menor, o de 13 bilhões de parâmetros, muitos declaram que rodando diversos testes, os mesmos que a própria OpenAI usa pra avaliar o GPT deles, dizem que o Vicuna 13B chega a 90% do nível de qualidade do GPT 4 ou Google Bard. E o Vicuna, sendo derivado do LLaMA do Facebook, nesses mesmos testes, ultrapassa o LLaMA original. Como pode isso?</p>
<p>Antes de mais nada, o que diabos são esses tais parâmetros? No contexto de machine learning, parâmetros se referem aos pesos que o modelo aprende durante o processo de treinamento. De forma simplificada e grosseira, lembra a lista de probabilidades de transição de estados da cadeia de Markov que mostrei no exemplo? Aquilo poderíamos chamar de parâmetros. No nosso treinamento com só 3 frases, gerou um modelo de 16 parâmetros. Pense numa lista como aquela só que bilhões de linhas. 13 bilhões ou 30 bilhões no caso do Vicuna, ou 170 trilhões no caso do ChatGPT 4.</p>
<p>E lembra como no exemplo, dado uma palavra, podemos ir na lista de probabilidades e ver qual poderia ser a próxima palavra? A grosso modo, é como GPT ou Vicuna fazem. Só que em vez de considerar só a palavra anterior e procurar a próxima, ele vê quais foram as palavras anteriores e leva todas em consideração pra tentar prever a próxima palavra. Lembra como minha GPU fica processando sem parar enquanto monta a resposta? É isso que  está fazendo: pesquisando no modelo pela próxima palavra, mas  considerando parte ou todo o texto anterior, incluindo as palavras que ele mesmo sugeriu, vai ficando cada vez mais pesado. Por isso demora.</p>
<p>Antes que o pessoal acadêmico me crucifique, é melhor eu me corrigir aqui. Eu repeti várias vezes &ldquo;a grosso modo&rdquo;, &ldquo;a grosso modo&rdquo;, porque o modelo do GPT não são probabilidades de pares de palavras, daquele jeito bonitinho como no exemplo. Intuitivamente, poderíamos pensar que quando se quebra um texto longo, teríamos conjuntos de &ldquo;palavras&rdquo;. Mas vamos recordar a metáfora do bebê aprendendo?</p>
<p>Todo mundo já deve ter percebido que um bebê não ouve um adulto falar uma palavra e sai repetindo bonitinho igualzinho, certo? Parte da diversão é justamente ver ele falando errado no começo e ir ajustando. O bebê tenta reproduzir o que ele &ldquo;acha&rdquo; que ouviu. Daí você dá o feedback negativo e ele vai tentando de outras formas, até uma hora acertar. No mundo de machine learning e deep learning, podemos usar isso como metáfora. No treinamento ele não isola palavras,  isola &ldquo;patterns&rdquo; de funcionalidades, ou features, do material de treinamento. </p>
<p>Fora de IA, no mundo de full-text search, ou em processamento de linguagem natural, temos ferramentas como Elasticsearch ou Apache Solr, que expliquei no episódio do Twitter. Eles não quebram os textos indexando palavras, mas sim grams. Quem já leu a documentação deve ter visto o termo n-gram, que são sequências de &ldquo;n&rdquo; itens ou tokens. Pode ser uma palavra inteira, mas pode ser só parte de uma palavra. Uma sequência de só uma letra seria um unigram, duas letras um bigram, três letras, trigram e assim por diante.</p>
<p>Quando indexamos texto, é mais útil indexar n-grams do que palavras inteiras, é o que nos permite fazer coisas como achar palavras &ldquo;parecidas&rdquo; ou que &ldquo;soam&rdquo; parecido, que tem mesmo sufixo ou mesmo prefixo, como conjugação de verbos. Mesma coisa com o teclado de auto-correção, ele indexa n-grams. Por isso você digita a pesquisa tudo errado num Google, mas ele diz no resultado &ldquo;você quis dizer X&rdquo;. O mundo de indexação de textos é todo baseado no conceito de n-grams. Vale a pena estudar isso depois.</p>
<p>Quer dizer que o tal modelo do GPT são pesos em cima de n-grams? Infelizmente também não é assim fácil. Eu mencionei n-grams só pra explicar como podemos dividir palavras de outras formas não intuitivas. Agora, o problema com sinapses no nosso cérebro ou redes neurais é que os pesos não são aplicados em cima de idéias discretas, como palavras ou letras ou imagens inteiras. De novo, pra entender tecnicamente como redes neurais funcionam, procure material de universidades como do MIT, Stanford ou lugares assim, e de novo, acadêmicos, peguem leve comigo.</p>
<p>No fim do dia, um modelo é como se fosse um banco de dados, contendo probabilidades ou pesos de um elemento pra outro elemento. Parecido com o exemplo da auto-correção. Mas o principal é que não são necessariamente palavras, não são também só n-grams, pode ser qualquer tipo de pattern que foi identificado no aprendizado. Pode ser uma letra pra uma palavra. Pode ser um bigram pra um trigram. Pode ser muitas coisas. Por exemplo, poderia ser &ldquo;ef&rdquo; pra &ldquo;yw&rdquo; probabilidade 0.01234. O que isso significa?</p>
<p>Isoladamente não significa absolutamente nada. Ela só vai fazer sentido dentro de uma rede de pesos. A probabilidade final é uma composição de múltiplos passos pelos nós dessa rede. O modelo não é uma lista, provavelmente é mais como um espaço vetorial multi-dimensional, tipo matrizes de matrizes. Eu expliquei espaços vetoriais no episódio do Twitter também. Similaridade de Cosseno, Álgebra Linear, lembram? De novo, pra visualizar, não pense num modelo como sendo uma lista, como um array simples. Pense como um array de arrays, de arrays. Multi-dimensional.</p>
<p>Se multi-dimensional não é intuitivo pra vocês, pense num elemento simples, tipo uma variável de tipo inteiro. Isso é o que chamamos de valor escalar, com zero dimensões. Representa um único valor. Agora, um array de escalares, como um array de inteiros. Isso é vetor, uma lista de uma única dimensão. Em seguida, em vez de escalares, se eu fizer um array de arrays, onde cada elemento do array é outro array, e esse array for unidimensional, agora temos uma matriz, que é um retângulo, ou uma grade de valores escalares.</p>
<p>Finalmente, e se esse array interno também tiver arrays como elementos? Agora temos um array de arrays de arrays,  tridimensional. E podemos ir adiante, esse último array também pode ter arrays de elementos, que tem arrays de elementos, que tem arrays de elementos, aí temos matrizes multi-dimensionais ou mais corretamente, tensores de alto ordenamento ou tensores n-dimensionais.</p>
<p>Aliás, tudo isso que eu expliquei são tensors, sabe, do tal Tensorflow do Google? Um escalar é um tensor de zero dimensões, um vetor é um tensor de uma dimensão, uma matriz é um tensor de duas dimensões e acima disso é um high-order tensor ou n-dimensional tensor. Então, voltando pro tal modelo do GPT ou Vicuna, não pense como no exemplo simples do auto-corretor, que foi somente um vetor, um array unidimensional de pesos. Pense que esses pesos estão estruturados em tensores n-dimensionais.</p>
<p>Todos esses conceitos que vim explicando não são a ponta do iceberg. São a raspa da ponta do iceberg. Pode parecer que quero dizer que um ChatGPT não é mais que uma cadeia de Markov, só que maior. Não é isso, é só uma metáfora pra explicação. Deixa eu tentar explicar qual foi a tal &ldquo;revolução&rdquo; que permitiu o salto de um mero auto-corretor pra um ChatGPT. Mas entenda: redes neurais e deep learning existem faz décadas. A comunidade de ciências da computação vem fazendo descobertas e refinando as tecnologias faz muito tempo. Não foi do nada que isso apareceu.</p>
<p>Seguindo o exemplo do auto-corretor, deve ser fácil de perceber um dos problemas: ele só usa a palavra anterior pra tentar descobrir qual a próxima palavra. Por isso muito rapidamente a frase fica sem sentido. Dá impressão que foi um humano que escreveu, mas um humano bem burro. É diferente de um punhado de palavras completamente aleatórias, mas as frases que gera, são bem inúteis. Quanto maior tentar fazer a frase, pior vai ficar.</p>
<p>Claro, o certo é que a próxima palavra leve em consideração não só a palavra anterior, mas todas as palavras anteriores, pra manter a coerência. É isso que poderíamos chamar de backtracking ou recurrency. Isso tem que ser levado em conta durante o treinamento. Não basta quebrar o texto em palavras e só fazer o peso da palavra seguinte. Tem que ser o peso da palavra seguinte dado as palavras anteriores. É aí que nasce coisas como RNN ou Recurrent Neural Networks.</p>
<p>RNNs foram desenhados pra lidar com sequências de tamanhos variados, como sentenças, dados de séries de tempo, sinais de discurso. Eles conseguem processar inputs, como texto, um passo de cada vez e ao mesmo tempo mantendo um estado interno escondido que mantém informações dos passos anteriores. Ou seja, ele mantém memória durante o aprendizado. É como a gente aprende também.</p>
<p>Uma coisa é aprender, por exemplo, a palavra &ldquo;foda&rdquo;. Dependendo do contexto pode significar coisas diferentes. Pode ser que signifique &ldquo;puts, que foda esse macbook novo&rdquo; ou seja, positivo. Ou pode ser &ldquo;puts, que trampo foda de difícil&rdquo;, ou seja, negativo, e várias outras variações. Precisamos de contexto, e contexto precisa de memória pra gerar pesos diferentes pra contextos diferentes. Consegue imaginar o trampo de processar pensando dessa forma?</p>
<p>RNNs usam técnicas como BPTT ou Backpropagation Through Time, literalmente propagação reversa através do tempo, pra computar gradientes e atualizar os parâmetros do modelo. Então não é um processamento que você pega um texto, lê só uma vez do começo ao fim e já gera um modelo, escrito linearmente do começo ao fim. Tem que ficar voltando pra trás no modelo pra ajustar. E já que é pra dificultar, em todo paper de IA por aí vamos esbarrar nesse termo &ldquo;gradiente&rdquo;. Deixa eu resumir.</p>
<p>Gradiente se refere à derivada da loss function, função de perda, ou de custo. Puts Akita, eu nunca vou usar Cálculo na vida, é perda de tempo. Bom, eis um pequeno exemplo. Pra que serve derivada? Ela serve pra medir a taxa de mudança de uma função. Em resumo, a derivada nos diz como o resultado de uma função muda à medida que fazemos pequenas modificações nos valores de entrada. Lembra de física cinemática no colegial? Fórmula pra saber o espaço ao longo do tempo? Fórmula de Velocidade ao longo do tempo? A de velocidade é derivada da fórmula de espaço, porque velocidade é a taxa de mudança da fórmula de espaço ao longo do tempo. Aceleração é a taxa de mudança da fórmula de velocidade. </p>
<p>Em particular, gradientes em cálculo de múltiplas variáveis é um vetor que aponta na direção da ascendente mais íngreme da função. Isso é super importante em otimização de algoritmos, como gradient descent, ou descida de gradiente, que iterativamente atualiza os parâmetros pra encontrar a função de custo mínimo. Dado que gradientes nos ajudam a entender a taxa de mudança de resultados de uma função, podemos usar pra encontrar os pontos de máxima e mínima, pra otimização. E esse conceito é importante em otimização de machine learning.</p>
<p>Em Machine Learning tem um troço que chamam de &ldquo;loss function&rdquo; também conhecido como função de custo, que é uma função matemática que quantifica a discrepância entre resultados previstos de um modelo com os valores de verdade. Lembrando, uma das formas de treinar é dar um monte de dados de treinamento, daí pedir pro modelo devolver respostas a várias perguntas e ver se as respostas estão corretas. Justamente pra calibrar o aprendizado. Lembra do bebê aprendendo a falar e olhando pra nossa cara pra ver se a gente entendeu? Tipo isso.</p>
<p>O objetivo do tal treinamento é ver quão bem um modelo performa determinadas tarefas. A escolha de qual função de custo usar depende do problema que queremos resolver, como regressão, classificação, geração de sequências. Por exemplo, em tarefas de regressão, uma função de custo popular é o MSE ou mean squared error, erro quadrado mediano, ou o MAE que é mean absolute error, erro absoluto mediano. Em tarefas de classificação tem custo de entropia cruzada e assim por diante. O importante é entender que treinamento não é um troço aleatório. Tem funções de métrica e controle pra calibrar e otimizar.</p>
<p>Durante o treinamento, os parâmetros do modelo são ajustados pra minimizar essa função de custo usando algoritmos de otimização como o &ldquo;gradient descent&rdquo;. Agora vocês entendem uma das formas que cálculo influencia a qualidade do treinamento de um modelo de rede neural. Pra hoje, pense em rede neural como uma caixa preta, que nem uma função que você programa na sua linguagem de programação. Ela tem variáveis de entrada e algum retorno. A entrada seria as tais toneladas de textos pro treinamento. Daí no meio, nessa caixa preta, esses dados são processados de alguma forma, e o retorno vai ser o tal modelo multi-dimensional.</p>
<p>Essa etapa no meio é o processamento dos textos e materiais de treinamento que passamos. Processos como tokenização que é quebrar o texto em listas de sequências de palavras. São uma série de transformações pra massagear esses dados em diversas camadas escondidas. Essas transformações envolvem cálculos, por exemplo, soma ponderada. Cada neurônio de uma camada escondida recebe inputs de uma camada anterior. A cada input é assinalado um peso. O neurônio computa a soma ponderada dessas entradas, onde os pesos determinam a significância da contribuição de cada input pra saída do neurônio.</p>
<p>Daí a soma ponderada pode ser passada pra uma função de ativação, que introduz não-linearidade e determina a saída da função. Não linearidade e sistemas complexos é um assunto gigantesco, nem vou tentar explicar. Mas pra ter intuição pense assim: você tá acostumado a pensar em sistemas lineares. Por exemplo, se 1 litro de gasolina dá pra andar 15 quilômetros, então 10 litros de gasolina vai dar 150 quilômetros. Mas não-linearidade é que nem tentar prever o tempo. </p>
<p>Só porque temos 80% de humidade no ar e no passado vimos que isso indicava uma chuva de, sei lá, 20 milímetros. Não quer dizer que se eu medir hoje 80% vai dar os mesmos 20 milímetros. Pode ser 40. Pode ser zero. Tem uma rede de outras variáveis, algumas mensuráveis, algumas desconhecidas, uma influenciando a outra. Variáveis minúsculas podem amplificar resultados completamente inesperados.</p>
<p>É o famoso caso da Borboleta de Lorenz. Aquela história que uma borboleta bate as asas no Brasil e tem um tsunami no Japão. Não foi a borboleta que causou o tsunami, não é linear, não tem causalidade direta. Mas quer dizer que essa minúscula contribuição, somado a milhares de outras, &ldquo;pode&rdquo; ter causado o tsunami. </p>
<p>Depois parem pra ler sobre Teoria do Caos, é fascinante, e o mundo real é cheio de efeitos não-lineares. E levamos isso em conta em redes neurais. Nosso cérebro tem aprendizado não-linear, e é o que tentamos simular com redes neurais. Agora, Deep Learning, como o próprio nome diz é aprendizado profundo, e profundidade se refere a várias camadas de aprendizado. Aquela função caixa preta que eu mencionei? Imagine várias delas em série, uma chamando a outra, várias camadas de profundidade. Lógico, explicação simplificada, mas só pra dar uma noção.</p>
<p>Enfim, a parte importante é que processar texto em deep learning usando técnicas como RNN e BPTT seria absurdamente caro, pra manter toda a memória e fazer todo esse backtracking na força bruta. É aí que entra o famoso paper do Google, &ldquo;Attention is All You Need&rdquo;. Literalmente &ldquo;Atenção é Tudo que Você Precisa&rdquo;, publicado por Vaswani e equipe em 2017. É o paper que introduz a arquitetura de Transformers, os famosos transformadores que permitiram esta geração de LLMs como GPT.</p>
<p>Em pouquíssimas palavras, ele introduz o mecanismo de self-attention, ou auto-atenção, também conhecido como atenção escalada de produto escalar. Eu expliquei produto escalar no contexto de espaços vetoriais no episódio do Twitter. Não vou explicar de novo. Mas esse mecanismo permite o modelo pesar a importância de diferentes posições na sequência de entrada, possibilitando capturar efetivamente as dependências longas. Eu sei, é difícil de entender isso, e também de explicar. Mas lembra como RNN precisa manter um estado em memória pra lembrar o contexto? Self-attention é uma otimização disso.</p>
<p>Em vez de ser recurrent neural network, ele passa a poder usar feed forward neural network. Da forma como eu entendo, em vez de um processo onde você dá um passo pra trás antes de poder dar um passo pra frente, agora é só passos pra frente, feed forward. O que possibilita isso é o tal mecanismo de auto-atenção. Elimina a necessidade de recorrência ou convolução. E ainda permite paralelizar o processamento. Antes, como o passo seguinte dependia do passo anterior, tinha que ser feito em série, em sequência linear. Parte da dificuldade de conseguir rodar coisas em paralelo é eliminar as dependências que amarram o passo seguinte com o passo anterior. Isso vale não só pra IA mas qualquer coisa.</p>
<p>Tornando o processo feed forward, evitando convolução, podemos paralelizar o processamento. O que levaria, chutando, um ano pra treinar, poderia ser feito em um mês. O importante é entender que essa arquitetura de transformers é uma otimização massiva. É mais ou menos o tipo de impacto que você vê num desenvolvimento web comum quando coloca um índice numa tabela gigante, ou quando coloca um cache na frente do banco de dados e ganha 5x ou 10x a performance. Independente de como funciona no detalhe, o importante é entender que foi um salto grande. </p>
<p>Toda hora eu fico falando que os acadêmicos vão me matar vendo essas minhas explicações grosseiras, mas eu mesmo fico doído de ficar toda hora falando &ldquo;a grosso modo&rdquo;, &ldquo;simplificando&rdquo;, &ldquo;em metáfora&rdquo;, porque cada parágrafo que falei até agora são dúzias de papers e formalidades matemáticas. Estou tentando trazer um pouco desse vocabulário, pra vocês entenderem que não é um chute do nada, mas também reduzir em poucas palavras que ajudem a dar uma intuição. Pra maioria de nós, os detalhezinhos não importam tanto. Tem mais valor ter a noção desse processo, em linhas gerais, pra entender que não é mágica. Principalmente: qual o limite dessa mágica.</p>
<p>Mas com tudo isso que falei, vamos tentar entender o que é o ChatGPT então. Como é um projeto proprietário, fechado e secreto da OpenAI, temos que acreditar nas informações que eles disponibilizaram. Então sempre leiam isso com vários quilos de sal. Isso dito, parece que o treinamento foi baseado num corpo de aproximadamente 570 gigabytes de texto. Quais textos exatamente? Não sabemos, mas eles mencionam Wikipedia, artigos de pesquisa e papers, websites e outras formas de conteúdo escrito na web, com um limite até 2021. Isso é arbitrário.</p>
<p>Pessoalmente, achei pouco texto, eu teria chutado mais. Mas 570 gigabytes só de texto puro é bastante coisa na real. Por exemplo, a Wikipedia inteira dá um total de 21 gigabytes, e isso eu acho que é contando com o HTML que monta as páginas. Se filtrar e limpar só os textos puros vai ser bem menos. Mas digamos que seja 21 gigabytes. Precisaria de mais de 30 Wikipedias inteiras pra completar os 570 gigabytes de dados de treino. É um volume respeitável.</p>
<p>Esse tanto de texto, dizem que deu um total de 300 bilhões de &ldquo;palavras&rdquo;, entre aspas, mas acho que o jornalista entendeu errado. Um dicionário de inglês, como o Merriam Webster online não tem meio milhão de palavras. Acho que são 300 bilhões de tokens, que incluem palavras, mas também n-grams como falei antes, e seja lá quais outros patterns o deep learning identificou nesse material. Daí passa por semanas fazendo todo o processo que falei de transformação. Esse processo, que levaria meses, agora parece que dura mais ou menos um mês rodando em não sei quantos servidores usando hardwares como os agora famosos NVIDIA Grace-Hopper, os GH100.</p>
<p>Lembra que falei que, internamente, não estamos lidando com valores escalares e sim com tensores multi-dimensionais? CPUs como um Intel ou AMD que roda no seu PC, mesmo os M1 ou M2 da Apple, são chips com instruções feitas pra cálculos em cima de valores escalares. Uma função de soma pega dois valores inteiros de 64 bits e cospe um resultado inteiro de 64 bits. Eu explico como isso funciona nos episódios de emuladores como o do Super Mario, com processadores de 8-bits, depois dêem uma olhada.</p>
<p>CPUs modernas incluem instruções pra lidar com vetores, instruções SIMD, Single Instruction, Multiple Data, literalmente uma instrução pra múltiplos dados. Começou com as instruções MMX dos primeiros Pentium nos anos 90. Hoje temos conjuntos de instruções como SSE4 ou AVX-512. Pra ter a intuição, em vez de uma função que recebe um inteiro, pense numa outra função que recebe dois arrays, soma os dois arrays, e cospe um array resultante, tudo numa única instrução.</p>
<p>GPUs, diferente de CPUs, não tem capacidade de rodar qualquer programa genérico. Lembram do meu episódio de Turing Complete? A grosso modo, uma máquina de Turing é basicamente qualquer programa. Em particular, pra ser Turing Complete, pra ser um computador moderno, ele precisa ser capaz de rodar um programa que consegue simular ser um computador. Como exemplo mais óbvio pense uma máquina virtual. Ele nem precisa conseguir rodar na prática, mas tem que ter a capacidade teórica. Num CPU ARM M2 da Apple, é possível simular uma CPU Intel usando o Rosetta. E esse programa de Intel roda achando que tá num PC de verdade. Isso é possível porque um CPU ARM M2 é Turing Complete.</p>
<p>Já uma GPU não tem essa capacidade. Diferente de CPUs, que são genéricos, e podem simular qualquer coisa, mesmo que lento, uma GPU é um hardware especializado, pra executar um conjunto pequeno de tarefas bem definidas. Uma GPU não consegue rodar um sistema operacional genérico, nem simular ser outra GPU, tipo um AMD Radeon tentar simular ser um NVIDIA RTX. Não funciona assim. Quando existe camada de abstração, quem cuida disso é a CPU. A GPU é boa numa única coisa: fazer cálculos de vetores e matrizes.</p>
<p>Uma CPU Intel costuma ter sei lá, 8, 16, 32 núcleos com 2 threads cada, rodando a 4 ou 5 gigahertz hoje em dia. Mesmo chips de servidores como um Intel Xeon ou AMD EPIC não tem muito mais que isso de cores. Já uma GPU é diferente. Uma novíssima RTX 4090 tem nada menos que 16 mil núcleos pra shading de CUDA, 128 núcleos pra ray tracing e nada menos que 512 núcleos exclusivos só pra tensors. Uma GPU, diferente de uma CPU, tem milhares de núcleos que rodam em clocks baixos como 1 gigahertz, pra funções altamente especializadas.</p>
<p>O que eu falei que é o resultado do treinamento de deep learning? Um modelo de tensors n-dimensionais. O que foi feito pra calcular tensors multi-dimensionais? GPUs. Processar áudio, processar video, processar polígonos ou voxels tridimensionais, é tudo processamento de matrizes multi-dimensionais. Uma tela de computador ou do seu smartphone, como é representado? Num monitor Full HD, é um array de 1080 colunas, onde cada elemento é um array de 1920 elementos pra cada linha. Se eu quiser escurecer essa imagem inteira? Pode ser uma substração em cada valor desses arrays. Fazemos isso adicionando, ou subtraindo uma matriz por outra matriz, que chamamos de um &ldquo;filtro&rdquo; ou kernel.</p>
<p>Numa CPU, você programaria como um loop nas colunas e outro loop nas linhas pra calcular a nova cor, pixel a pixel, seria 1920 vezes 1080 operações, ou mais de 2 milhões de operações. Numa GPU eu passo a matriz inteira e ele calcula tudo numa única operação. De novo, não vou conseguir entrar em detalhes, mas essa é a diferença fundamental.</p>
<p>De curiosidade, um dos maiores problemas dessa arquitetura de CPU controlando GPU é o compatilhamento de memória. Em PCs modernos a CPU tem um conjunto de RAM e a GPU tem um conjunto de VRAM separados. A CPU prepara os dados e tem que mandar pra GPU processar. Daí uma vez calculado, a CPU precisa puxar o resultado de volta pra própria RAM. No frigir dos ovos, essa comunicação é um gargalo.</p>
<p>Não é raro vermos jogos, por exemplo, que perde frames mesmo a GPU não estando em 100%, mas se olhar a CPU, ela que tá em 100%, então vira um gargalo e a GPU fica um tempo parado esperando. Por isso desde a nona geração de consoles de videogames como PS5 e Xbox Series X se falou tanto em loads instantâneos e tecnologias de melhorar esse gargalo, como o Microsoft DirectStorage.</p>
<p>Também é por isso que a estratégia da Apple com os chips M1 e M2 é ser um SoC ou System on a Chip; um único chip que embute CPU, GPU e RAM tudo junto, pra minimizar ao máximo esse gargalo. Juntar tudo no mesmo lugar garante uso mais eficiente de memória e caminho mais curto de comunicação, ajudando a evitar gargalos.</p>
<p>Na velocidade que estamos hoje, a distância da sua CPU Intel pros pentes de RAM é gigante se comparado a soldar tudo junto no mesmo chip como a Apple faz. É um saco porque não dá pra aumentar RAM depois, mas a razão não é porque eles são uma corporação querendo arrancar mais dinheiro de vocês, mas sim porque tirar esse gargalo faz muita diferença.</p>
<p>A mesma coisa acontece na solução pra data centers da Nvidia, a tal arquitetura Grace-Hopper que eu falei, começa com um superchip Grace-Grace, tanto Intel quanto ARM, que eles mesmos desenvolveram, num único pacote com NVLink que é um barramento de altíssima velocidade entre eles, e a alternativa Grace-Hopper que é outro superchip que junta uma CPU Grace com uma GPU <code>H100</code> Hopper.</p>
<p>São soluções que juntam todos eses chips junto com meio terabyte de memória RAM LPDDR5 de 32 canais. Estamos falando de 96 núcleos de 3 nanômetros. É um monstro. É esse o produto que tem feito as ações da NVIDIA disparar, porque eles encaixam perfeitamente pra acelerar processamento de transformers. Meu PC não é &ldquo;parrudo&rdquo;, esse da NVIDIA sim, é a verdadeira definição de &ldquo;parrudo&rdquo;, o atual estado da arte em 2023.</p>
<p>É com servidores desse tipo, não sei quantos, que se pega meio terabyte de dados, quebramos em 300 bilhões de tokens, e no final a OpenAI consegue gerar um modelo de GPT 4.0 com os tais 170 trilhões de parâmetros. Conseguem entender melhor agora essa frase? Daí a mídia e os jornalistas ficam assustados e noticiam como o GPT 4 se iguala ao cérebro humano, que tem 100 trilhões em sinapses. Lembram? Sinapses mais ou menos são os pesos ou parâmetros entre neurônios. Agora que entendemos mais ou menos o que são esses parâmetros, vamos discutir a premissa errada: parâmetros não são equivalentes a sinapses do cérebro humano.</p>
<p>Quando se joga números arbitrários assim no título de uma matéria, todo mundo fica empolgado. Vamos entender o erro. Pra começar, nosso cérebro, em média, tem uns 100 bilhões de neurônios. De novo, eu não sou um neurologista, então já assuma que minha explicação vai ser simplificada e de alto nível. Em pesquisa de IA, tentamos igualar neurônios biológicos com neurônios digitais numa rede neural, e falamos em bits, como num computador. Mas pra começar, neurônios não são exatamente binários assim. Um único neurônio é capaz de lidar com múltiplos sinais e conexões. Em termos de sinapse pode ter até umas 7 mil.</p>
<p>Isso acho que seria máximo, mas em média o cérebro é capaz de até uns 600 trilhões de sinapses. Não sei porque, dependendo de onde se pesquisa, falam em 100 trilhões, outros falam em 600 trilhões. De novo, precisa pesquisar um pouco mais a literatura de neurologia pra entender o que isso significa. Mas não é um número absoluto. Tem vários fatores. Doenças como Alzeimer, por exemplo, afeta justamente a capacidade de fazer e manter sinapses.</p>
<p>Em crianças, quando o cérebro ainda é muito mais elástico e não foi limitado pelo crescimento, o potencial é de 1 quadrilhão de sinapses. É um número absurdo, mas mais importante, os 170 trilhões de parâmetros do GPT 4 não se equipara à quantidade de sinapses que nosso cérebro é capaz ainda. Portanto é falso que o GPT 4 já se igualou ao cérebro humano em quantidade de sinapses.</p>
<p>A outra premissa que errada: um parâmetro de modelo de GPT não é equivalente a uma sinapse do cérebro humano, nem de longe. Usamos vocabulário neurológico pra simplificar a descrição em termos de inteligência artificial, como redes &ldquo;neurais&rdquo;. É uma metáfora. Em nenhum momento, nenhum cientista da computação vai te dizer que um neurônio de rede neural é idêntico ou sequer próximo de um neurônio biológico. É só uma abstração.</p>
<p>E no caso de IA, parâmetros de modelos de transformers, comparado com neurônios do cérebro. Vamos voltar ao meu Vicuna rodando localmente na minha máquina. Lembram da afirmação do povo que fez testes e otimizou esse modelo? Um modelo Vicuna de 13 bilhões de parâmetros, em muitos casos, chega até 90% da qualidade de respostas de um ChatGPT 4.0 de 170 trilhões de parâmetros. Como isso é possível?</p>
<p>E por isso mencionei não-linearidade e Teoria do Caos. Um único parâmetro isolado, se tentar ler e interpretar, não tem como inferir nada. Só funciona se estiver combinado com vários outros parâmetros, numa rede. O resultado final depende da interação de múltiplos parâmetros, e por isso se gasta processamento da GPU pra gerar uma resposta. Parâmetros são pesos, probabilidades. E tem várias formas de otimizar isso. Por exemplo, probabilidades pra ter o máximo de precisão podem ser valores escalares de tipo float de 32 bits. É isso que se gera num modelo depois do treinamento.</p>
<p>Porém, pesquisas mostram que podemos truncar esses valores pra float-16 e a qualidade das respostas não cai tão drasticamente. É uma forma de otimização. Simplificando, é parecido com o conceito de música em MP3, que dados das frequências que o ouvido humano não é capaz de detectar são cortados fora. Tecnicamente isso tira qualidade do áudio, mas na prática a maioria dos humanos não sente. Em termos de armazenamento, economizamos 10 vezes o espaço fazendo isso.</p>
<p>Expliquei no episódio de 25 tera pra 5 gigas, onde pegamos uma imagem bruta em bitmap e reduzimos pra um JPEG. A qualidade cai, mas o olho humano sem treinamento, não nota diferença tão grande assim. Otimizações e compressão são formas de simplificar os dados, diminuir a qualidade, de forma que nossos sentidos, sem treinamento, não sintam diferença significativa. Fazemos isso com modelos de IA também.</p>
<p>Reduzir os valores de Float 32 pra Float 16 é uma forma de quantização. Existem várias formas de quantização que são otimizações dos modelos, pra exigir menos processamento pra gerar respostas, sem danificar demais a qualidade. Isso ajuda a conseguir fazer um clone de ChatGPT como o Vicuna, rodar numa máquina caseira como a minha. Parece que minha RTX 3090 já é quase topo de linha, mas não. Por isso expliquei sobre a NVIDIA GH100 que é o tipo de hardware necessário pra rodar o ChatGPT de verdade.</p>
<p>Mas respondendo como um Vicuna de 13 bilhões de parâmetros consegue competir com um GPT 4 de 170 trilhões é porque além de quantização, os algoritmos de auto-atenção tem evoluído também. Em auto-atenção, que acontece no processo de treinamento, cada token numa sequência precisa ser considerado com todos os outros tokens pra capturar dependências e relacionamentos. Tipicamente, auto-atenção é computado dentro de uma janela de contexto, onde cada token é considerado com tokens vizinhos.</p>
<p>Agora tem uma variante chamada &ldquo;auto-atenção global&rdquo; onde cada token é considerado independente de posição ou distância, que permite o modelo capturar dependências num contexto mais global. Na prática é assim: custa mais caro pra treinar, mas os parâmetros resultantes no modelo tem mais qualidade. Então com menos parâmetros conseguimos chegar em respostas de qualidade similar. Entenderam? A qualidade de um parâmetro não é universal nem estático, está mudando à medida que aperfeiçoamos os algoritmos de treinamento e estruturas de dados.</p>
<p>Redes neurais, mesmo deep learning com auto-atenção global, ainda são representações rudimentares e grosseiras do nosso cérebro. Pra um GPT ou Vicuna da vida, conseguir escrever um texto com estilo de Shakespeare, precisamos treinar com todos ou quase todos os textos do Shakespeare. Pra conseguir gerar uma música parecida com Mozart, temos que dar o máximo de composições de Mozart quanto possível. O processo de treinamento vai encontrar patterns ou padrões e criar pesos pra eles, registrando no modelo. Mas isso ainda é bem ruim se comparado ao cérebro humano.</p>
<p>Pense você. Se estiver treinando em literatura ou música. Mesmo não lendo nem perto de todas as obras de Shakespeare, mesmo não estudando nem de perto todas as composições do Mozart, rapidamente consegue começar a copiar o estilo deles. Veja você como programador. Não precisou ler todos os códigos já feitos em React pra começar a escrever códigos. Bastou uns 2 tutoriais. Nosso cérebro consegue aprender muito melhor que um transformer, com muito menos dados, e produzir resultados similares ou melhores dentro de um mesmo determinado assunto. IA hoje em dia ainda depende muito de força bruta.</p>
<p>Desde que o LLaMA foi lançado em fevereiro de 2023, estamos só em junho e já temos dezenas de modelos diferentes, com vários níveis de quantidade de parâmetros, caindo desde 65 bilhões até só 7 bilhões, com vários tipos de otimização como GPTQ pra quantização, ou Float 16. E isso permite rodar algo parecido com esse meu Vicuna local, num Android ou até num Raspberry Pi.</p>
<p>É o que eu acho ideal: um transformer rodando localmente, offline, sem compartilhar nenhum dado pessoal meu, nenhuma conversa, com nenhuma corporação por aí. Quanto menos dados meus eu tiver que dar pra alguém, melhor. Especialmente se esse alguém não me paga por isso. Pelo contrário, pra usar OpenAI eu preciso pagar assinatura. E sabe-se lá o que fazem com minhas conversas.</p>
<p>Bacana, significa que se o jornaleiro fala que o GPT 4 é próximo já de um ser humano, e os nerdolas das ciências da computação já compactaram e tornaram eficientes até esse ponto do VIcuna. Então já era. Tá fácil fazer a Skynet né? Afinal, GPT 4 já consegue escrever código de programação. Basta eu carregar o código do GPT 4 e mandar ele melhorar o código e gerar o GPT 5. Daí eu pego o novo GPT 5 e faço ele fazer uma versão melhor, o GPT 6, e assim sucessivamente, até eu ter o Gigachad GPT T-1000 Skynet que vai dominar o mundo, certo?</p>
<p>Errado. No final do dia, um GPT 5, 10, 20, continua não sendo mais que um auto-corretor do seu teclado em versão maior. Tirem da cabeça a noção de que é uma &ldquo;inteligência&rdquo;. Ela não é inteligente. Tentar definir inteligência é um buraco de coelho profundo demais, vai ter gente masturbando filosofia aqui até o fim dos tempos e ninguém vai chegar numa conclusão. Vamos só assumir que até hoje não temos uma definição exata de &ldquo;inteligência humana&rdquo;. Na verdade, quando chamamos de &ldquo;inteligência artificial&rdquo; só quer dizer que os resultados, pra um ser humano, podem &ldquo;parecer&rdquo; com algo inteligente, mas não que de fato &ldquo;é&rdquo; inteligente. Entendem a diferença?</p>
<p>Não ter uma definição exata é ruim, porque não temos um plano exato de pra onde ir. Na neurologia de verdade, apesar dos avanços, não temos uma receita exata de como neurônios e sinapses funcionam 100%. Temos uma boa idéia. Muita coisa ainda é especulativo. Mas podemos dar alguns chutes educados baseado em tudo que falei até agora. E aqui vai ser minha opinião pessoal. Se alguém tiver pesquisas que discordam, sintam-se à vontade pra linkar nos comentários abaixo. Opinião por opinião, cada um pode ter a sua. A maioria parece estar pulando rápido demais pra religião do AGI e eu vou ser advogado do diabo e dizer porque isso não está nem perto de acontecer.</p>
<p>Vocês entenderam até aqui? Quando vamos na interface Web do ChatGPT ou Vicuna ou qualquer outro derivado e escrevemos o tal do prompt, a pergunta, damos uns segundos, e ele nos trás uma resposta. Mas na realidade não é isso. O que vem na verdade é uma continuação do texto do prompt. Tem uma diferença importante aqui: ele não está tentando te responder. Acontece que com os parâmetros treinados do modelo, a probabilidade maior das próximas palavras é se parecer com uma resposta. Essencialmente o que está acontecendo é similar ao auto-corretor do teclado do seu celular: dado o texto que acabou de digitar, quais palavras tem mais probabilidade de serem continuação?</p>
<p>Entendam essa sutileza. Por isso falo que não é uma &ldquo;inteligência&rdquo;. GPT ou Vicuna não são pessoas e nem entidades com cognição tentando se comunicar. É meramente um completador de textos, ou mais tecnicamente, um transformer pré-treinado generativo, um gerador de texto. Uma pergunta, ou prompt, é um texto que se digita na expectativa que o modelo consiga continuar completando. É por isso que o texto da resposta vai aparecendo aos poucos, não é uma animação arbitrária, é igual você ficar clicando na próxima palavra sugerida pelo teclado.</p>
<p>Um dos parâmetros que afeta essa continuação se chama temperatura. Nós não temos controle da temperatura do ChatGPT pela interface Web, mas no Bing da Microsoft tem esses controles de mais balanceado, mais preciso ou mais criativo. E no meu Vicuna tenho esse campo numérico. Novamente, do jeito que todo mundo escreve, parece que estamos configurando uma pessoa pra ser &ldquo;mais criativa&rdquo;. Mas isso é só parte do showzinho. Um transformer não é mais ou menos criativo, ele é mais ou menos &ldquo;aleatório&rdquo;. </p>
<p>Temperatura controla a aleatoriedade do complemento de texto sendo gerado, o que você chama de &ldquo;resposta&rdquo;. O modelo assinala probabilidades pra cada possível token, que são candidatos pra ser a próxima palavra na sequência. Alta temperatura, valores maiores do que &ldquo;1.0&rdquo;, significa que o modelo assinala probabilidades similares a um conjunto maior de tokens. Se vários tokens tem probabilidades parecidas, as respostas podem variar mais quando se repete a mesma pergunta. Quanto maior a temperatura mais você vai &ldquo;achar&rdquo; que ele tá sendo mais criativo, mas também aumenta a probabilidade dele começar a dar respostas sem sentido.</p>
<p>Temperatura média, entre 0.5 a 1.0, é a resposta &ldquo;balanceada&rdquo; do Bing. Mesma coisa que alta temperatura mas ele assinala probabilidades similares pra um conjunto menor de tokens, controlando um pouco mais o que se perceberia como &ldquo;criatividade&rdquo;. Baixa temperatura, que é abaixo de 0.1, faz o modelo assinalar probabilidades similares pra palavras que realmente tinham mais chances de ser o próximo token. Isso faz a resposta parecer mais focada, determinística, previsível.</p>
<p>Respostas sem sentido, muitos chamam de alucinação. Eu não gosto desse termo, porque implica que se alucinação é temporária nas respostas, então no resto do tempo ele está sendo sóbrio ou racional. E não está. Todas as partes das respostas foram geradas mediante probabilidades do modelo. Eu só ajusto a quantidade de de aleatoriedade nos candidatos pra próxima palavra. Nada mais, nada menos. Ele nunca alucina, assim como nunca é sóbrio. Ele não é uma entidade consciente pra ser nenhuma das duas, é só um programa obedecendo probabilidades armazenadas num modelo.</p>
<p>Entenda, no frigir dos ovos, &ldquo;criatividade&rdquo; no mundo de transformers, é só uma métrica de aleatoriedade. Não tem nada a ver com criatividade humana. Assim como inteligência artificial não tem nada a ver com cognição de verdade. Transformers, e todo tipo de machine learning ou deep learning, são meros sacos de probabilidades. Eles não tem de fato cognição pra pensar &ldquo;huumm, os dados dizem que a probabilidade desse evento é X, mas realmente faz sentido? tem alguma coisa que eu não estou percebendo? Deixa eu parar pra pensar&rdquo;, que seria o que um ser humano inteligente consegue fazer. Transformers não refletem, só cospem exatamente o que as probabilidade do treinamento do modelo dizem pra ele cuspir, e nada mais.</p>
<p>Quando um transformer consegue pegar uma equação e achar a resposta, ou pegar até a planilha de balanço de uma empresa e dizer se está indo bem ou mau. Ela não está sendo inteligente. Simplesmente existem probabilidades no modelo que levam à resposta porque materiais que ele usou de treinamento tinham já a resolução de equações parecidas.</p>
<p>Da mesma forma que eu disse que não existe um monte de &ldquo;ifs&rdquo; de regras gramaticais pra conseguir escrever textos, também não tem um monte de &ldquo;ifs&rdquo; de regras matemáticas pra fazer contas. Sempre volte à imagem daquela lista de pares de palavras e probabilidades do exemplo do teclado com auto-corretor. Aquilo é tudo que ele tem. Um conjunto gigante de probabilidades. Toda resposta que te dá, por mais inteligente que pareça, não teve nenhum tipo de raciocínio ou lógica ou inferência, nem nada, só probabilidades.</p>
<p>Um texto gerado por um transformer às vezes pode parecer &ldquo;simpático&rdquo;. Se você for uma pessoa carente, vai parecer que o transformer responde se importando com você. Mas nada disso é intencional, é você projetando nele o que gostaria que ele fosse. Todo mundo faz isso com animais de estimação. Isso se chama &ldquo;antropomorfismo&rdquo;. Você pode atribuir emoções humanas pra animais ou objetos inanimados. Tem gente que jura que uma escultura sorri pra ele quando passa na frente. Obviamente não. </p>
<p>Um exemplo simples disso são nuvens no céu. Vira e mexe você olha pro céu e vê claramente uma escultura nas nuvens. Dependendo da sua inclinação ideológica poderia pensar. &ldquo;Uau, Deus realmente é criativo, olha que obra maravilhosa nos céus que ele está me dando&rdquo;. Ou &ldquo;Uau, a mãe natureza é especial, mãe Gaia continua a demonstrar sua genialidade criativa até nos céus&rdquo;. Ou &ldquo;Uau, a droga que eu acabei de tomar é da hora &hellip;&rdquo; Só esse último pode estar certo.</p>
<p>O fato é que a nuvem em si não está em formato de nada. Se passar um avião por cima da mesma nuvem, não vai ver nada, ou vai ver outra imagem. Da posição que você está, na sua cabeça, suas sinapses pré-treinadas sugerem que você está enxergando  a sillhueta de algum objeto ou animal que já viu antes. É um efeito colateral da nossa cognição. Poderíamos chamar de bug. É seu cérebro primitivo. O cérebro rápido, automático e meio burro.</p>
<p>Ninguém moldou essa nuvem, ela aleatoriamente acabou numa determinada posição, que parece ter um determinado formato. Não houve intenção. Foi aleatório. Eu diria que a IA tá pouco se fodendo pra você, mas isso implicaria que ela tem consciência. Ela nem está se fodendo pra você. Ela não pensou em você. Mesma coisa com um transformer. Ela não tem consciência, ela simples é. Uma mera ferramenta. Uma chave de fenda que sabe cuspir palavras segundo uma matriz de probabilidades. Nada mais, nada menos. Por acaso você está olhando pra chave de fenda e achando que está sorrindo pra você. Não está. Isso diz mais sobre seu estado emocional do que sobre a ferramenta.</p>
<p>Significa que ela não tem insights, não tem momento &ldquo;eureka&rdquo; de descobrimento de coisas novas. Ela só é capaz de sugerir uma palavra, depois das outras palavras que vieram antes, seguindo uma tabela de probabilidades que vieram do treinamento feito com gigabytes de texto. De vez em quando, dá impressão que ela &ldquo;criou&rdquo; algo que não existia, mas não é criação, é aleatoriedade, pura sorte. É a nuvem no céu. Não é um processo repetível e ela não sabe refletir algo como &ldquo;uau, isso que eu inventei é da hora, nunca tinha visto antes&rdquo;. Ela não tem emoções pra conseguir dizer &ldquo;uau&rdquo;.</p>
<p>Se você der o código de uma IA feita com RNNs como falei antes pro GPT 4 analizar, ela não vai magicamente conseguir chegar no paper de transformers. Se esse paper não estava no material de treinamento, ela não vai concluir sozinha que esse era o próximo passo. Portanto ela é incapaz de conseguir chegar num GPT 5. Novas descobertas precisam ser feitas por humanos, documentadas, alimentadas no treinamento da IA e só aí ela vai saber cuspir o texto desse paper. Ela é incapaz de usar o que aprendeu pra gerar descobertas novas de forma intencional. </p>
<p>Existe uma historinha que é chamado do Teorema dos Macacos Infinitos. O teorema sugere que se tivermos um macaco apertando teclas numa máquina de escrever por uma quantidade infinita de tempo, quase com certeza vai conseguir digitar qualquer tipo de texto, incluindo todas as obras do Shakespeare. Na prática, estamos falando de uma quantidade tão absurda de tempo que seria impossível até de vocês terem noção, estamos falando provavelmente de mais tempo que a idade atual do Universo desde o Big Bang. Mas é um conceito que demonstra que sim, sem nenhuma inteligência, só com aleatoridade, dado um tempo absurdo, uma hora tudo que já produzimos vai aparecer. Sem nenhuma intenção, só via aleatoriedade.</p>
<p>É uma história pra dar noção de conceitos como infinito e aleatoriedade. Essa história é creditada ao matemático francês Felix Émile Borel, de 1913. Ou seja, desde o começo do século XX já se tinha essa noção que muitos de vocês, mais de um século depois, ainda estão com dificuldades de entender. GPT e derivados são macacos modernos dessa história. Só que em vez de totalmente aleatório, demos um modelo de probabilidades pra facilitar o trabalho dele, só isso. Mas chamar de macaco é meio ofensivo, pro coitado do macaco, que ainda é mais inteligente que qualquer IA. </p>
<p>Falando em século passado. Deixa eu aproveitar pra voltar lá atrás na história das inteligências artificiais. O estudo de algoritmos e técnicas que contribuem pra esse campo existe desde os anos 50 pelo menos. Mas nos anos 70 surgiu um programa que até hoje ainda deixa os desavisados meio surpresos. O nome do programa é Eliza. Existem várias versões, inclusive rodando online. Dêem uma olhada.</p>
<p>(&hellip; screencast eliza &hellip;)</p>
<p>Como podem ver, é um chatbot também, que nem o ChatGPT. E parece uma pessoa bem desconfiada, fica dando umas respostas meio grossa. Weebs de anime chamariam ela de &ldquo;tsudere&rdquo;. Mas se hoje povo fica empolgado com ChatGPT respondendo, imagina isso nos anos 70. Só pra dar contexto, isso é antes da revolução dos microcomputadores de 8-bits como Commodore ou Apple II. Isso é antes da Microsoft ou Apple existirem. Nem internet existia ainda. Quando eu rodei uma versão de Eliza pra DOS lá no começo dos anos 90, lembro que fiquei fascinado e pensando como eu faria a minha própria versão. E aí vocês podem se perguntar, &ldquo;caraca, mas como era possível ter isso nessa época??&rdquo;</p>
<p>Bom, quem faz ciências da computação ou programa um pouco, já deve ter entendido o funcionamento. O modelo é super simples. É baseado inteiramente em pattern matching. Basicamente encontrar palavras-chave, ver se tem uma resposta pré-programada, e montar essa resposta, na mão. É um sistema de templates. Quando eu digo &ldquo;na mão&rdquo;, aqui sim, ao contrário de modelos de transformers, é realmente um monte de &ldquo;ifs&rdquo;. Quem já montou agentes ou robozinhos, tanto de chat de suporte ou email marketing, já fez coisa similar pra respostas automáticas. É um bom exercício de faculdade pra iniciantes.</p>
<p>No GitHub vai achar várias versões. Veja esta, é uma versão feita em Java. E vamos ver o tal &ldquo;modelo&rdquo; entre aspas. É uma lista de chaves e valores, e os valores, como podem ver, são frases pré-prontas. Por exemplo, se durante o chat você se desculpa e escreve algo como &ldquo;I&rsquo;m sorry&rdquo;, ele quebra essa string, encontra a palavra &ldquo;sorry&rdquo; e nesta lista olha só, tem 3 respostas pré-programadas. Ela pode te responder &ldquo;Por favor, não se desculpe&rdquo; ou &ldquo;Desculpas não são necessárias&rdquo;. E vai escolher aleatoriamente, pra não parecer que tá se repetindo.</p>
<p>Só isso já é suficiente pra passar no Teste de Turing, que é um teste feito pra identificar quando uma inteligência artificial consegue enganar um ser humano numa breve conversa. Eliza é a avó espiritual do ChatGPT. Mas o ponto em apresentar a Eliza é pra vocês verem que pra enganar seres humanos, não precisa  muito. Por alguma razão, nós seres humanos, somos muito fáceis de enganar e somos propensos a acreditar em qualquer coisa. Acho que somos animais com fé excessiva. É um ponto forte, mas é um enorme ponto fraco também. Eu pessoalmente sou cético, mas aí me chamam de &ldquo;do contra&rdquo;, vai entender. Vou continuar não sendo o trouxa, só isso.</p>
<p>Um ChatGPT, se eu quisesse simplificar bastante, não é muito diferente da Eliza, em conceito. É um programa que usa um modelo, um dicionário de probabilidades, que mostra frases de acordo com o que se digitou antes, roboticamente, automaticamente, pegando palavras cujos valores de probabilidade fazem mais sentido dado as palavras anteriores. Não existe nenhuma emoção envolvida. Não existe simpatia, não existe compaixão, não existe amargura. Nada. Não tem uma linha de código, nem de dados no modelo, que representam qualquer emoção. É apenas uma calculadora, que em vez de devolver números, devolve conjuntos de palavras. Só isso.</p>
<p>Mas e as IAs que geram imagens inéditas? Vide o próprio Dall-e 2 da OpenAI, vide o Midjourney, vide o Stable Diffusion, vide os novos plugins de geração e ajustes de imagens proprietário da Adobe, que começaram a ser distribuídos no Creative Cloud. Classificação de imagens e geração de novas imagens são campos diferentes dentro de inteligência artificial. Só pra não perder o gancho, deixa eu jogar na mesa mais alguns conceitos.</p>
<p>Um campo que existe desde pelo menos os anos 80 é o estudo sobre CNNs. (piada) Não, não, não é esse, é um CNN útil. Convolutional Neural Networks, que é um modelo que dizem que é bom pra processar dados que se parecem com grades, como uma grade de pixels, que é como representamos uma imagem. As tais camadas convolucionais, não sei se é assim que fala em português, aplicam vários filtros ou kernels, aos dados de pixels pra criar um mapa de funcionalidades. </p>
<p>Em resumo, esses filtros extraem características da imagem. O equivalente a tokenizar um texto e gerar palavras ou n-grams. Mas CNNs ficaram super famosos só depois de 2012 quando saiu a AlexNet de Alex Krizhevsky, Ilya Sutskever e Geoffrey Hinton. (piada) Arregacei os nomes, mas beleza. Era um concurso que todo ano o vencedor ficava um pouco melhor do que o do ano anterior. Mas em 2012, o salto foi uma ordem de grandeza melhor. Não lembro os números, mas faz de conta que todo ano os melhores algoritmos conseguiam identificar 85% das imagens, no ano seguinte, 86%, aí do nada deu um salto pra 99%. Isso gerou um enorme interesse na comunidade de pesquisa em cima de classificação de imagens.</p>
<p>Em paralelo, em 2014 surgiu o conceito de GANs, Generative Adversarial Networks ou redes generativas adversárias, desenvolvido pelo Ian Goodfellow e seus colegas. Foi um avanço no conceito de geração de imagens. De forma simplificada é como se fossem duas IAs uma competindo com a outra. Um gerador cria imagens e um discriminador avalia. Por exemplo, digamos que quero gerar imagens de gatos, o gerador faz as imagens, e o discriminador tenta identificar se é um gato mesmo. É um sistema que acelera o processo de aprendizado por fornecer feedbacks mais rápidos do que um treinamento supervisionado por humanos.</p>
<p>CNNs e GANs eu mencionei mais pra vocês saberem alguns nomes importantes. Mas o Dall-e da OpenAI não usa GANs, ele usa um derivado do modelo de transformers do GPT só que aplicado a imagens. Assim como geração de respostas de texto, onde ele vai prevendo uma palavras após a outra, levando o contexto anterior em consideração, o Dall-e também usa uma arquitetura de transformers, criando imagens um pedaço atrás do outro. Ou seja, também usa mecanismos de auto-atenção em vez de camadas recorrentes ou convolucionais tradicionais.</p>
<p>Essa não é a única forma de se criar um gerador de imagens. Mais do que o Dall-e, que é fechado, eu gosto mais do Stable Diffusion que, assim como o LLaMA da Meta, também foi liberado publicamente como modelo aberto. Quem me acompanha no Instagram viu quando eu fiquei brincando de usar o Stable Diffusion com outras ferramentas abertas pra fazer remasterização, upscaling de video. Assim eu pegava um video super antigo com qualidade de DVD e conseguia fazer ele redesenhar uma versão 4K. Se não viu isso, veja o destaque &ldquo;Usando IA&rdquo; no meu Insta. Enfim, diferente do Dall-e que usa transformers, o Stable Diffusion, como o próprio nome diz, usa um modelo de difusão.</p>
<p>Esse modelo gera imagens simulando um processo aleatório rodando em reverso, ou seja, o processo de geração começa com uma simples distribuição, como um Gaussian noise, literamente um gerador de barulho aleatório mesmo, e vai gradualmente refinando esse barulho, passo a passo, até chegar numa imagem que parece com os dados que o modelo foi treinado. É meio como partir de um punhado de argila, tudo bagunçado, e passo a passo ir esculpindo e refinando, até chegar numa estátua. Esse processo de refinamento é dirigido por uma rede neural que aprende a predizer o próximo passo da difusão. E por isso se chama Stable Diffusion, ou Difusão Estável.</p>
<p>Um site que parece que tem ganhado relevância na comunidade de pesquisa e desenvolvimento de ferramentas e modelos de IA, é o HuggingFace. Ele serve como um repositório. É de lá que podemos baixar os modelos do LLaMA, Alpaca, Vicuna em todos os diferentes tamanhos e formatos. Existem modelos específicos pra texto como o Vicuna, específico pra imagens como o do Stable Diffusion, conversor de texto pra áudio como o Speech T5 do Facebook e muitos outros. O ponto é que não existe uma única inteligência unificada que faz tudo, existem modelos isolados e independentes feitos pra tarefas específicas. Um GPT não sabe converter texto em áudio, isso é outro modelo.</p>
<p>Portanto, quando ver produtos web que parecem uma única inteligência, não é, é um integrador que divide o que você pediu entre diversas inteligências diferentes. O que me leva a outro tema que queria tocar de leve, mesmo porque só comecei a estudar isso recentemente. Eu expliquei como as coisas funcionam, superficialmente, por baixo dos panos. Mas agora precisamos falar do que fica por cima dos panos, o que usuários normais como eu e você enxergam, a interface.</p>
<p>Não só interface gráfica web de chat, mas interface de APIs e tudo mais. Surgiu um framework que tem ganhado cada vez mais relevância na comunidade que está investindo pra construir ferramentas integradas com os diversos serviços de IA que mencionei, como GPT, Bard, Stable Diffusion e outros. Esse framework se chama LangChain. A grosso modo, pense um framework como Django, ou Laravel, ou Rails, ou Spring, mas feito pra construir aplicativos que usam IA.</p>
<p>Eu gostei particularmente da documentação. Pra um projeto de código aberto novo, tem material suficiente de estudo, incluindo pesquisa de papers acadêmicos sendo publicados agora. Em particular queria tocar no ponto de prompts. A moda dos parasitas sangue-suga agora, é criar cursos online de prompts. Já viram por aí? &ldquo;Vire um Engenheiro de Prompt&rdquo;. É a coisa mais idiota que já vi.</p>
<p>Seria o equivalente a você se chamar de &ldquo;Engenheiro de Pesquisa no Google&rdquo;. Já deixo a dica pra você: não faça nenhum deles. Sem nem olhar, posso garantir que quase todos são pega-trouxa. Lembra aquele ditado? Todo dia um malandro e um otário acordam e vão pra rua, e quando se encontram, rola negócio. Não seja o otário.</p>
<p>Enfim, o LangChain é um framework extenso. Não pense algo simples como um Express de Node.js. Isso tá mais pra um Spring de Java. Tem diversos conceitos, como Models pra interfacear com os diversos serviços de IA, como o ChatGPT. A grosso modo esses Models seriam como um Hibernate, um ORM pra IAs. Mas tem outras abstrações. Tem Agentes. Tem Correntes. Tem Índices, mas uma das partes interessantes é que tem Prompts.</p>
<p>Diferente desses cursinhos idiotas por aí, que ficam mais no esquema &ldquo;olha só, eu testei uns prompts aqui e vou compartilhar com vocês&rdquo; ou então povo que fica copiando teorias da conspiração de prompt que surgem no Reddit. De fato existem pesquisas acadêmicas sendo feitas no estudo das melhores formas de se fazer perguntas pra passar pra um transformador generativo como GPT. Lembram o que eu falei? Os transformers só continuam adicionando palavras na frente do prompt que você escreveu. Quanto melhor escrito for o prompt, maiores as probabilidades de se conseguir as respostas que procura. Ele vai conseguir prever melhor as palavras seguintes.</p>
<p>O melhor prompt não é escrever um textão aleatório, mas sim conciso e estruturado. Na documentação eles fazem links pros diversos papers acadêmicos com o estudo pra cada tópico. Eu não levaria 100% a sério, nem todos os papers são consenso, muita coisa é só teoremas e hipóteses. Mas é melhor do que chutes do YouTuber.</p>
<p>Com um LangChain, eu poderia fazer um Model que se integra com o meu Vicuna rodando localmente, daí poderia não depender de serviços de terceiros e também manter a privacidade dos meus dados, já que não preciso compartilhar nada com ninguém. Se alguém tiver interesse de estudar, pesquisar ou até empreender com essa nova geração de transformadores, essas são ferramentas que podem ser muito úteis. Esquece curso idiota que é &ldquo;duh, olha só como sou inteligente, sei integrar com a API da OpenAI&rdquo;. Sério, isso é o básico do básico do básico, você aprende em 10 minutos num blog post. Não tem absolutamente nada demais. </p>
<p>Aliás, um curso de IA que se preza, tem por obrigação ter tudo que eu falei neste vídeo, só que explicado com 10 vezes mais detalhes. Caso contrário não vale seu dinheiro e muito menos seu tempo. Não perca tempo com esses cursos caça níquel. São todos perda de tempo. Veja os links que deixei na descrição do video e estude um a um que no final você vai aprender muito melhor, e de graça. O hype em torno de IA está fora de proporção. Um prato cheio pros oportunistas de plantão.</p>
<p>Pra finalizar, acho que vale a pena voltar na questão que incomoda todo programador iniciante: &ldquo;mas será que com essa evolução rápida de transformers, não é questão de tempo até ele substituir todos os programadores?&rdquo;. Eu já tinha feito um video só pra responder isso e recomendo que assistam, mas considerando o que expliquei hoje, vou explicar porque, tecnicamente, isso não vai acontecer.</p>
<p>Primeiro. Um transformer é incapaz de gerar textos sobre assuntos que nunca viu na vida. Isso é importante de entender. Vamos recordar, o que é um modelo, é um banco de dados de tokens retirados dos textos de treinamento e mais importante: as probabilidades de um token pra outro token dentro de uma rede. O modelo não contém o texto original, só pesos.</p>
<p>Vou repetir porque isso é importante: nenhum texto original aparece na sua forma original, dentro do modelo. Pra ter na cabeça pense assim. Já viram que vários livros no final tem algumas páginas com um &ldquo;índice remissivo&rdquo;? São todas as palavras importantes que aparecem no texto do livro e as páginas onde aparecem. É que nem um índice rudimentar de banco de dados.</p>
<p>Quando fazemos um prompt e pedimos, sei lá, pra ele citar um trecho da peça King Lear, de Shakespeare. Vai gerar o trecho, se não igual, muito próximo ao trecho do texto original. E isso confunde, pois parece que então, ele tem o texto inteiro guardado em algum lugar. Mas não. Por exemplo, pedi pra ele citar um diálogo entre Albany e Cordelia e corretamente diz que não existe diálogo entre os dois na peça, e sugere outra cena, Ato 4, Cena 2, onde Albany fala com sua esposa Goneril. E segue o trecho exatamente como está na peça. </p>
<p>Novamente, é muito difícil traçar o caminho exato dentro do modelo, indo de probabilidade em probabilidade pra reverter o processo e descobrir como o GPT conseguiu escrever o texto. Mas podemos dar um chute educado: a sequência de palavras do diálogo tem altíssima probabilidade. Primeiro, no meu prompt tem todas as palavras-chave importantes pro contexto como &ldquo;Shakespeare&rdquo;, &ldquo;Albany&rdquo; e tudo mais.</p>
<p>Quando ele começa digitando o trecho &ldquo;You are&rdquo; no contexto to diálogo, a chance maior é da próxima palavra ser &ldquo;not&rdquo;, depois da palavra ser &ldquo;worth&rdquo;, depois ser &ldquo;the&rdquo; e depois &ldquo;dust&rdquo;. São as probabilidades mais altas dessa sequência. Ele não tem o texto original, mas tem as probabilidades, que permitem remontar uma boa parte do texto original.</p>
<p>Até certo ponto, a grosso modo, o modelo acaba servindo como uma versão comprimida dos textos originais, mas com perdas. Quebramos todos os textos em tokens e gravamos os relacionamentos de forma que é possível recuperar alguns desses textos. Mas não podemos garantir que é possível recuperar tudo, porque a mistura de alguns textos similares vai desajustar as probabilidades.</p>
<p>No final teríamos no máximo uma versão mesclada de dois ou mais textos. Quanto mais repetidas vezes um determinado texto aparece em diversas fontes no material de treinamento, maiores as chances de conseguir reconstruir depois. Shakespeare, tendo obras que foram analisadas, discutidas e citadas inúmeras vezes em várias fontes, tem maiores chances de ser reconstruído.  </p>
<p>Sobre mim, Fabio Akita, já vai ser mais difícil, porque no contexto geral da Web, eu sou extremamente pouco citado. Mesmo assim, o GPT 4 até que consegue fazer uma descrição super genérica sobre mim. Fala coisas certas, por exemplo, que sou brasileiro, que fundei a CodeMiner, que palestrei em eventos e ajudei a divulgar Ruby on Rails. Mas aí fala que também ajudei a divulgar metodologias Ágeis, que não é mentira, mas nunca foi um ponto importante.</p>
<p>Ele fala que fui &ldquo;keynote speaker&rdquo;, ou seja, que dei palestra de abertura em muitas conferências, o que não é verdade. Ele já começou a misturar informações de outras pessoas similares. Mas isso não é acidente, é porque comparado a um Shakespeare, não tem quase nada sobre mim na Web, e as probabilidades são muito parecidas com outros palestrantes de tecnologia da mesma área.</p>
<p>Por isso também é difícil pra ele citar fontes exatas, porque cada palavra do texto que gera pode ter vindo de um lugar diferente. Não tem como saber exatamente. Pra ter links pra fontes, como o Bing faz, especulo que precisa de um processo em duas ou mais etapas. Primeiro, gerar o texto da resposta, como um GPT faz. Depois, pegar esse texto e fazer uma pesquisa tradicional no Bing antigo. Daí cruzar as duas informações e ver se os textos minimamente batem, e finalmente apresentar juntas, como se a resposta já tivesse saído pronto com os links pras fontes. </p>
<p>Não é o transformer que dá os links, é um segundo processo separado. Porque o modelo do transformer, em si, não tem como garantir exatamente qual parte veio de onde. Quanto mais um certo assunto tiver textos pra treinar, mais precisas vão ser as respostas. Por isso é mais fácil conseguir informações sobre celebridades, do que sobre alguém como eu ou vocês. No nosso caso, nossas informações vão ser misturadas com de outras pessoas similares, porque as probabilidades não vão ajudar. </p>
<p>Lembra a temperatura de &ldquo;criatividade&rdquo;? Ela garante que, mesmo sem saber a resposta exata, o ChatGPT vai continuar tentando responder baseado nas probabilidades que tem no modelo atual. Mas não tem jeito, toda resposta sobre coisas que não tinha no material original de treinamento, ou tinha muito pouco, vai ser uma alucinação probabilística. Só isso. E vai estar errado. E isso vai acontecer com tudo que for novidade. No dia 1, todo transformer vai ser praticamente inútil.</p>
<p>Outra coisa, mesmo sendo capaz de ler código, explicar código dos outros e gerar códigos, ainda não vai ter a capacidade de gerar um projeto inteiro. Lembra os códigos vazados do Twitch ou o código de ranking do Twitter que eu analisei? Estamos falando de milhares de arquivos, centenas de milhares de linhas de código. Sabe qual é um dos calcanhares de aquiles de toda IA? Backtracking e memória. As respostas costumam ser boa hoje porque ele leva em consideração o texto anterior. Em partes.</p>
<p>Existe um limite de quanto consegue voltar atrás no texto anterior. No caso do GPT 3 e 4 atual, ele se limita a enxergar uma janela de contexto de no máximo 2048 tokens. E as respostas também tem um limite. Quanto mais longo for esse limite, mais caro fica pra processar o resultado seguinte do gerador de palavras, maiores as chances dele começar a &ldquo;esquecer&rdquo; o contexto e muito maiores as chances de rapidamente perder a coerência de respostas longas.</p>
<p>Entendam: o GPT atual não tem capacidade de ler mais que 2048 tokens. Então jogar o código inteiro da kernel do Linux pra ele analisar, por exemplo, é impossível. E pedir pra ele escrever um código desse mesmo tamanho, é mais impossível ainda. Lógico, nada é impossível, mas é altamente improvável. No meu Vicuna rodando local, tem essa tela de configuração pra justamente configurar o tamanho da janela de contexto e de resposta. E não é um número muito grande não,</p>
<p>Num teste não científico, tentei abrir duas janelas do projeto text-generation que é a interface web que estou usando em cima do Vicuna. Não sei se é uma precaução da interface web ou uma limitação do próprio Vicuna por baixo, mas só tem capacidade de responder uma pergunta de cada vez. Se eu abrir outro navegador e tentar fazer outra pergunta, enquanto na outra janela ele ainda não terminou de responder a primeira, vai ignorar e não deixa rodar em paralelo.</p>
<p>E mesmo se conseguisse. Só com um único processo de resposta, está consumindo, no mínimo, 50% da minha GPU, consumindo mais de 18 gigabytes de VRAM, e enquanto responde esse consumo continua aumentando. Ou seja, numa máquina como a minha, ele só consegue responder uma pergunta de cada vez. Você está pensando em fazer um serviço online, SaaS, pra vários usuários acessarem? Vai sair super caro, porque só vai possibilitar uma resposta a cada 2 a 5 segundos. Um servidor web pequeno tem capacidade pra responder, sei lá, 1000 requisições por segundo ou mais. Mas um Vicuna está mais pra 0.2 requisições por segundo. É ridiculamente pesado e não escala.</p>
<p>Não lembro que artigo que a OpenAi falava que cada dia de operação do ChatGPT custa 1 milhão de dólares. Sim, 1 milhão, por dia. Mais ou menos faz sentido, dado o custo de GPU necessário pra responder todo mundo. Mas claro, novas técnicas de otimização tem aparecido, como o exemplo de quantização que expliquei antes, mas estamos falando que precisa melhorar ainda umas 10 mil vezes pra ficar economicamente viável pra casos como escrever um livro inteiro de 500 páginas, sem perder coerência, ou escrever um projeto de verdade com milhares de arquivos de código. No estágio atual da tecnologia, isso é impossível.</p>
<p>Essas são só algumas das razões de porque um ChatGPT não vai substituir programadores e outras profissões. A partir de um certo ponto fica tão caro que não compensa, especialmente porque como já expliquei, essa ¨inteligência&quot; não é inteligente, só cospe probabilidades. Não tem criatividade, não sabe tomar decisões baseadas em coisas como custo-benefício ou bom senso. Não vai ter coerência em respostas longas, não tem noção se o código sendo cuspido tem segurança, ou escalabilidade. Ela tem memória menor que um peixinho dourado. 2048 tokens. Mesmo se aumentar 10x esse limite, ainda é super pouco. O script deste episódio tem 15 mil palavras. O ChatGPT é incapaz de escrever meus scripts.</p>
<p>Mas tudo bem, como uma ferramenta de auxílio, é super boa. Trechos pequenos e repetitivos, ou código trivial que facilmente encontramos num stackoverflow repetidas vezes. O estado atual de transformers é mais que suficiente pra acelerar um bom profissional. Hoje é impossível e vai continuar sendo impossível alguém que não é programador conseguir fazer um projeto complexo inteiro. Qualquer um que diga o contrário não tem a menor noção do que está falando. Considere como os algoritmos funcionam e considere os pontos de máximo e mínimo. Não basta dizer &ldquo;ah, eu tenho esperança, é só ter fé&rdquo;. Precisamos de números e os números dizem que é extremamente improvável.</p>
<p>Eu uso o GitHub Copilot faz meses. Em vários videos, toda vez que aparece trechos de código, usei a ajuda do ChatGPT ou Copilot. Ambos tem plugins pra vários editores como NeoVim ou VS Code e funcionam super bem. Tem capacidade de ler o código do arquivo que se está editando. Não consegue usar o projeto inteiro de contexto pelos limites que acabei de explicar, mas só de usar o trecho próximo do que está digitando, já ajuda muito. Ou seja, parte do prompt é o código do arquivo aberto naquele momento. </p>
<p>Pra mim, vale cada centavo. E todo desenvolvedor que quiser ter vantagens de produtividade, deveria considerar usar esses plugins. De novo, não vai conseguir fazer tudo pra você, e por causa da aleatoriedade e alto risco de alucinações, você nunca deve aceitar o código que ele sugere sem ler com muita calma antes. Mas pra tarefas bem braçais, tediosas, onde faríamos muito copy e paste, como fazer testes unitários simples, ajuda. Testem.</p>
<p>Com tudo isso que expliquei, minha conclusão pessoal é que não estamos nada perto do que o povo chama de AGI, ou Inteligência Artificial Geral. A IA que vai superar todas as IAs. A IA que vai pegar o código inteiro do GPT 4 e sozinho gerar um GPT 5 melhorado. O coitado male male tem capacidade de lembrar de um único arquivo de código, que dirá milhares. Mesmo se conseguisse, ele não estará &ldquo;analisando&rdquo;. Ele não analisa nada. Ele não sabe analisar. Não sabe as regras de cálculo, as regras de programação. Só sabe juntar pedaços que viu repetidas vezes de muitas fontes, 100% via probabilidades. Quando não sabe, vai pegar as probabilidades mais próximas e cuspir o que der, e vai ser um texto sem sentido nenhum.</p>
<p>Vou repetir: transformers não tem inteligência nenhuma de análise e cognição. Ele não sabe porque 2 mais 2 é 4, só leu muitas vezes que é, e as probabilidades fazem ele repetir isso. GPT quer dizer Transformer Generativo Pré-Treinado. Hoje você entendeu o que esses termos significam. E se ficou até aqui, espero que tenha entendido: por mais impressionante que pareçam os textos que gera, é só um gerador de textos, um auto-corretor de teclado de celular glorificado.</p>
<p>Um gerador de textos nunca vai ser inteligente. Só vai &ldquo;parecer&rdquo; inteligente. Assim como o formato de animal que você viu numa nuvem foi totalmente um acidente. Ninguém teve a intenção de desenhar um animal na nuvem, sequer é um animal, é só um formato aleatório. Foi você que escolheu entender assim. </p>
<p>E foi por isso que no outro episódio de ChatGPT eu afirmei e repito: Seu grau de empolgação com IA é inversamente proporcional ao seu entendimento de IA. Quanto menos entender, mais empolgado vai ficar. Vai acreditar que as ações da NVIDIA estarem a mais de 400 dólares é normal, porque acredita que negócios de IA vão aumentar absurdamente e em breve a ação vai atingir 1000 dólares. Vai investir todo seu dinheiro nisso. Já eu, acho que a ação da NVIDIA está hiper valorizado por puro hype. O preço justo da ação deveria ser abaixo de 300 dólares, e vai voltar pra isso uma hora. Não sei quando vai estourar, mas eu estou precavido.</p>
<p>Não tenho dúvidas que transformers são úteis. Várias ferramentas já estão usando. Em particular o Creative Cloud da Adobe tá tirando bom proveito. A Microsoft vai embutir em Office, Windows e tudo mais. Mas no final é isso que vamos ter: um Alexa melhorado. Um template mais inteligente de Excel. Mas não ache que vai substituir seu Diretor Financeiro e que ele vai conseguir tomar decisões inteligentes. Só parece, por sorte e por acidente. Quem apostar em mais do que isso, nas tecnologias atuais, vai perder.</p>
<p>Pessoalmente eu acho que as tecnologias de transformers e outras que nem mencionei hoje que compõe o que é o GPT, assim como tudo, segue uma curva em S. Passamos pela parte do S que é o crescimento que parece exponencial, mas a curva tem um teto em cima. Quando pulamos de bilhões de parâmetros pra trilhões de parâmetros, vamos começar a ter retornos diminuídos, diminishing returns. Se amanhã sair um GPT 5 com o dobro de parâmetros, sei lá 300 trilhões, não vai ficar duas vezes melhor. Talvez melhore sei lá, 20%. Cada melhoria custa mais caro do que o retorno. Isso é diminishing returns. </p>
<p>Não existe crescimento infinito, e quanto mais rápido você força, só chega mais rápido no teto. Precisa acontecer novas descobertas, novas invenções que ainda não conhecemos, obviamente, pra ter uma nova etapa de evolução significativa. Enquanto isso não acontecer e só insistir no que temos até hoje, não vai ser muito melhor que isso não. Já estamos perto do limite. E AGI não está no horizonte ainda. É a mesma coisa ficar especulando se estamos pertos de conseguir construir uma USS Enterprise. É irrelevante, beira ficção científica.</p>
<p>Acho que já me estendi demais. Como falei no começo do video, eu não sou nem de longe um especialista no assunto, sequer tenho experiência prática de trabalhar em projetos relacionados. Meu conhecimento até agora é puramente teóricos. Eu só pesquisei o que existe publicado online. Se eu entendi até aqui em poucos dias, quem dedicar algumas semanas vai acabar sabendo muito mais do que eu, muito rapidamente. Não é tão difícil assim. Espero que tenha dado pra quebrar alguns mitos que tinham na cabeça e finalmente tenham conseguido separar o joio do trigo. Se ficaram com dúvida ou quiserem complementar, fiquem a vontade 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>llms</category><category>modelos-locais</category><category>machine-learning</category><category>akitando</category></item><item><title>[Akitando #141] Conversando sobre TDAH, Autismo, Ansiedade e Depressão | Tirando a Camisa de Força</title><link>https://akitaonrails.github.io/2023/04/25/akitando-141-conversando-sobre-tdah-autismo-ansiedade-e-depressao-tirando-a-camisa-de-forca/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/04/25/akitando-141-conversando-sobre-tdah-autismo-ansiedade-e-depressao-tirando-a-camisa-de-forca/</guid><pubDate>Tue, 25 Apr 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/yjhn2fjByNU"
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;Assunto delicado mas importante. Muitos não sabem que podem ter transtornos neurológicos como TDAH, Autismo, que podem ser a razão de crises de ansiedade, depressão. E isso pode estar afetando seu aprendizado, sua carreira e seus relacionamentos. Vamos entender o que de fato são esses transtornos, como eles se manifestam, como funcionam e o que você poderia fazer.&lt;/p&gt;
&lt;h2&gt;Capítulos&lt;span class="hx:absolute hx:-mt-20" id="capítulos"&gt;&lt;/span&gt;
&lt;a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:00:33 - CAP 00 - Por quê? Objetivos&lt;/li&gt;
&lt;li&gt;00:02:11 - CAP 01 - Definições - Introdução&lt;/li&gt;
&lt;li&gt;00:04:07 - CAP 02 - Depressão - Você pensa assim?&lt;/li&gt;
&lt;li&gt;00:06:33 - CAP 03 - TDAH - Introdução a Deficit de Atenção&lt;/li&gt;
&lt;li&gt;00:10:34 - CAP 04 - Espectros - Transtornos não são binários&lt;/li&gt;
&lt;li&gt;00:12:31 - CAP 05 - Cérebro - Fisiologia do Transtorno&lt;/li&gt;
&lt;li&gt;00:17:27 - CAP 06 - Efeitos do TDAH - O que o transtorno faz?&lt;/li&gt;
&lt;li&gt;00:22:44 - CAP 07 - Motivação - Você Procrastina?&lt;/li&gt;
&lt;li&gt;00:27:29 - CAP 08 - Ansiedade - Como Lidar?&lt;/li&gt;
&lt;li&gt;00:30:03 - CAP 09 - Sensitividade à Rejeição - Caminho pra Depressão&lt;/li&gt;
&lt;li&gt;00:33:33 - CAP 10 - Por que temos esses Transtornos? Teoria Evolucionária&lt;/li&gt;
&lt;li&gt;00:38:47 - CAP 11 - Genética + Ambiente - O que fazer?&lt;/li&gt;
&lt;li&gt;00:43:14 - CAP 12 - Qualidades de TDAH? Boas Notícias&lt;/li&gt;
&lt;li&gt;00:46:24 - CAP 13 - Meu Espectro Autista - Como é pro Akita?&lt;/li&gt;
&lt;li&gt;00:53:03 - CAP 14 - Conclusão - Próximos Passos&lt;/li&gt;
&lt;li&gt;01:00:07 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Paper: Amygdala Abnormalities in Adults With ADHD (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/26964869/"target="_blank" rel="noopener"&gt;https://pubmed.ncbi.nlm.nih.gov/26964869/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: The heritability of clinically diagnosed Attention-Deficit/Hyperactivity Disorder across the life span (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4071160/"target="_blank" rel="noopener"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4071160/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: Genomic analysis of the natural history of attention-deficit/hyperactivity disorder using Neanderthal and ancient Homo sapiens samples (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7248073/"target="_blank" rel="noopener"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7248073/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: The Emerging Neurobiology of Attention Deficit Hyperactivity Disorder: The Key Role of the Prefrontal Association Cortex (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2894421/"target="_blank" rel="noopener"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2894421/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: Associations of Irritability With Functional Connectivity of Amygdala and Nucleus Accumbens in Adolescents and Young Adults With ADHD (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/34724835/"target="_blank" rel="noopener"&gt;https://pubmed.ncbi.nlm.nih.gov/34724835/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: Anxiety modulates the relation between attention-deficit/hyperactivity disorder severity and working memory-related brain activity (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5581282/"target="_blank" rel="noopener"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5581282/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: Evaluating Dopamine Reward Pathway in ADHD (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2958516/"target="_blank" rel="noopener"&gt;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2958516/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Paper: Hunter in a Farmer&amp;rsquo;s World (&lt;a href="https://www.longdom.org/open-access-pdfs/hunters-in-a-farmers-world-adhd-and-hunter-gatherers-2332-0915-1000150.pdf"target="_blank" rel="noopener"&gt;https://www.longdom.org/open-access-pdfs/hunters-in-a-farmers-world-adhd-and-hunter-gatherers-2332-0915-1000150.pdf&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Livro: ADHD: A Hunter in a Farmer&amp;rsquo;s World (&lt;a href="https://www.amazon.com.br/ADHD-Hunter-Farmers-Thom-Hartmann/dp/162055898X"target="_blank" rel="noopener"&gt;https://www.amazon.com.br/ADHD-Hunter-Farmers-Thom-Hartmann/dp/162055898X&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Livro: Adult ADHD: How to Succeed as a Hunter in a Farmer&amp;rsquo;s World (&lt;a href="https://www.amazon.com.br/Adult-ADHD-Succeed-Hunter-Farmers/dp/1620555751"target="_blank" rel="noopener"&gt;https://www.amazon.com.br/Adult-ADHD-Succeed-Hunter-Farmers/dp/1620555751&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Dr. Tracey Marks (&lt;a href="https://www.youtube.com/@DrTraceyMarks"target="_blank" rel="noopener"&gt;https://www.youtube.com/@DrTraceyMarks&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;HealthyGamerGG Channel (&lt;a href="https://www.youtube.com/@HealthyGamerGG"target="_blank" rel="noopener"&gt;https://www.youtube.com/@HealthyGamerGG&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/yjhn2fjByNU"
    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>Assunto delicado mas importante. Muitos não sabem que podem ter transtornos neurológicos como TDAH, Autismo, que podem ser a razão de crises de ansiedade, depressão. E isso pode estar afetando seu aprendizado, sua carreira e seus relacionamentos. Vamos entender o que de fato são esses transtornos, como eles se manifestam, como funcionam e o que você poderia fazer.</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:33 - CAP 00 - Por quê? Objetivos</li>
<li>00:02:11 - CAP 01 - Definições - Introdução</li>
<li>00:04:07 - CAP 02 - Depressão - Você pensa assim?</li>
<li>00:06:33 - CAP 03 - TDAH - Introdução a Deficit de Atenção</li>
<li>00:10:34 - CAP 04 - Espectros - Transtornos não são binários</li>
<li>00:12:31 - CAP 05 - Cérebro - Fisiologia do Transtorno</li>
<li>00:17:27 - CAP 06 - Efeitos do TDAH - O que o transtorno faz?</li>
<li>00:22:44 - CAP 07 - Motivação - Você Procrastina?</li>
<li>00:27:29 - CAP 08 - Ansiedade - Como Lidar?</li>
<li>00:30:03 - CAP 09 - Sensitividade à Rejeição - Caminho pra Depressão</li>
<li>00:33:33 - CAP 10 - Por que temos esses Transtornos? Teoria Evolucionária</li>
<li>00:38:47 - CAP 11 - Genética + Ambiente - O que fazer?</li>
<li>00:43:14 - CAP 12 - Qualidades de TDAH? Boas Notícias</li>
<li>00:46:24 - CAP 13 - Meu Espectro Autista - Como é pro Akita?</li>
<li>00:53:03 - CAP 14 - Conclusão - Próximos Passos</li>
<li>01:00:07 - 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>Paper: Amygdala Abnormalities in Adults With ADHD (<a href="https://pubmed.ncbi.nlm.nih.gov/26964869/"target="_blank" rel="noopener">https://pubmed.ncbi.nlm.nih.gov/26964869/</a>)</li>
<li>Paper: The heritability of clinically diagnosed Attention-Deficit/Hyperactivity Disorder across the life span (<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4071160/"target="_blank" rel="noopener">https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4071160/</a>)</li>
<li>Paper: Genomic analysis of the natural history of attention-deficit/hyperactivity disorder using Neanderthal and ancient Homo sapiens samples (<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7248073/"target="_blank" rel="noopener">https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7248073/</a>)</li>
<li>Paper: The Emerging Neurobiology of Attention Deficit Hyperactivity Disorder: The Key Role of the Prefrontal Association Cortex (<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2894421/"target="_blank" rel="noopener">https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2894421/</a>)</li>
<li>Paper: Associations of Irritability With Functional Connectivity of Amygdala and Nucleus Accumbens in Adolescents and Young Adults With ADHD (<a href="https://pubmed.ncbi.nlm.nih.gov/34724835/"target="_blank" rel="noopener">https://pubmed.ncbi.nlm.nih.gov/34724835/</a>)</li>
<li>Paper: Anxiety modulates the relation between attention-deficit/hyperactivity disorder severity and working memory-related brain activity (<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5581282/"target="_blank" rel="noopener">https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5581282/</a>)</li>
<li>Paper: Evaluating Dopamine Reward Pathway in ADHD (<a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2958516/"target="_blank" rel="noopener">https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2958516/</a>)</li>
<li>Paper: Hunter in a Farmer&rsquo;s World (<a href="https://www.longdom.org/open-access-pdfs/hunters-in-a-farmers-world-adhd-and-hunter-gatherers-2332-0915-1000150.pdf"target="_blank" rel="noopener">https://www.longdom.org/open-access-pdfs/hunters-in-a-farmers-world-adhd-and-hunter-gatherers-2332-0915-1000150.pdf</a>)</li>
<li>Livro: ADHD: A Hunter in a Farmer&rsquo;s World (<a href="https://www.amazon.com.br/ADHD-Hunter-Farmers-Thom-Hartmann/dp/162055898X"target="_blank" rel="noopener">https://www.amazon.com.br/ADHD-Hunter-Farmers-Thom-Hartmann/dp/162055898X</a>)</li>
<li>Livro: Adult ADHD: How to Succeed as a Hunter in a Farmer&rsquo;s World  (<a href="https://www.amazon.com.br/Adult-ADHD-Succeed-Hunter-Farmers/dp/1620555751"target="_blank" rel="noopener">https://www.amazon.com.br/Adult-ADHD-Succeed-Hunter-Farmers/dp/1620555751</a>)</li>
<li>Dr. Tracey Marks (<a href="https://www.youtube.com/@DrTraceyMarks"target="_blank" rel="noopener">https://www.youtube.com/@DrTraceyMarks</a>)</li>
<li>HealthyGamerGG Channel (<a href="https://www.youtube.com/@HealthyGamerGG"target="_blank" rel="noopener">https://www.youtube.com/@HealthyGamerGG</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>Imagino que o thumbnail deixou vocês preocupados de alguma forma, e tenho certeza que alguns vieram aqui pra saber que merda que o Akita vai cagar regra. Então deixa eu já começar com um enorme disclaimer: este video, de maneira nenhuma, deve servir de base pra diagnóstico de ninguém. Então qual o objetivo?</p>
<p>Quero falar um pouco sobre desordens psicológicas, que é um assunto muito importante, que afeta seu aprendizado, sua carreira, relacionamentos e sua vida em geral. Este video provavelmente vai ser o único onde vou falar sobre esse assunto, então vai ser denso como de costume.</p>
<p>&ldquo;Porra Akita, você não é psicólogo nem nada, vai cagar regra até nisso?&rdquo; Tem toda razão de pensar assim! Não é um assunto pra ser discutido sem cuidados. Nada do que vou dizer hoje é recomendação, muito menos substitui consultar um especialista licenciado. Nem este video, nem nenhum que você encontrar online, especialmente nos TikToks da vida, representa qualquer tipo de solução, só informação.</p>
<p>Lifehacks são divertidos de compartilhar, mas a intenção de hoje não é isso também. É aumentar awareness ao problema. Embora eu não goste da palavra em português, é contribuir na &ldquo;conscientização&rdquo;. Tentar ajudar a tirar o estigma e alguns mitos. Tudo isso tem base científica e tem toneladas de pesquisa pra entender.</p>
<p>Minha experiência e interesse existem porque ao longo de anos, sempre acabei me relacionando com pessoas neurodivergentes. E eu não gosto de não entender o que está acontecendo. Mais do que isso, eu mesmo sou neurodivergente. Sim, eu fui diagnosticado com T.E.A. transtorno do espectro autista, nível 1, por psicólogo, um ano atrás. Portanto também significa que eu queria entender um pouco mais de como minha própria cabeça funciona.</p>
<p>(&hellip;)</p>
<p>Faz anos que esse assunto fica flutuando na minha cabeça. Por um lado, eu sempre sou super cético. Alguns assuntos, categoricamente podemos dizer que é pseudociência, seja astrologia, homeopatia, os MBTI da vida. Sendo eu um boomer, muitos problemas psicológicos e psicologia em geral, na minha cabeça, sempre pareceu encaixar mais com pseudociência que ciência. Tenho certeza que muitos dos meus pares, povo da faixa de 40 anos pra cima, ainda pensam assim. E é um dos motivos que resolvi explicar um pouco disso.</p>
<p>O que está mais na moda falar é TDA ou TDAH, que em inglês é ADHD, o famoso transtorno ou desordem de déficit de atenção. Procurando no YouTube vem centenas de videos sobre isso. Comparando com meu próprio conhecimento antes de pesquisar sobre, sei que é muito fácil entender esse transtorno da maneira errada. Pra começar, todos vocês assistindo, ou são considerados neurotípicos ou neurodivergentes.</p>
<p>Neurotípico é um termo usado pra descrever pessoas cujo desenvolvimento neurológico ou padrões de comportamento são considerados típicos ou &ldquo;normais&rdquo; pra determinada idade ou cultura. Vocês não costumam ter diferenças significativas em processamento sensorial, interações sociais ou estilos de comunicação.</p>
<p>Neurodivergente, por outro lado, é um termo usado pra descrever pessoas cujo desenvolvimento neurológico e padrões de comportamento são considerados &ldquo;atípicos&rdquo; pra faixa etária ou cultura. Isso pode incluir condições como TEA ou transtorno do espectro autista como no meu caso, ou coisas como TDAH, dislexia e outras condições de processamento cognitivo, sensorial ou emocional.</p>
<p>Importante explicar, coisa que eu também não entendia, que neurodivergência não é uma patologia, uma doença, mas uma variação nas funções neurológicas. Significa dizer que o perfil psicológico é diferente do que é considerado &ldquo;típico&rdquo;. Pessoas neurodivergentes sofrem com problemas mas ao mesmo tempo podem ter formas únicas de esforço, perspectiva e jeitos de interagir com o mundo, que tem muito valor. Vou chegar nesse ponto.</p>
<p>O problema é que, sem as informações adequadas, quem sofre disso pode realmente achar que é uma doença. E como não é visível como uma perna amputada, essa pessoa pode sentir muita vergonha, muita culpa. E isso vai evitar que ela procure o tratamento adequado. Todos os transtornos psicológicos são espectros. Significa que não existe um tratamento geral pra todo mundo. Não existe fórmula mágica. E ao sofrer as consequências desses não entendimento, muitos podem cair em problemas maiores depois. Deixa eu ilustrar.</p>
<p>Vamos começar com algumas perguntas. Você costuma ter pensamentos de que não é bom o suficiente? Inclusive no sentido de supervalorizar seus fracassos e subestimar ou desvalorizar seus sucessos? Na sequência, isso te leva a pensar que fracassa tanto que ninguém liga pra você ou vão deixar de ligar pra você? Afinal, porque alguém iria ligar pra alguém fracassado né? Daí você costuma ter a sensação de, &ldquo;pra que me esforçar&rdquo;, é melhor ficar sozinho, não? Talvez doa menos. Pior, você se sente constantemente cansado, indisposto, até com aquela sensação que tá meio doente? E aí pensa, lógico, nem eu me aguento comigo mesmo, quem vai querer ficar perto de alguém como eu?</p>
<p>Se pensa até aqui, provavelmente já deve ter pensado algo como &ldquo;além do mais, ninguém entende como eu me sinto&rdquo;. E é verdade, muitas das pessoas ao seu redor não tiveram que passar pelo que você passou, mas seu pensamento vai além, você acha que ninguém vai ser capaz de te ajudar, nem mesmo ajuda profissional. Você acha que seu caso é único, seu fracasso vai ser constante, seu esforço parece inútil?</p>
<p>Pior do pior, quando outras pessoas te vêem nesse estado, ficam te falando &ldquo;vai fazer exercícios, vai comer melhor, vai fazer alguma coisa&rdquo;, e você vê todo mundo conseguindo fazer atividades que você não consegue e, de novo, isso aumenta sua sensação que é tudo culpa sua, certo? Você se sente culpado de ser incapaz de se tirar dessa situação desesperançosa, e se afasta mais ainda das pessoas. FInalmente, isso te leva à inevitável conclusão de que nada mais importa, nada do que você tenta fazer jamais vai ser suficiente, e claro, você nunca vai melhorar.</p>
<p>Já pensou dessa forma repetidas vezes? Ao ponto de ficar debilitado, sem querer fazer nada, faltando faculdade, faltando trabalho, faltando eventos sociais ou familiares? Ficando o dia todo maratonando Netflix, maratonando TikTok, ou no seu LOL ou Valorant da vida, horas e horas sem parar? Ou simplesmente escondido na sua cama o dia todo? Você está ficando pra trás, todo mundo parece tão distante lá na frente e você sente que não tem o que fazer?</p>
<p>Se sim, é possível que você esteja experimentando sintomas de depressão. Coisas como TDAH, autismo ou outros transtornos, se não forem tratados, podem te levar a pensar dessa forma. E de novo, não estou tentando te dar um diagnóstico, mas se sente assim, no mínimo deveria procurar um especialista, porque ao contrário do que parece na sua cabeça, essa situação é ruim, mas não tanto quanto pensa e o principal, isso não é permanente. Agora deixa eu explicar porque alguns chegam nessa linha de pensamento.</p>
<p>Vamos pegar TDAH por exemplo, transtorno de déficit de atenção. A primeira impressão é que se trata de uma pessoa que tem dificuldade de prestar atenção em alguma coisa. Só que não é isso. Se uma criança é diagnosticada com TDAH, alguns pais podem discordar, porque eles vêem que essa criança consegue ficar o dia inteiro jogando os Fortnite ou Roblox, nem precisa comer, está em hiperfoco. Mas não consegue prestar atenção na aula ou estudos. Então ela não tem déficit de atenção, só é preguiçosa mesmo.</p>
<p>Antes de continuar, deixa eu fazer mais um disclaimer. Existem sim, pessoas que que por diversos motivos se tornam maliciosas, mau caráter, desonestas e tudo mais. Vou pular esses casos, não é esse o foco de hoje. Assuma por um segundo que a criança do exemplo, não tem razão pra ser desonesta. Vou assumir que você assistindo é honesto.</p>
<p>E sim, de fato essa criança hipotética tem capacidade de hiperfoco em algumas atividades e não em outras, e isso é um déficit de atenção. Déficit não significa que ela tem atenção baixa o tempo todo, significa que, comparado com uma pessoa típica, tem uma montanha russa de atenção: algumas vezes super abaixo, algumas vezes super alto, e o principal: não tem controle sobre isso. Por isso é um transtorno ou desordem, não é uma incapacidade.</p>
<p>Algumas pessoas romantizam o tal hiperfoco, mas pra pessoa com TDAH não parece tão legal assim porque não é um super poder que pode ativar quando bem entender. Uma das melhores metáforas que encontrei foi assim: digamos que essa pessoa tem um compromisso muito importante às 6 da tarde. Fodeu. Ela vai passar o dia todo ansiosa com esse compromisso e vai ser incapaz de fazer qualquer outra coisa durante o dia até essa hora. Ou, se conseguir começar a fazer, é possível que esqueça do compromisso original.</p>
<p>Por que? Faz de conta que sua cabeça tem um editor de textos. Os neurotípicos conseguem salvar o documento parcial, deixar de lado, e abrir outros documentos pra trabalhar. Já uma pessoa com TDAH não tem a funcionalidade de salvar. Ou ela mantém o documento aberto até as 6 da tarde, ou se fechar, pode perder. Não é exagero, é assim que funciona.</p>
<p>Todo mundo tem algum grau de inatenção, hiperatividade ou impulsividade, que são características de TDAH. O problema é quando o transtorno afeta negativamente a vida da pessoa, ou seja, ela começa a se tornar incapaz de funcionar como gostaria, vai mal na escola, vai mal no trabalho, vai mal nos relacionamentos. E não é um episódio isolado, onde podemos identificar um momento de estresse, como ser despedido do emprego. É frequente durante a vida, desde a infância. É nesse caso que realmente precisa procurar tratamento.</p>
<p>Uma pessoa que, de vez em quando, esquece alguma coisa, de vez em quando faz alguma coisa impulsiva, de vez em quando fica distraída, isso é normal, é típico. Todo mundo passa por eventos estressantes vez ou outra que levam a isso. Você não &ldquo;ganha TDAH&rdquo; só porque passou por uma situação estressante. Tendo sido diagnosticado ou não o TDAH já existia desde que era criança.</p>
<p>E é por isso que TDAH é também um transtorno muito mal diagnosticado. Ao mesmo tempo muita gente que não sofre do transtorno acha que tem, e muita gente que tem, acha que não tem. Um exemplo disso são crianças com QI mais alto. Ironicamente, pessoas mais inteligentes, podem sofrer mais com esse tipo de transtorno. Por exemplo, imagine uma criança com TDAH que é forçada a participar da chatice de uma sala de aula normal. Ela tem dificuldade de prestar atenção.</p>
<p>Aí o professor nota a pessoa desatenta e chama ela pra responder uma pergunta da aula. O aluno se assusta, mas ele já sabe, o TDAH deixou ele distraído, tava mais focado girando caneta nos dedos. Mas ele é altamente inteligente, rapidamente lê a lousa, olha onde o livro do amigo do lado, tenta achar a resposta via pura dedução. Por acaso acerta, o professor faz o &ldquo;hum&rdquo;, e volta a dar aula.</p>
<p>Via força bruta, ele bypassou o prejuízo da inatenção. É um jeito dela não ser diagnosticada com tendo TDAH. Pessoas neurodivergentes, não só TDAH, nós costumamos ter diversas técnicas pra meio que &ldquo;mascarar&rdquo;, pra passarmos despercebidos num mundo feito pra neurotípicos. É algo que fazemos inconscientemente, como um mecanismo de defesa.</p>
<p>Deixa eu voltar num ponto que falei antes: transtornos psicológicos, no mundo Ocidental, costumam ser percebidos como uma chave de liga e desliga: ou a pessoa tem, ou não tem. É tipo tem câncer ou tem diabetes. É tudo ou nada. E faz sentido, esse tipo de doença, por todo o século XX, sempre foram as prioridades, são problemas literalmente mortais, se não tratados.</p>
<p>Por causa disso, problemas psicológicos sempre foram uma categoria de menor prioridade e menos atenção. Muita pesquisa nova e importante tem acontecido nos últimos 20 anos, ainda é um campo em desenvolvimento. Não sabemos tudo, mas por esse lado, nem coisas como câncer sabemos tudo ainda. Não nos impede de tratar e ter sucesso nos tratamentos. Digo isso pra tomar cuidado porque muitos ainda usam uma cartilha antiquada, de décadas atrás pra falar desses transtornos.</p>
<p>O que sabemos hoje é que esse tipo de transtorno é um espectro, um intervalo, e não um valor discreto. É como no meu caso de autismo. Por muito tempo, na minha cabeça, autismo era como um Dustin Hoffman em Rain man, talvez o mais famoso de todos. O meu grau de autismo não é alto, é o que antigamente se chamava de Asperger, hoje é grau 1 de autismo, o Rainman provavelmente era grau 3. Mesmo esses &ldquo;graus&rdquo; são só guias, de novo, não é uma chave com 3 valores discretos. É como um termômetro, o que é quente ou frio? São espectros, uma gradação.</p>
<p>Uma pessoa com TDAH não é alguém 100% inatento, 100% impulsivo e 100% hiperativo. São graus que variam dinamicamente dependendo da situação e diversas condições fisiológicas. E falando em fisiologia, a gente costuma pensar nesses transtornos  como algo que é exclusivo dentro da mente da pessoa. Por isso muitos pensam: &ldquo;só lida com isso e resolve&rdquo;. Mas o problema é físico tanto quanto é mental. E é aqui que quero quebrar a noção de que isso é tudo um problema inventado. É um problema físico e mensurável. Diversas pesquisas neurológicas já objetivamente mediram, por exemplo, usando ressonância magnética do cérebro.</p>
<p>Falando bem a grosso modo, nosso cérebro não é uma máquina simples e ainda não sabemos tudo dela. Mas já conhecemos o funcionamento de diversas regiões diferentes. Vamos entender algumas. Temos a insula que é uma região do cérebro localizada bem fundo no córtex cerebral, e tem diversas funções como processamento de emoções, habilidade de sentir e entender o estado interno do corpo, cognição social.</p>
<p>Temos a amygdala, que é diferente da amigdala na garganta, que em inglês é tonsil e eu sempre me confundo. No cérebro ela tem estrutura no formato de uma amêndoa, localizada fundo dentro do lobo temporal, e tem um papel importante no processamento de emoções, particularmente medo, ansiedade e agressão. Guarde essa informação, vai ser importante.</p>
<p>O nucleus accumbens é o sistema de recompensa do cérebro, localizado no prosencéfalo basal, responsável pelo processamento de prazer, recompensa e a tal da motivação. Mais pra cima, na região do lobo frontal temos o cortex prefrontal dorsolateral, que se envolve nas funções executivas, como memória de trabalho, a nossa RAM, flexibilidade cognitiva, tomada de decisões.</p>
<p>Finalmente, na frente temos o córtex frontal orbital, também no lobo frontal. Ele se envolve em tomada de decisões, regulação das emoções e comportamento social. Pra ilustrar, imagine que a região da insula e amygdala são responsáveis por te dar medo ou ansiedade.</p>
<p>Alguém estranho se aproxima de você, do nada, no meio da rua deserta, a noite. Adrenalina é produzida em segundos e você sente fisiologicamente: sua pulsação aumenta, sua respiração aumenta, sua visão periférica fecha, pupilas dilatam, você foca. É a reação de fight or flight, seu corpo sentiu uma ameaça, e te coloca em estado de fugir ou lutar.</p>
<p>Mais informação chega no seu cérebro à medida que você reconhece esse estranho, e na verdade era um conhecido seu, que você não tinha percebido. Possivelmente, seu córtex frontal orbital entra em ação e faz a computação cognitiva de mais alto nível, analisando se é mesmo uma situação que oferece risco ou não. E ao decidir que não, pode pedir pra regular a amygdala e desligar o alarme, vamos assim dizer. Não quer dizer que é exatamente assim que acontece, mas de novo, só pra ilustrar como esses componentes poderiam funcionar juntos.</p>
<p>Outra situação é que você quer entrar em forma, resolve fazer uma dieta. Seu centro de gratificação, o nucleus accumbens, junto com o cortex frontal, vão computar e avaliar o que todo mundo chama de &ldquo;bom senso&rdquo;. Se comer um doce agora, vou ficar gratificado agora, mas vou me arrepender amanhã. Daí ele põe na balança, e toma a decisão correta de, &ldquo;ok, não preciso comer esse doce agora&rdquo;. Pra neurotípicos, esse tipo de reação é normal, você sente uma pequena ansiedade, mas os sistemas de regulação entram em ação e você não tem nenhum tipo de sofrimento extra.</p>
<p>Não é o que acontece na cabeça de um neurodivergente com TDAH. Embora existam diversas pesquisas, ainda não é um consenso sobre exatamente os mecanismos, mas pra simplificar, entende-se que existe uma correlação entre pessoas com TDAH e alguma desregulação de receptores de dopamina em diversas regiões do cérebro. Justamente o neurotransmissor que é produzido quando temos alguma experiência boa, como comer um doce. A produção de dopamina é uma das formas de reinforçar comportamentos que tem benefício pra nossa sobrevivência. É como a gente se auto-treina.</p>
<p>Mas não só isso, dopamina está envolvido em outras coisas como controle de movimentos, em particular movimentos voluntários. Também é importante pra processar informação relacionado a recompensa e punição. E também se envolve na regulação de humor e emoções. Você consegue entender que se isso é desregulado, começa a fazer sentido pessoas que são &ldquo;desastradas&rdquo;, ou tem dificuldade de aprender, ou tem mudanças de humor estranhas.</p>
<p>E cuidado que existe outra desordem ligada a mudanças de humor, como desordem bipolar, que é diferente de TDAH e se trata diferente também, nem os medicamentos são os mesmos, aliás, medicamento de TDAH pode piorar bipolaridade. Por isso não devemos nos auto-avaliar e muito menos se auto-medicar.</p>
<p>Problemas graves de desregulação de dopamina tá relacionado a doenças consideradas mais sérias como Parkinson ou Esquizofrenia, vícios, mas em outro grau do espectro, também com TDAH. Outro aviso, cuidado, desregulação de dopamina é um fator, mas não é a causa de TDAH. Some a isso que algumas pesquisas escaneando o cérebro de diversos pacientes notou que de fato, regiões como insula e amygdala tem volume menor, e ao contrário do bom senso, isso pode causar hiperatividade nessas regiões.</p>
<p>Significa que os sinais de medo e ansiedade são amplificados muito além do normal, somado a um centro de recompensa desregulado, e pra piorar, tendo menos influência, menos regulação do córtex frontal orbital, que ajudaria a desligar esses alertas. Assim começa o tal transtorno do ponto de vista fisiológico. De novo, não é um problema inventado, é consequência dessa desregulação química.</p>
<p>Um fato que todos precisam entender é que emoções não existem à toa. Toda emoção são reações químicas e neurológicas que cumprem alguma função de sobrevivência. São ferramentas que foram sendo adaptadas e ajustadas ao longo de centenas de milhares de anos. Sentir medo ou ansiedade são características normais, mas no mundo moderno, esse sistema ainda não conseguiu se readaptar.</p>
<p>Um dos efeitos colaterais é esse déficit de regulação emocional. A pessoa com TDAH tem uma amplificação da sensação de medo e ansiedade em momentos ou situações que uma pessoa neurotípica não sentiria quase nada. Ela pode reagir de forma muito mais agressiva do que o momento exigiria. Isso deixa a pessoa mais irritável também e, claro, essas coisas somadas levam a problemas de relacionamento. Não só romântico, mas familiar, no trabalho.</p>
<p>Entendendo isso vamos imaginar um cenário. Uma criança que ninguém sabe que tem TDAH. Todo mundo tá conversando sobre filmes, sei lá, e essa criança não consegue acompanhar. Pro cérebro dela, começa a entediar rápido, ela se isola mentalmente. A cabeça está viajando em diversos pensamentos e por causa da impulsividade, do nada, ela interrompe a conversa e começa a falar de outro assunto nada a ver, tipo, de videogame. Mas não é que ela quis ser rude porque não tava gostando da conversa. Na cabeça dela acho que nem tava notando que estavam conversando, ela só quis começar a falar, por impulso.</p>
<p>Repita episódios assim ao longo dos anos na escola. Não só inatenção, impulsividade, mas hiperatividade, irritabilidade. Todo mundo acha essa pessoa esquisita. Não convida mais pra eventos sociais. Todo mundo achando ela esquisita, se torna o alvo favorito pra bullying. Essa criança não entende porque é o alvo ou porque acham ela esquisita. Em alguns casos, pode se safar se for do tipo que consegue ser tipo o palhaço da sala, a pessoa engraçada que consegue fazer os outros rirem.</p>
<p>Mais comum vai ser ela começar a notar o padrão. Os bullies só aparecem quando tem outras crianças por perto. Todo bully gosta de uma platéia. Então ela entende que ter pessoas por perto é uma situação de perigo. E isso é reinforçado. Portanto, toda vez que se vê perto de um grupo de crianças, dá meia volta, se esconde. Ela começa a ficar boa em evitar grupos porque o sistema de defesa deixa ela ansiosa perto de outras pessoas. Você se torna um adulto que se considera anti-social, que tem ansiedade perto de estranhos. Eis uma forma de como isso poderia surgir.</p>
<p>Antes de continuar, eu falei anti-social no sentido de se afastar das pessoas, mas não confundir com transtorno de personalidade anti-social que é um padrão de não se importar com as outras pessoas, não ter empatia ou remorso. Esse tipo de transtorno fala de pessoas que são mentirosas, manipuladoras, roubam, agridem, e coisas assim. É completamente diferente. E se pra alguns isso pareceu narcisismo, isso é outro transtorno diferente. E de novo, é por isso que ninguém deve sair se auto-diagnosticando.</p>
<p>Enfim, ansiedade não é só adrenalina alta por alguns minutos. Outra substância importante na reação de fight or flight é cortisol. Se numa situação de perigo no meio da floresta de antigamente, adrenalina é o boost de energia nos primeiros minutos pra você conseguir dar no pé, cortisol é a energia que vai durar 24 horas pra manter você correndo até conseguir se afastar do perigo. Imagina, tem um tigre no meio da floresta, você tem que conseguir fugir sem parar um segundo. Adrenalina e cortisol são justamente os tais hormônios de estresse.</p>
<p>Mas não pára nisso. Correndo no meio do mato cerrado, você vai se arranhar, se cortar, e isso pode infeccionar. Imagina que trágico conseguir correr do tigre só pra morrer depois por infecção? Ao mesmo tempo que os hormônios de estresse ativam, em paralelo seu sistema imunológico também entra em ação. Milhões de células brancas dormentes se ativam e se preparam pra combater infecções.</p>
<p>Mas estamos no século XXI. Hoje não tem uma situação de correr na floresta por horas e se machucar. Sua ansiedade social perto de outras pessoas, tá amplificado, mas felizmente você não vai se tornar comida de tigre. Mas e o cortisol e sistema imunológico que ativaram? É só se acalmar que desligam e tá tudo bem? Ah, se fosse fácil assim.</p>
<p>O cortisol extra vai garantir que você não vai conseguir dormir. Insônia, pode causar hiperatividade no meio da noite. Só que você precisa dormir, porque precisa acordar cedo pra faculdade ou pro trabalho. Agora fodeu. Sua ansiedade aumenta porque não consegue dormir. Quanto mais ansioso ficar por não conseguir dormir, mais acordado vai ficar, e é uma sensação amplificada, e com desregulagem, ou seja, difícil de desligar. E é por isso que pessoas hiper ansiosas tem dificuldades de dormir.</p>
<p>Mas a má notícia continua, o sistema imunológico foi ativado, tá pronto pra lidar com machudados e infecções, que não acontecem. Ele vai começar a atacar você mesmo, causando inflamação e danos em tecidos. Sabe problemas digestivos, gastrite, manchas ou machucados por sua pele? Pode ser isso. É seu sistema atacando você mesmo.</p>
<p>E finalmente, essas respostas também modificam seu metabolismo, podendo te tornar resistente a insulina, aumentando o armazenamento de glicose como células de gordura, fazendo você ganhar mais peso do que deveria. É seu corpo te preparando pro pior no meio da floresta, só que você não vive no meio da floresta. Muitos de vocês que sofrem desses transtornos podem ter problemas físicos que aparecem do nada, mas está tudo relacionado. Entenderam como uma coisa pode ligar na outra?</p>
<p>TDAH é uma disfunção do sistema executivo no cérebro. O sistema executivo é tipo nossa CPU, a unidade lógica, responsável por organização de tarefas, organização de tempo, tomada de decisões, tudo que neurotípicos consideram &ldquo;normal&rdquo;, só que ele está desregulado. Numa hora tá tudo bem, tá estudando, trabalhando, mas qualquer coisa pode distrair, ou causar parálise de análise, ou overanalysis, que muitos poderiam chamar de procrastinação ou falta de motivação.</p>
<p>Isso vocês já ouviram várias vezes. Todo mundo fala, &ldquo;evite procrastinar&rdquo;. Fácil falar né, se fosse fácil, todo mundo evitaria. Ou então &ldquo;tente ser mais motivado&rdquo;. Mas o que significa isso? Eu acabei de falar, medo, por exemplo, é uma reação instintiva, veio com milhares de anos de sobrevivência. Mas o que é motivação? É um neurotransmissor? É uma região do cérebro? Na realidade não, motivação é uma propriedade emergente de outros fatores.</p>
<p>Por exemplo, pra neurotípicos, pode ser uma simples recompensa. Se eu te der 1000 reais, você trabalha essa semana. Se eu te der 2000 reais você trabalha duas semanas. Pra muitas pessoas, esse motivador externo é suficiente pra engajar numa atividade e completar até o objetivo que tem em mente, digamos, 4000 reais, então ela se motiva a trabalhar 4 semanas.</p>
<p>Mas eu disse que o nucleus accumbens e a dopamina de uma pessoa com TDAH estão desregulados. O mesmo tipo de recompensa externa que funcionou pra um neurotípico não ativa as mesmas propriedades no cérebro de uma pessoa com TDAH. De repente ela resolve, sei lá, pintar os cantos das páginas do caderno, cada seção com uma cor. Algo assim, que pra um neurotípico, não faz o menor sentido.</p>
<p>Qual programa de organização devo usar? Notion? Obsidian? Um neurotípico pega a primeira folha de papel, lista o que precisa e é isso. Uma pessoa com TDAH primeiro precisa ver se dá pra customizar, deixar mais colorido, mais chamativo, e ela pode gastar horas nisso, antes de conseguir começar a escrever o primeiro item da lista. E quando começar, talvez lembre que tinha outro programa que queria testar, e vai instalar e customizar o outro. 3 programas depois, ainda não escreveu a primeira linha da lista. Isso acontece, e não é por sacanagem, nem procrastinação, é porque o sistema de recompensas dessa pessoa funciona diferente.</p>
<p>Deixa eu voltar rapidamente à questão dos pais, incrédulos de que o filho tenha déficit de atenção, porque eles sabem que o menino consegue ficar em hiperfoco, 12 horas por dia, na frente do fortnite. Só pode ser procrastinação. Lógico, como disse antes, pode ser mesmo, mas considere a pessoa com TDAH. Games foram feitos pra esse tipo de pessoa, porque foram desenhados com o objetivo de demandar toda a atenção. Toda hora tem alguma coisa nova, não entedia, tudo chama atenção: pontuação, itens, armas novas, fases novas. São horas e horas de distração que seu cérebro fica viciado mesmo.</p>
<p>Mesmo jogos que se chamam de &ldquo;open world&rdquo;, com um sandbox pra você fazer &ldquo;o que quiser&rdquo;, na verdade não são &ldquo;open&rdquo; de verdade, como a vida real. Eles tem objetivos principais e sidequests, organizados em listas, com objetivos bem definidos e recompensas no final de cada item. Foi sem querer, mas esses tipos de jogos foram feitos sob medida pra cabeça de alguém com TDAH.</p>
<p>Eu não tenho dados pra suportar isso, mas tenho uma hipótese sobre cursos online. Pessoas com TDAH possivelmente consumam  mais pela mesma razão que se viciam em videogames: são gamificados. Tem listas de items bem definidos, tarefas e objetivos bem definidos, passo a passo bem definido, recompensas constantes com sensação de avanço. É como limpar a lista de sidequests de um jogo open world. Você fica viciado. Programas de organização, como um Notion, que permite infinitas customizações, mesma coisa. Eles chamam atenção, e viciam, pelos motivos errados.</p>
<p>Motivação não tem uma resposta única. Ela vai ser diferente pra cada pessoa. Você pode tentar entender os componentes fisiológicos, os neurotransmissores, as técnicas, mas pra variar, não tem receita mágica. Pergunte pra qualquer pessoa que você considere motivada. &ldquo;Por que trabalha com tanto afinco?&rdquo; E vai notar que nenhuma delas consegue dar uma resposta exata, vai fazer aquela cara de &ldquo;ahh, sei lá, eu só faço&rdquo;, se for honesto. Ou vai ter uma historinha empacotada, o que ela acha que você quer ouvir: &ldquo;ah, eu faço isso pra construir um mundo melhor&rdquo;.</p>
<p>Pra muitas pessoas, neurotípicas, costuma ter um componente lógico ou um desejo. Claro que eu quero trabalhar muito pra ficar rico pra me aposentar bem. Claro que eu quero estudar muito pra conseguir o melhor emprego na melhor empresa. Claro que eu quero ficar marombado, pra fazer mais sucesso na balada. Ou qualquer coisa assim. Mas de novo, pra neurodivergentes, o sistema lógico tem menos influência, e o sistema de recompensa está desregulado, então esse &ldquo;claro&rdquo;, não é tão claro assim.</p>
<p>O problema é que a pessoa já tem os problemas de inatenção, irritabilidade e impulsividade. Ela afasta ou pelo menos sente, que afasta as pessoas. Não consegue ir bem no trabalho. Tem dificuldades de criar relacionamentos duradouros, e ao mesmo tempo sente que não tem motivação, leu que isso se chama procrastinação. Seus resultados são ruins e isso causa vergonha, porque ela olha pros pais e amigos e vê olhos de decepção. Seu sistema de medo e ansiedade são desregulados, em breve ela chega a uma conclusão: eu não sou suficiente. Eu nunca vou ser suficiente. Eu sou um fracasso, uma decepção. É assim que pode surgir sensitividade a rejeição e até uma disforia, que pode levar à depressão, como ilustrei no começo.</p>
<p>Em situações de ansiedade, como ansiedade social, tipo ir numa festa ou evento, todo mundo tem inseguranças, será que tenho roupa pra ir? Será que vou falar alguma bobagem e as pessoas vão rir de mim? Será que vou conseguir falar com alguém? Existem 3 formas que as pessoas lidam com isso. A primeira, e mais óbvia, é que se esse medo for grande demais ela pode só Evitar, avoidance, e desistir de ir. Ou então se forçar a ir, mas quando chega a mente desliga, dá zone out, ela tá lá, mas não tá lá. É um cognitive mental check out.</p>
<p>Ou pode Suprimir, que é diferente do check out, você vai, mas vai brigando com você mesmo, tipo &ldquo;eu aguento, eu aguento&rdquo;, o sofrimento em silêncio, com a esperança de &ldquo;espero que ninguém perceba&rdquo;, o &ldquo;fake it until you make it&rdquo;, vou falsificar que tô gostando até uma hora conseguir gostar.</p>
<p>E finalmente, o ideal, que é enfrentar o medo, ir, processar, e aos poucos se adaptar, vendo que não era tão ruim quanto o medo inicialmente dizia que ia ser. Processar o medo como sendo irracional e compreender que não era o fim do mundo. Ninguém riu de você. Ninguém judiou de você, você tá bem. Pessoas neurotípicas fazem um pouco dessas três formas e tendem pra essa terceira opção, que é onde se amadurece. Todo mundo tem medo e ansiedade, mas aos poucos aprendemos a processar.</p>
<p>Mas pessoas com TDAH, exageradamente tomam o primeiro caminho: avoidance. Evitam a situação de ansiedade completamente, mas ao fazer isso ela está desistindo de coisas que são boas pra ela. Não só uma festinha. Deixa de explicar pro professor que passou mau e se podia fazer outra prova. Deixa de explicar pro namorado ou namorada que não tá bem. Deixa de pedir aumento no trabalho. E tudo isso retro-alimenta a sensação de fracasso, de insuficiência, e de novo, vira mais combustível pra sensitividade de rejeição e pra eventual depressão.</p>
<p>Motivação parece ser uma coisa que é só você ter um desejo muito forte, tipo quero ser rico ou quero muito ser famoso. E se a pessoa não tem motivação é porque ela não deseja forte o suficiente. Mas desejo e motivação são duas palavras diferentes por um motivo: elas não são iguais. Uma pessoa com ansiedade e TDAH como descrevi até agora, sem tratamento, tem chances de crescer sozinha, em total solidão ou pelo menos se sentindo solitária. Não tem nada que essa pessoa deseje mais do que ter uma companhia que não a faça se sentir insuficiente. Não tem nada que ela queira mais. Se fosse simples assim, ela já teria conseguido. Solidão não costuma ser uma opção, é uma consequência. E vocês já devem ter pelo menos ouvido falar o que solidão extrema pode causar.</p>
<p>Pra piorar, pessoas com TDAH parece que costumam desvalorizar seus sucessos e aumentar seus fracassos. Aumentar fracassos, achar que porque fez uma prova ruim, vai repetir o semestre, e por isso vai fracassar na faculdade, e por isso nunca vai conseguir um bom emprego, e por isso nunca vai ter uma boa carreira e por isso sua vida acabou, agora, já. É sua insula e sua amygdala hiperativa aumentando seus medos e ansiedades muito além da imaginação. Pra quem é neurotípico, pode parecer uma linha de pensamento absurda, mas pra um neurodivergente é absolutamente real.</p>
<p>Mas desvalorizar seus sucessos é mais sutil. É um mecanismo de defesa. O que eu falei do Evitar, avoidance. A pessoa desvaloriza o sucesso de agora porque se fracassar depois, na cabeça dela, vai ser menos dolorido. Se eu acertei hoje, mas fracassei amanhã, então eu sou um fake, uma fraude? É assim que pode nascer Síndrome do Impostor. Ou seja, o sistema dessa pessoa hiper-aumenta as consequências de um fracasso e minimiza os sucessos. Como você acha que ela vive?</p>
<p>Não estou querendo ser fatalista, e nem tratar esse assunto com desrespeito, mas é só pra explicar uma das formas de porque algumas pessoas chegam no pensamento de acabar com a própria vida. Pra neurotípicos não faz nenhum sentido. Mas se seu sistema de defesa está hiperativo, seu sistema de recompensa está desregulado, você não consegue tirar prazer das coisas porque seu sistema de recompensa tá desregulado. Sua irritabilidade e inatenção te afasta das pessoas, e seus resultados são cada vez piores, ao ponto onde, na sua cabeça, se sente totalmente incapaz e sozinho, e com a certeza que não existe solução. Não é difícil entender porque esse pensamento aparece.</p>
<p>E ao contrário do que filmes e ficção costumam mostrar, onde uma pessoa que decide desistir da vida faz toda uma preparação, escreve e envia cartas, inventa até jogos e tudo mais, faz parecer que nós conseguiríamos ver os tais &ldquo;sinais&rdquo; com antecedência e conseguir interceder. Mas não é bem verdade isso. Segundo pesquisas com sobreviventes, na maior parte dos casos, o que se sabe é que do momento que a pessoa tomou a decisão, o ato em si, costuma ser questão de poucos minutos, não dias. Lembrem-se de outra característica de TDAH: impulsividade. Impulsividade é justamente tomar uma ação por impulso, sem consideração pelas consequências.</p>
<p>Impulsividade é que leva uma pessoa com TDAH a tomar decisões impensadas, pedir demissão, desistir da escola, dirigir como doido  num momento de frustração. E falando tudo isso, se você se relacionou com as características de TDAH talvez esteja se sentindo aflito, ansioso, desesperançoso. Mas calma. Até aqui foi pra explicar, pra quem não sabia, como a cabeça de alguém com TDAH poderia funcionar, pra entender que não é uma escolha, é uma consequência. Agora temos que entender, por que é assim?</p>
<p>Não existe uma teoria exata que todos concordam, e até 1995 o consenso era que TDAH era como uma doença mesmo, um defeito. Mas na antropologia surgiu uma teoria interessante que pode ajudar a dar perspectiva. De novo, é uma teoria. Vamos lá. Muitos de nós, temos dificuldade de entender o conceito de tempo. A Revolução Industrial, que moldou o mundo moderno como conhecemos hoje, começou na Inglaterra no final do século 18. De lá pra cá se passou só uns 250, talvez 260 anos. Dois séculos e meio. Parece um puta tempo né?</p>
<p>Mas pensa, estamos no século XXI, implica que se passou mais de 2000 anos desde que começamos a contar esse calendário. Isso é  8 vezes mais tempo do que os dois séculos desde a revolução industrial, é tempo pra caramba. Mas no frigir dos ovos, ainda não é nada. Nós somos parte da espécie Homo Sapiens, a sequência dos Neandertais, e essa espécie surgiu mais ou menos 300 mil anos atrás. Parece muito tempo, mas não é tanto assim.</p>
<p>Os primeiros hominídios, o galho que não tem a ver com os macacos primatas, começou com os australopitecus, em alguma coisa como 4.2 milhões de anos atrás. Se tivéssemos um relógio de 12 horas pra representar os 4.2 milhões de anos, começassemos a evolução dos seres humanos a partir do meio dia, os 250 anos da história industrial moderna seria só os últimos dois segundos e meio antes da meia noite.</p>
<p>Entendem essa proporção? As duas grandes guerras mundiais, o nascimento e evolução do motor a combustão do primeiro automóvel até chegar nos Teslas e foguetes da SpaceX, o nascimento do transistor e sua evolução até a internet e inteligência artificial, tudo isso aconteceu nos dois últimos segundos desse relógio. Já nossa evolução biológica enquanto seres vivos, levou as quase 12 horas anteriores.</p>
<p>A revolução da agricultura, onde deixamos de ser só catadores de subsistência, começou uns 10 mil anos atrás. Por toda nossa história, nossos antepassados começaram a formar tribos, e papéis foram sendo definidos. E é aí que entra a teoria de &ldquo;caçadores catadores&rdquo;. A história dos caçadores ou catadores de subsistência entrando no mundo dos fazendeiros.</p>
<p>Pra resumir, imagine que na pré-história alguns dos papéis mais importantes eram do caçador ou catador. O fazendeiro é alguém até certo ponto satisfeito em viver uma vida regrada, acordar todo dia 5 da manhã, fazer o trabalho repetitivo do arado, colheita até a tarde, ir dormir e começar de novo a mesma coisa no dia seguinte. O caçador, é o oposto, não tem uma rotina bem definida, às vezes precisa ir longe no meio de uma floresta desconhecida, prestar atenção em tudo, caçar e evitar ser caçado. Tudo pode ser uma ameaça, um urso ou uma escorpião.</p>
<p>Podemos imaginar que um caçador precisa conseguir escanear o ambiente rapidamente, sem ficar focado numa coisa só, daí agir rapidamente, ser hiper-vigilante. Ser mais agressivo que o normal, por impulsividade, talvez seja a diferença entre a vida e a morte nas savanas da África da pré-história. Lembrem-se, era uma época de recursos escassos. Todo mundo tá acostumado com agricultura moderna, mas na pré-história não existia essa tecnologia. O normal da história humana por milhões de anos sempre foi pouca comida, muita doença, muitos perigos e baixa expectativa de vida.</p>
<p>É por isso que levou até as 11 horas, 57 minutos e 30 segundos do nosso relógio evolucionário pra conseguirmos chegar perto do primeiro 1 bilhão de habitantes na Terra. E foi só por causa das tecnologias modernas, que começaram depois da revolução industrial, que fomos de uma só vez de 1 bilhão pra 8 bilhões de pessoas. Em dois segundos. É claro que isso ia ter consequências.</p>
<p>Mais uma vez de forma simplista, a revolução industrial foi uma enorme revolução de eficiência. Levou o homem do campo pras cidades, da agricultura pra indústria. E precisamos treinar essas pessoas. Daí nasce o sistema educacional como conhecemos hoje: salas de aula com vários alunos presos em cadeiras o dia inteiro. 30 ou 40 crianças ouvindo um professor chato o dia inteiro. E depois uma carreira de trabalho de 9 às 5 fazendo trabalho repetitivo, sentados em cadeiras o dia inteiro. Seja numa fábrica ou seja num escritório do mundo moderno. É um mundo ótimo pra quem gosta de rotina.</p>
<p>Entenderam: a sociedade moderna foi feita à imagem dos fazendeiros. Trouxemos os caçadores juntos e fizemos eles ficarem sentados o dia inteiro, por anos e anos. Tentamos encaixar uma peça redonda num buraco quadrado. O mundo moderno foi feito pra pessoas que conseguem aguentar a rotina de ficar fazendo poucas atividades, sentados o dia inteiro, em horário fixo. O exato oposto do que é um caçador.</p>
<p>Pesquisas sugerem que TDAH é de fato passado geneticamente. Tem papers com pesquisas que acompanharam a vida de mais de 50 mil gêmeos ao longo de décadas pra entender. Existe um fator genético e existia uma utilidade evolucionária. Não é um problema aleatório que aparece do nada na cabeça das pessoas, é uma pré-disposição. Mas como falei antes, não é uma chave de liga e desliga. O entendimento é que esse fator é uma vulnerabilidade em neurodivergentes que pode ou não ser ativada, em diferentes graus, dependendo do ambiente onde cada um cresceu.</p>
<p>Em pesquisas com alunos em sala de aula, também já foi identificado que 30 alunos com 1 professor tem o dobro de diagnósticos de TDAH do que numa sala com os mesmos 30 alunos mas 2 professores disponíveis. Ter um professor a mais que consiga dar mais atenção e redirecionar a atenção do aluno da forma correta, ajuda a criança a manifestar menos ou até não sofrer os problemas de TDAH. O ambiente é um enorme fator pra severidade da condição. Só ter a pré-disposição genética não garante que você vai necessariamente sofrer com isso.</p>
<p>Também se acredita que adultos diagnosticados tardiamente com TDAH na verdade já tinham isso desde a infância, mas infelizmente, nunca foram tratados quando deveriam. Seja por ignorância de pais e escolas, seja por preconceito contra medicamentos ou tratamentos. E na vida adulta a condição pode levar a outros problemas como desordem de ansiedade e, nos piores casos, depressão.</p>
<p>E mesmo quando há diagnóstico, ainda existe essa idéia de chave de liga e desliga. Ah, é símples, dá Aderol ou Ritalina e tá resolvido. Mas vou repetir porque é importante: a condição não é um único componente de liga e desliga igual pra todo mundo. É uma desregulação de diversos componentes e diversos neurotransmissores. Estimulantes não agem só numa região isolada, ela tem efeitos colaterais, que variam de pessoa pra pessoa. Cada pessoa tem uma combinação única de fatores que dão espectros de severidade diferentes pra cada sintoma de TDAH.</p>
<p>Algumas pessoas tem déficit de atenção mas não necessariamente hiperatividade. Outras podem ter dislexia somado. Em graus diferentes. É uma matriz multidimensional de combinações, e não uma única variável binária. Por isso toda hora estou falando que nada substitui o tratamento: porque só um profissional licenciado vai conseguir identificar as variáveis e como tratar no seu caso específico.</p>
<p>A medicação em si é super estigmatizada, tem aquela imagem que a pessoa vai virar um junkie viciado ou um zumbi. E sim, se um neurotípico se automedicar com uma ritalina porque assistiu video de tiktok falando que isso dá boost de produtividade, vai ter problemas. Uma pessoa neurodivergente que precisa de ritalina é parecido com uma pessoa com diabetes que precisa de insulina. Você pode chamar uma pessoa diabética de junkie em insulina?</p>
<p>Tecnicamente até sim, mas a diferença pra você, neurotípico, é que seu corpo fabrica insulina, mas algumas pessoas precisam de insulina externa, porque a desordem é justamente não conseguir fabricar sozinho. É pra casos assim que medicamentos são necessários. Não só isso, nosso corpo pode se tornar resistente a certos medicamentos. Precisa modificar dosagem, trocar de medicamento e tratamento. E o único que vai saber disso é um psiquiatra licenciado.</p>
<p>Psiquiatras não são médicos de loucos. São médicos, que aprenderam da mesma medicina que todos os outros clínicos gerais e tem especialização na neurologia e efeitos dos químicos que eu simplifiquei hoje. Um video de YouTube nunca vai substituir anos de experiência nessa área. De novo, se você se relacionou com algumas das condições que tentei explicar, procure um psiquiatra.</p>
<p>E não só um psiquiatra. Muito do que uma pessoa de TDAH sofre pode ser contornado com técnicas holísticas. Primeiro tem a medicação, especialmente em casos graves e urgentes. Mas se for dignosticado cedo, o ideal é CBT que é terapia cognitiva comportamental, que é como uma re-educação pra aprender como seu sistema funciona e técnicas de como compensar. Todo mundo recebeu uma educação de neurotípicos, mas claro que não funciona pra todo mundo. Existe uma educação pra neurodivergentes, que é a terapia.</p>
<p>Por exemplo, pra casos mais leves do espectro, coisas como sistemas de alarmes, notificações de calendário, listas de coisas a fazer bem escritas, com itens que tem ações específicas e não abstratas tipo &ldquo;estudar mais&rdquo; e sim &ldquo;estudar o capitulo 5 do livro X&rdquo;, sabe? Sidequests de jogos com objetivos bem definidos? São exemplos de mecanismos externos, que bem aplicados, compensam nossa inatenção, por exemplo.</p>
<p>Se sair procurando, vai achar uma tonelada de videos de lifehacks que pessoas com TDAH podem tentar, mas cada video vai mostrar o que funcionou praquele criador de conteúdo, no espectro dele. Nunca vai ser uma regra geral. E sair na tentativa e erro, com toneladas de video, sem o entendimento correto disso, em vez de ajudar, pode piorar sua ansiedade, amplificar o sentimento de fracasso. Por isso insisto, procure terapia de um profissional qualificado.</p>
<p>Agora, não são só más notícias pro pessoal de TDAH. Pra mim a teoria antropológica faz muito sentido: estamos forçando caçadores a sentar na cadeira de uma escola ou escritório, num horário fixo, e ficando putos porque eles não conseguem ser produtivos. Mas é lógico, é uma estrutura feita por fazendeiros. Por acaso, esse perfil é o que foi considerado &ldquo;neurotípico&rdquo;. Na pré-história, talvez o &ldquo;típico&rdquo; tenha sido ter TDAH. Eu mesmo tenho dificuldades nessa estrutura, sempre tive.</p>
<p>Porém, as qualidades de caçadores começaram a voltar a fazer sentido no mundo moderno. Empreendedorismo é uma atividade não estruturada onde pessoas com TDAH parecem ter algumas vantagens. Não é de hoje que tem videos e mais videos associando um Elon Musk a TDAH. A maioria das pessoas neurotípicas tem como objetivo na cabeça uma rotina de 9 às 5, sentado num escritório, com trabalho estável, sem dor de cabeça. Já um Elon, o que ele faz? Carro elétrico? Hm, não, entendiou. Lançar foguete então? Hm, não, entendiou também, zoar no Twitter. É isso. Pra muitos, Elon é o ápice do TDAH.</p>
<p>Várias áreas criativas costumam abrigar a impulsividade, a necessidade de coisas novas, a atenção em detalhes de TDAH. Seja literatura, música, artes, cinema, moda e muito mais. Veja vendas ou marketing, ou criação de conteúdo, ou streaming, que são áreas que precisam da hiperatividade, alta energia e habilidades sociais de pessoas com TDAH.</p>
<p>Muitos não pensariam nisso de primeira, mas uma pessoa de TDAH não procrastina, ela sofre tudo que expliquei até agora. Mas justamente uma das muitas técnicas que usam em sua organização é tentar quebrar as coisas em tarefas menores e mais urgentes. Em vez de ter um grande trabalho pra entregar no final do semestre, se os professores forem atentos, é melhor dar pequenos trabalhos com mais frequência.</p>
<p>O senso de urgência é um grande motivador pra pessoas com TDAH e pelo mesmo motivo, parece que elas conseguem se dar bem em trabalhos de emergência, emergência de hospital, por exemplo. Pessoas com TDAH parecem se dar bem em ambientes que parecem caóticos, com ritmo acelerado, de alta pressão e urgência. Se o ambiente exige tomadas de decisão muito rápidas, como por impulso, com hiperatividade, parece combinar.</p>
<p>E finalmente, tecnologia. Programação, design gráfico, edição de video, tarefas que exigem atenção a detalhes, a tal habilidade pra hiperfoco. Já falei que não tem como controlar nem garantir o hiperfoco, mas ela costuma vir em atividades que excitam mais o centro de recompensa do cérebro dessas pessoas. Se justamente forem atividades de tecnologia, isso pode ajudar muito. Não é de hoje que vejo muita gente que me acompanha e tem algum espectro de TDAH. A gente brinca que precisa ser meio doido pra ser programador, e acho que essa observação não tá muito errada.</p>
<p>Existem milhões de pequenas dicas e lifehacks que eu poderia explorar aqui, mas não é esse o ponto. Primeiro porque já existem centenas de videos desse tipo. Mais importante, porque o foco não é tentar consertar seu problema neste video. Isso eu não consigo fazer. Nem eu, nem nenhum youtuber. Só um especialista, durante várias sessões, ao longo de vários meses, num tratamento feito sob medida pro seu espectro em particular.</p>
<p>O objetivo deste video é awareness. Muitos neurotípicos não entendem que estão cercados de neurodivergentes, e só categorizam a pessoa como &ldquo;esquisita&rdquo; e ficam longe ou zoam gratuitamente, o que não ajuda. Eu sou neurodivergente e não sabia disso e certamente tenho dificuldades de identificar outros neurodivergentes porque tenho espectro autista, minha dificuldade é justamente em comunicação.</p>
<p>Aí vocês devem estar coçando a cabeça. &ldquo;Como assim comunicação? Você não é a porra de um YouTuber??&rdquo; Entenda, e isso é o que todo mundo que me critica não entende: eu leio um script e meus videos são editados. Já viram os bloopers no final do video? Eu não falo sem errar e sem parar. Eu deixo os bloopers justamente pra todos verem que eu erro como qualquer outro.</p>
<p>Já me falaram que tenho tom arrogante de sabe tudo. Eu tenho mesmo muita dificuldade de entender isso. Não sei se posso atribuir isso totalmente ao autismo, mas tenho muita dificuldade quando estou dando uma resposta que na minha cabeça é clara e objetiva e as pessoas me falam que eu fui mau educado ou algo assim.</p>
<p>Juro, não é brincadeira, na minha cabeça eu estou me esforçando pra dar a melhor resposta que consigo, da forma mais curta, justamente porque parece ruim fazer as pessoas perderem tempo  me ouvindo falar coisas desnecessárias. Então edito os scripts pra ser o mais eficiente possível. É como eu prefiro. Mas depois me dizem que soou arrogante. Eu até acho que entendo, mas não compreendo, literalmente não computa na minha cabeça.</p>
<p>Eu entendia que tinha problemas de comunicação, mesmo antes de ser diagnosticado. Entenda, eu não sabia nada disso até pouco tempo atrás. Se procurar online por características do nível 1 de espectro autista vai encontrar coisas como: dificuldade com interações sociais. Em particular a dificuldade de entender sinais, expressões. A gente não nota muito a pessoa com quem tá falando. Eu sempre sou o último a entender piadas ou sarcasmo, normalmente eu vejo as outras pessoas rindo, e eu rio junto depois, sem entender bem porque.</p>
<p>Muitos de nós literalmente somos incapazes de falar olhando nos olhos da outra pessoa. Eu realmente não gosto, mas aprendi a olhar um pouco pro lado dos olhos, pra parecer que estou prestando atenção. Eu fui aprendendo a me comportar como as pessoas parece que esperam que eu me comporte. Mas isso é muito exaustivo. Por isso, no geral, prefiro atividades que posso fazer sozinho. Quanto menos precisar falar com outras pessoas, pra mim é melhor. Gosto mais de ambientes onde no máximo preciso falar online, é sempre mais confortável.</p>
<p>Segundo ponto é comportamento repetitivo. Preferência por rotina e rituais. É exatamente como eu gosto das coisas. Não tira nada do lugar que eu coloquei. Não me chama pra fazer nada em cima da hora. Tudo que altera minha rotina ou meu planejamento, me incomoda profundamente, eu tenho que me segurar muito pra não explodir mesmo em coisas triviais que eu não planejei antes.</p>
<p>Além disso eu tenho interesses muito estreitos. Eu me distraio, como pessoas com TDAH. Se você me acompanha no instagram, deve ter notado que meus interesses são bem limitados a poucos tópicos que vou super a fundo. É como eu funciono. Dizem que pessoas com autismo tem força cognitiva, tipo memória acima da média ou atenção a detalhes, ou até memória fotográfica como um Sheldon da vida. Eu nunca medi, mas por experiência, acho que pode ser o caso. Mas, eu esqueço muito fácil coisas do dia a dia, eu tinha dificuldade até de lembrar data de aniversário dos meus pais. Mas me pergunte fatos sobre a história da computação e eu te digo nomes e datas. Não sei bem como isso funciona ainda.</p>
<p>É como escrever um script como esse, eu fiz a pesquisa, tenho minhas experiências, tenho o assunto inteiro tudo de uma só vez na minha cabeça enquanto estou escrevendo. Quando eu chamo de &ldquo;dump&rdquo;, não é um exagero, é um dump mesmo, eu derrubo da memória pro teclado de uma só vez. E tenho atenção a detalhes. De bater o olho, vejo um errinho ortográfico e isso me irrita e eu preciso consertar. É assim que escrevo 50 páginas em um dia, em hiperfoco.</p>
<p>Daí tem alguns pontos que eu não me identifico tanto como sensitividade sensorial, tipo hiper sensitividade a cheiros, luzes, toques ou coisas assim. Sons eu tenho, se estou tentando me concentrar, barulhos me irritam profundamente. Quem assiste meus bloopers já viu isso. Também falam de ansiedade, mas comparado a uma pessoa com TDAH, minha ansiedade não chega nem aos pés. Até hoje, situações sociais me deixam ansioso, mas eu aprendi a suprimir. Eu subo num palco e dou uma palestra. Não gosto, por isso evito quando posso. Tem outras nuances como personas e máscaras que são mecanismos que eu aprendi a usar sozinho.</p>
<p>Isso pode soar estranho, porque todo mundo conhece a persona Akita do canal Akitando, youtuber, palestrante, ex-organizador de eventos grandes. E como assim eu tenho problemas de comportamento social e comunicação? Exatamente porque essa persona impede que vocês vejam esses problemas. Por anos, via tentativa e erro, aprendi meio a compensar, mas mesmo assim fica meio Sheldon, às vezes. Pra muitos assistindo, meu transtorno pode me fazer parecer uma pessoa arrogante. Como sou um youtuber, parece normal.</p>
<p>Claro, acho que a maioria consegue entender que não estou tentando agredir ninguém de propósito nem nada disso. E nem eu consigo dizer quanto que a forma como vocês de fora me enxergam tem a ver ou não com os efeitos do meu autismo, ou só por eu ser youtuber mesmo. Como eu nunca tratei e só descobri agora, ao longo dos anos fui criando mecanismos pra me defender e navegar o mundo. Eu sempre achei que era estranho, mas nunca parei muito pra refletir sobre isso, até muito pouco tempo atrás.</p>
<p>Pra piorar, desde quando eu era criança, sempre entendi que era tratado de forma diferente. Bullying por exemplo. Mas na época assumia que era por ser asiático. &ldquo;Ah sim, japoneses naturalmente são zoados&rdquo;. Normalmente eram só uns 2 ou 3 na sala de aula como eu, no máximo. Desde criança eu me achava diferente, mas achava que tinha a ver mais com isso, e nunca passou pela minha cabeça, mesmo depois de adulto, que poderia ser algo como espectro autista.</p>
<p>Mas é aquela sensação de &ldquo;ahhhhh, agora tudo faz sentido&rdquo;. Inclusive já teve pessoas que trabalhavam comigo que me zoavam me chamando de autista, porque quando eu tava no meu hiperfoco, literalmente desligava do mundo, e quando conversava era meio mão única, tinha o lance que eu não entendia sarcasmo ou piadas, então devia ser meio esquisito falar comigo. Eu tendo a falar e interromper mais do que ouvir quando é um assunto que me interessa.</p>
<p>Também me entedio muito fácil da conversa, já quero ir direto pra solução, sem tanta cerimônia. Eu até tenho empatia, mas a história dos outros não costuma conseguir segurar meu interesse muito tempo, eu me distraio e começo a pensar outras coisas, tenho aquele olhar que parece que tô olhando pra você, mas na verdade estou olhando 1km atrás de você. E uma hora depois, se não foi assunto do meu interesse, já esqueci do que falamos.</p>
<p>De qualquer forma, eu não tive muito interesse em estudar o autismo ainda, me foquei no TDAH por motivos pessoais e achei que valia a pena compartilhar. Eu vi em primeira mão os efeitos que depressão pode causar numa pessoa sem suporte e sem tratamento. Mais importante: numa pessoa que acredita que essa condição é fixa, imutável e a única certeza é sofrimento eterno. Eu entendi que um grande passo pra melhorar é justamente &ldquo;entender&rdquo; que o transtorno existe e os mecanismos por trás disso. Torna o problema muito mais palpável, menos abstrato, que começa a dar a noção de que é gerenciável.</p>
<p>Num mar de falsos gurus vendendo soluções mágica no Tiktok, isso só aumenta a ansiedade e frustração e piora a depressão. Cada oferta de solução mágica que não funciona, amplifica a sensação de que a culpa é da pessoa e que nada nunca funciona só pra ela. A culpa não é sua.</p>
<p>Falando em tiktokers, uma dúvida que paira no ar, e que é controversa, é que muitos assistindo devem ter notado que nos anos recentes aumentou bastante o aparecimento de casos de TDAH. Pode ser, ou porque o acesso a diagnósticos melhorou, ou porque de fato aumentou o número de pessoas sofrendo de algum espectro de TDAH. E acho que é um pouco das duas coisas.</p>
<p>Boomers e pessoas mais velhas que eu tem dificuldade de aceitar que transtornos psicológicos existem. Pra muitos parece só drama, mimimi, e gente tentando chamar atenção. Não ajuda que online, tem mesmo muito disso. Por isso os que sofrem de verdade não querem parecer que são esse tipo de pessoa, e sentem vergonha. A maioria das pessoas aprendeu a navegar como deu, evitando situações e, por consequência, limitando sua vida, ou suprimindo, e tendo todo tipo de efeitos colaterais, estresse, depressão, aumento de auto-medicação, álcool e drogas e por aí vai.</p>
<p>Desordens neurológicas são reais, se manifestam fisiologicamente, não é só fruto da imaginação. Também não se pode achar que todo mundo é uma vítima. Pessoas que de verdade enfrentam essas condições são as primeiras que não gostam de se vitimizar e que tenham dó delas. Elas não se expõe. Só querem conseguir fazer as coisas. O problema são os aproveitadores, que se usam dos transtornos dos outros pra aparecer. Não estão ajudando nada.</p>
<p>E tratamento não é camisa de força, hospício, lobotomia nem nada disso. O problema é o estigma. A vergonha de alguém descobrir que você decidiu se tratar, a ansiedade de ficar pensando que alguém vai achar que você é louco e coisas assim. Felizmente, estamos em 2023 e não em 1990. Mas mesmo na minha área, de tecnologia, a maioria de nós ainda não sabe como lidar com isso. Muitos de nós já nos adaptamos de alguma forma, provavelmente não da melhor, mas sabemos das dificuldades, então pelo menos podemos tentar aumentar o interesse em aprender mais sobre o assunto.</p>
<p>Deixando os problemas de lado, a neurologia, psicologia evolucionária e tudo mais são assuntos fascinantes por si só. Você é criador de conteúdo, ou de marketing, ou de UX, ou design, ou economia, qualquer profissão que tenta influenciar o comportamento das pessoas. Mas as pessoas não são nem totalmente lógicas e nem totalmente idiotas pra ficar caindo em truques baratos. Entender essas diferenças ajuda a produzir conteúdos melhor direcionados e melhora os negócios. É sempre ganha-ganha.</p>
<p>Um exemplo pequeno, home office, pra muitas pessoas com TDAH pode ter sido bom, porque como falei, os momentos de hiperfoco não são controláveis. Não dá pra forçar acontecer no horário comercial. E se vier às 9 da noite? Você vai se vestir, pegar o metrô, ir até o escritório? Aí chega lá e a urgência passou. Home office pelo menos dá a oportunidade da pessoa conseguir ser mais produtiva. Tem prós e contras, como sempre, mas novas opções começaram a aparecer em alguns lugares.</p>
<p>Outro exemplo, ouvi falar que algumas poucas escolas começaram a entender o diagnóstico de TDAH e oferecem opção desse aluno ter mais tempo pra fazer uma prova, ou fazer numa sala separada. À primeira vista pode parecer injusto. Mas você não tá entendendo. Ela não controla a ansiedade, ainda. Some a isso outras desordens, como dislexia. A prova em si já deixa ela mais ansiosa que todo mundo e com dislexia significa que vai ver todo o enunciado embaralhado. Ela é totalmente capaz de fazer a conta, mas pra isso precisa conseguir enxergar os números e letras nas posições corretas primeiro. A escola é justamente o lugar onde ela deveria ter a oportunidade de aprender a calibrar essas coisas.</p>
<p>Ainda não tem pesquisas o suficiente, tem muito ajuste em muitos processos que precisam acontecer, e nada disso vai acontecer da noite pro dia. Enquanto isso, nós neurodivergentes, precisamos aprender a navegar no mundo dos neurotípicos, aprendendo os mecanismos pra aliviar a maior quantidade de obstáculos quanto possível. É muito difícil fazer isso sozinho, e é por isso que a mensagem desse video é: se for o caso, aceite que a desordem existe, aceite que tem trazido prejuízos demais, e aceite que não é sua culpa. Mais importante: aceite que existem formas de melhorar drasticamente sua qualidade de vida.</p>
<p>Não vai ser com óleo de cobra, não vai ser ervas nem óleos essenciais, nem lifehacks engraçadinhos, vai ser com os medicamente corretos, com a terapia correta, com um bom especialista. É como quem quebrou feio uma perna e precisa ir num fisioterapeuta reaprender a andar. Não é uma perda de tempo, é uma economia de tempo, no futuro.</p>
<p>Eu não sou o melhor exemplo, mas pelo menos vejam, um neurodivergente pode ter uma carreira produtiva e sucesso. Isso não é um acidente. É aprender o seu espectro e criar estratégias que funcionam pra você. Durante o video todo eu disse que o certo, se você se relacionou com os sintomas que descrevi no video, é ir procurar um especialista. Mas digamos que você queira mais informações. Aqui mesmo no YouTube andei vendo alguns canais que podem ajudar a dar mais informações, caso tenha preguiça de ir ler os papers.</p>
<p>O primeiro é o canal da Dra. Tracey Marks, que é psiquiatra, e faz videos muito bem explicativos e informativos, que podem ajudar a dar nome pra coisas. Vale a pena dar uma olhada. Alguns exemplos que mencionei hoje, vi em videos dela. Outro canal que eu gostei, com ressalvas, é do Dr. K, do canal HealthyGamer. Ele sempre dá vários disclaimers, e suas explicações são muito boas, bem embasadas em pesquisa e exemplos clínicos. Achei interessante um quadro que ele faz conversas com streamers do Twitch, alguns famosos como Pokimane, que se abrem pra discutir seus problemas.</p>
<p>A ressalva é porque ele vende coaching online e fala muito de medicina indiana, que não é exatamente consenso. Mesmo assim, as explicações são sólidas e também peguei emprestado alguns exemplo. Mas tem canais que eu não gostei, como o AspieWorld. Esse é o caso de um não-profissional que fala da condição específica dele. Até tem boas informações, mas acho que o estilo dele de quantidade em vez de qualidade, encheu o canal de muita bullshit, achei mais perda de tempo do que vale a pena. E é uma pena porque vejo toneladas de canais falando de TDAH mas esse é um dos poucos de autismo.</p>
<p>Normalmente eu digo que se ficaram com dúvidas mandem nos comentários abaixo, mas cuidado, se ficaram com dúvidas sobre sua própria condição, consulte um especialista, mas se for como eu e ficou fascinado com o assunto, não deixe de pesquisar. Tem diversos psiquiatras e pesquisadores de neurologia que publicam não só papers, mas videos também pra resumir. 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>psicologia</category><category>pessoal</category><category>akitando</category></item><item><title>[Akitando #140] Desbloqueando o "Algoritmo" do Twitter - Introdução a Grafos</title><link>https://akitaonrails.github.io/2023/04/11/akitando-140-desbloqueando-o-algoritmo-do-twitter-introducao-a-grafos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/04/11/akitando-140-desbloqueando-o-algoritmo-do-twitter-introducao-a-grafos/</guid><pubDate>Tue, 11 Apr 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/uIflMYQnp8A"
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;O Twitter liberou parte do código que chamou de &amp;ldquo;The Algorithm&amp;rdquo;, que forma o sistema que popula a aba de recomendações &amp;ldquo;Para Você&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Vamos entender qual é o contexto de ciência da computação e história necessários pra começar a entender esse código todo. E uma pequena introdução à ciência por trás de recomendações e grafos em geral.&lt;/p&gt;
&lt;p&gt;Finalmente, vou poder mostrar na prática a diferença de &amp;ldquo;código de curso&amp;rdquo; e &amp;ldquo;código de verdade&amp;rdquo; e exatamente porque software nunca acaba.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/uIflMYQnp8A"
    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>O Twitter liberou parte do código que chamou de &ldquo;The Algorithm&rdquo;, que forma o sistema que popula a aba de recomendações &ldquo;Para Você&rdquo;.</p>
<p>Vamos entender qual é o contexto de ciência da computação e história necessários pra começar a entender esse código todo. E uma pequena introdução à ciência por trás de recomendações e grafos em geral.</p>
<p>Finalmente, vou poder mostrar na prática a diferença de &ldquo;código de curso&rdquo; e &ldquo;código de verdade&rdquo; e exatamente porque software nunca acaba.</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:57 - CAP 1 - Avisos e Descrevendo o Repositório</li>
<li>00:02:51 - CAP 2 - Interpretando Errado: Não Acredite em Blog Posts</li>
<li>00:07:14 - CAP 3 - Derrubando o Meme: Pessoas Idiotas</li>
<li>00:11:30 - CAP 4 - Objetivos do Video: derrubar os Clones. Código de Verdade</li>
<li>00:16:37 - CAP 5 - Entendendo Grafos. Introdução a Relevância</li>
<li>00:21:47 - CAP 6 - Primeira Geração de Relevância: WTF</li>
<li>00:27:49 - CAP 7 - Relevância nos Primórdios da Web: PageRank e HITS</li>
<li>00:35:43 - CAP 8 - Segunda Geração de Relevância: RealGraph e SALSA</li>
<li>00:39:22 - CAP 9 - Escalando com Hadoop e MapReduce: HDFS e Pig</li>
<li>00:43:15 - CAP 10 - Algoritmos Escaláveis de Grafos: Relembrando Mergesort</li>
<li>00:46:58 - CAP 11 - Terceira Geração de Relevância: GraphJet</li>
<li>00:50:36 - CAP 12 - Earlybird: Light Ranker. Apache Lucene</li>
<li>00:51:37 - CAP 13 - Ids para Sistemas Distribuídos: Snowflake</li>
<li>00:55:23 - CAP 14 - Serialização Eficiente sem JSON: Thrift e Protobufs</li>
<li>01:01:07 - CAP 15 - A Controvérsia do &ldquo;Rails Não Escala&rdquo;. Por que Scala?</li>
<li>01:07:37 - CAP 16 - Introdução a Similarity Clusters: Similaridade de Cosseno</li>
<li>01:16:08 - CAP 17 - Resumindo os demais Projetos: Aplicando o que Aprendemos</li>
<li>01:20:22 - CAP 18 - Qualidade do Código: Mini-Assessment</li>
<li>01:22:43 - CAP 19 - Sendo Educado na Web: Memes Idiotas</li>
<li>01:24:32 - CAP 20 - Refletindo sobre o Código: Lições a Aprender</li>
<li>01:27: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>Fio no Twitter (<a href="https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20</a>)</li>
<li>Twitter: The Algorithm (<a href="https://github.com/twitter/the-algorithm"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm</a>)</li>
<li>Twitter: The Algorithm ML (<a href="https://github.com/twitter/the-algorithm-ml"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm-ml</a>)</li>
<li>Twitter&rsquo;s Recommendation Algorithm (<a href="https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm</a>)</li>
<li>Steven Tey: # How the Twitter Algorithm works in 2023 (<a href="https://steventey.com/blog/twitter-algorithm"target="_blank" rel="noopener">https://steventey.com/blog/twitter-algorithm</a>)</li>
<li>Aakash Gupta: The Real Twitter Files: The Algorithm (<a href="https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm"target="_blank" rel="noopener">https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm</a>)</li>
<li>Earlybird: Real-Time Search at Twitter (<a href="https://stephenholiday.com/notes/earlybird/"target="_blank" rel="noopener">https://stephenholiday.com/notes/earlybird/</a>)</li>
<li>Twitter Search is Now 3x Faster (<a href="https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster</a>)</li>
<li>Paper: GraphJet: Real-Time Content Recommendations at Twitter (<a href="https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf"target="_blank" rel="noopener">https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf</a>)</li>
<li>Paper: RealGraph: User Interaction Prediction at Twitter (<a href="https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf"target="_blank" rel="noopener">https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf</a>)</li>
<li>Paper: Real-Time Twitter Recommendation: Online Motif Detection in Large Dynamic Graphs (<a href="http://www.vldb.org/pvldb/vol7/p1379-lin.pdf"target="_blank" rel="noopener">http://www.vldb.org/pvldb/vol7/p1379-lin.pdf</a>)</li>
<li>Paper: Discovering Similar Users on Twitter (<a href="http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf"target="_blank" rel="noopener">http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf</a>)</li>
<li>GitHub: FlockDB (<a href="https://github.com/twitter-archive/flockdb"target="_blank" rel="noopener">https://github.com/twitter-archive/flockdb</a>)</li>
<li>GitHub: Cassovary (<a href="https://github.com/twitter/cassovary"target="_blank" rel="noopener">https://github.com/twitter/cassovary</a>)</li>
<li>GitHub: GraphJet (<a href="https://github.com/twitter/GraphJet"target="_blank" rel="noopener">https://github.com/twitter/GraphJet</a>)</li>
<li>Twitter: Finagle (<a href="https://twitter.github.io/finagle/"target="_blank" rel="noopener">https://twitter.github.io/finagle/</a>)</li>
<li>Apache Avro (<a href="https://avro.apache.org/"target="_blank" rel="noopener">https://avro.apache.org/</a>)</li>
<li>AkitaOnRails: Chatting with Blaine Cook (Twitter) (<a href="https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter"target="_blank" rel="noopener">https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter</a>)</li>
<li>Fireship: Vector databases are so hot right now. WTF are they? (<a href="https://www.youtube.com/watch?v=klTvEwg3oJ4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=klTvEwg3oJ4</a>)</li>
<li>Slideshare: Restrição == Inovação (<a href="https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos"target="_blank" rel="noopener">https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos</a>)</li>
<li>Elastic: Vector Search (<a href="https://www.elastic.co/what-is/vector-search"target="_blank" rel="noopener">https://www.elastic.co/what-is/vector-search</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>Já não é notícia nova, a esta altura acho que todo mundo já viu ou ouviu alguém comentando sobre o código dos algoritmos de recomendação do Twitter, que tio Elon Musk liberou, parcialmente, no último dia de março de 2023. No momento em que estava escrevendo o script deste episódio não se passou uma semana ainda. E até a gravação deste video ainda não foi liberada a segunda parte do código. Mas vamos ver o que temos até agora.</p>
<p>(&hellip;)</p>
<p>No mesmo dia que a primeira parte do código foi liberado, eu e diversos outros programadores e pesquisadores começamos a avaliar o código e surgiu muita coisa interessante. Eu mesmo fui twittando em tempo real o que achei de primeiras impressões. Meu foco foi mais na parte técnica e arquitetura. Os links pros meus tweets originais estão na descrição abaixo e neste video vou elaborar em mais detalhes o que não coube em pequenos tweets.</p>
<p>Antes de começar, preciso deixar alguns avisos muito claros aqui. Este código é incompleto. Ainda falta pelo menos uma segunda parte. E mesmo com essa parte, ainda não vamos ter certeza que tudo vai ser liberado. O que o Twitter soltou não é mais que uma pequena fração de todo o código necessário pra se fazer um verdadeiro clone de Twitter.</p>
<p>Esse código só cobre uma parte dos sistemas que devolvem os tweets recomendados que você vê na aba de &ldquo;Para você&rdquo;. Em termos coloquiais chamamos isso tudo de &ldquo;Algoritmo do Twitter&rdquo;, mas não é um único algoritmo: é uma coleção de dezenas de algoritmos e heurísticas que, funcionando juntos, geram recomendações que tem relevância pros seus interesses.</p>
<p>Mesmo sendo incompleto, estamos falando de mais de 4 mil e seissentos arquivos. Mais de 330 mil linhas de códigos, sendo mais da metade código feito na linguagem Scala, e quase 30% em Java. A próxima linguagem mais relevante depois dessas duas é Rust, na nova parte de machine learning chamada Navi, mas mesmo assim é só 2% do total desse código.</p>
<p>Mais importante, o sistema de build que eles utilizam é o Bazel. Pense NPM se você for de Javascript, ou algo parecido com Gradle se for de Java mesmo. Eu mesmo nunca vi Bazel sendo utilizado num projeto de verdade e cada sub-projeto tem um arquivo de BUILD em Bazel, mas eles omitiram os arquivos WORKSPACE, que são necessários pra conseguirmos realmente compilar e buildar cada projeto.</p>
<p>É possível reconstruir do zero, via tentativa e erro, mas é um trabalhinho bem chato que eu não tenho interesse em fazer e, até o momento, não vi ninguém se prontificando a fazer. Portanto tudo que eu, ou qualquer outro analisar, é baseado unicamente em análise estática do código, que é um jeito mais bonito de dizer que só temos como ler o código ou fazer compilações parciais. Por isso não acredite em tudo que ler em somente um pedaço do código. Deixa eu dar um exemplo.</p>
<p>Alguns pesquisadores foram mais interessados na parte das regras de negócio: os critérios que afetam o alcance de tweets. Em resumo, não teve nenhuma grande novidade bombástica além do que todo mundo mais ou menos já sabia só por observação. Vamos ver dois exemplos, o blog post do desenvolvedor Steven, da Vercel, e o blog post do Aakash Gupta. Ambos os links, novamente, estão na descrição abaixo.</p>
<p>No post do Steven, você vai ler que o algoritmo de ranqueamento vai determinar pesos em algumas ações em cima do seu tweet. Por exemplo, um like tem peso 0.5, um retweet é mais importante, tem peso 1, se alguém clicar no tweet, der reply ou like ou ficar mais que 2 minutos lendo, vai ter peso 11 e assim por diante. Mas, de onde ele tirou essa informação?</p>
<p>Ele coloca o link pro README no diretório projects/home/recap dentro do repositório the-algorithm-ml, que é o repositório menor de machine learning. A contagem de Scala e Java que falei acima foi no repositório principal the-algorithm, que é o maior; mas tem esse outro menor que é basicamente um projetinho Python, com 91 arquivos e umas 6 mil linhas de código pra PyTorch, que eles chamam de &ldquo;heavy ranker&rdquo;.</p>
<p>O problema é que o único lugar nesse código onde esses números de peso aparecem, é na documentação desse README. Olha só, se eu abrir o VS Code no projeto the-algorithm-ml e procurar por esse parâmetro &ldquo;is_negative_feedback_v2&rdquo;, que tem um peso negativo 74, só aparece no README, em nenhum lugar no código tem de fato esse valor.</p>
<p>Existe um arquivo &ldquo;local_prod.yaml&rdquo; que parece que configura esses parâmetros pra produção, e no servidor deles pode ser que esteja mesmo este número, mas no arquivo neste repositório, não tem nada. Portanto, não posso dizer que está nem certo, nem errado, só que é inconclusivo. Só que se você ler o post, vai achar que isso é um fato. E tenho certeza que um monte de gente vai referenciar esse blog post e repetir a informação sem checar o que significa. O Steven não errou nem mentiu, mas ele está descrevendo informação incompleta.</p>
<p>Mas não é só isso. Se for agora no post do tal Gupta, ele vai falar de outros parâmetros de peso. Por exemplo, ele fala que retweets dão um boost de 20 vezes. E de onde ele tira isso? É do repositório principal the-algorithm, no arquivo EarlybirdTensorflowBasedSimilarityEngine.scala no projeto cr_mixer. Lá tem esse trecho da função <code>getLinearRankingParams</code>. Ah, agora não é um README, é trecho de código. Então deve ser confiável, certo? Também não.</p>
<p>Vamos procurar quem chama essa função. Caímos nesse trecho da função <code>getRelevanceOptions</code> que, caso essa variável <code>useTensorflowRanking</code> seja falsa, daí vai chamar a função com os parâmetros que vimos antes, caso contrário, vai chamar essa outra função <code>getTensorflowBasedRankingParams</code>. Vamos ver o que ela define. Voltamos pro arquivo anterior e vemos que ela chama a função <code>getLinearRankingParams</code> abaixo, mas muda o tipo e, mais importante tem esse <code>applyBoosts</code> pra false.</p>
<p>O que isso significa? Será que então todos esses parâmetros com nome &ldquo;boost&rdquo; aqui embaixo vão ser ignorados? Como <code>offensiveBoost</code> ou <code>tweetHasTrendBoost</code>? Aí que tá, não temos como saber. Como disse antes, esse código é incompleto. Até o presente momento não sabemos nem se 100% desse código compila, não sabemos quais dependências estão faltando, não sabemos como buildar e, portanto, não sabemos como executar esse código e nem quais caminhos dentro são realmente executados pra cada tipo de dados e configurações.</p>
<p>E mesmo se conseguirmos fazer rodar, vamos voltar praquele README que mostrei agora pouco, da parte de machine learning com PyTorch. Olha o que diz: &ldquo;não podemos liberar os dados reais de treino por causa de privacidade&rdquo;. Faz sentido, claro, mas sem ter o mesmo material de treino, ou pelo menos o mesmo modelo gerado no final, não temos como reproduzir exatamente o que roda no Twitter e, portanto, não podemos garantir que é exatamente a mesma coisa rodando em produção nos servidores deles.</p>
<p>Portanto, do ponto de vista de auditoria, podemos confiar que, sim, esse código é certamente retirado do código real dentro do Twitter. É código demais pra falsificar, não seria prático. Mas os detalhes do que exatamente roda lá dentro, talvez a gente nunca saiba. Sempre é possível omitir camadas de filtros na fase de mixagem que adicionam ou removem tweets, ou omitir os arquivos de configuração reais que modificam todos esses pesos e boosts.  Não temos como realmente saber, por isso cuidado com afirmações sendo feitas só lendo esse código.</p>
<p>Antes de continuar, eu preciso falar da vergonha alheia que foi o comportamento geral das pessoas frente a esse código. Vamos estabelecer: eu não sou fã de Elon Musk, Dogecoin é shitcoin, e acho uma puta perda de tempo gente que idolatra o cara ou que perde tempo engajando. Ele é só mais um cara que existe. A existência dele não me afeta, assim como a existência de qualquer outro, pra mim, é insignificante. E é só isso.</p>
<p>O Twitter é uma empresa privada. Como qualquer empresa, ela não tem nenhuma obrigação de liberar código. Portanto, o que ela fez ao liberar, pra mim, é um excelente marketing. Uma cortina de fumaça pra desviar atenção, controlar a narrativa e deixar todo mundo coçando a cabeça tentando desvendar esse quebra cabeça; e ao mesmo tempo cutucar os críticos no governo e cutucar os concorrentes como um Zuckerberg da vida. No mínimo vai deixar muita gente chata ocupada tentando abrir uma caixa semi-transparente que não tem chave pra tranca.</p>
<p>A real motivação por trás disso não me interessa. Só me interessa que temos esse código. O que foi liberado, continua sendo um código excelente de explorar. Porém, pessoas idiotas parece que tem talento de encontrar a parte mais insignificante, e viralizar os memes mais idiotas. Se você pesquisar a palavra &ldquo;Elon&rdquo; no VS Code, a primeira coisa que vai aparecer é essa chave &ldquo;author_is_elon&rdquo;, logo em cima de outras chaves &ldquo;suspeitas&rdquo; como &ldquo;author_is_democrat&rdquo; ou &ldquo;author_is_republican&rdquo;. Uau, que vazamento inesperado! O que é isso, a prova que o Elon dá boost nos tweets dele? O que é isso de democratas e republicanos, tem manipulação neles também?</p>
<p>Até gente que se auto-denomina &ldquo;programador&rdquo; ficou fazendo meme disso. É uma vergonha alheia. Se você viu alguém fazendo meme disso, pode des-seguir, o cara é inútil. Deixa eu mostrar como faz. Vamos lá pro código. Esse &ldquo;author_is_elon&rdquo; é chave pra um Map que no final vai se chamar PredicateMap, que é gerado através desta Sequence, onde cada elemento é um tuple com uma string que serve de chave e um FeatureMap, que é uma função booleana. No caso desse trecho do Elon, ele recebe um candidato, provavelmente candidato de tweet pra recomendar, e checa se a tal feature de Author contém esse tal DDGStatsElonFeature. Ênfase em &ldquo;stats&rdquo;.</p>
<p>Bacana, e quem usa esse PredicateMap? Vamos procurar no código. Achamos aqui na linha 82 do objeto ServedEventsBuilder que extende essa trait ClientEventsBuilder. Nesse trecho ele tá iterando por cada predicado naquela PredicateMap e no final o que  tá gerando são counts pra cada predicado. É um contador. E quem vai usar essas contagens? Vamos de novo pesquisar, agora por esse objeto <code>ServedEventsBuilder</code>.</p>
<p>Pronto, chegamos neste <code>HomeScribeClientEventSideEffect</code>. É o que o Twitter chama de &ldquo;side-effect&rdquo;. Pelo que eu já tinha deduzido lendo o código, side-effect é um job de Kafka, um job assíncrono. E Scribe é justamente o servidor de logs open source que foi desenvolvido pelo Facebook. É só um servidor de log, não diferente de um Apache Flume ou Logstash, ou Rsyslog da vida. Esses códigos servem pra gerar métricas. É só isso.</p>
<p>&ldquo;Ah, mas tinha que ser o cuzão egocêntrico do Elon pra ter um código especial pra uma métrica só pra ele.&rdquo; Ué, mesmo se for. Se ele desenbolsou 40 bilhões pra comprar a porra toda, achei foi muito pouco código com o nome dele. Achei que fosse achar classes inteiras renomeadas pra DogeCoin ou algo assim espalhado por aí. Aliás, vocês estão assumindo que esse código foi feito por ordem do Elon.</p>
<p>Outra teoria muito mais plausível é que esse código foi feito justamente pelo povo que ele demitiu, pra monitorar os efeitos dos tweets do Elon antes da compra ter sido realizada. Muito mais plausível. Independente do que for, só vamos saber se, alguém de dentro do twitter, vazar os logs do repositório Git de verdade, com o timestamp do commit. E mesmo assim, commits de Git podem ser facilmente adulterados. Por isso é uma discussão meio inútil.</p>
<p>Todo mundo ficou fazendo meme com um código de log. E vocês se acham programadores? A discussão do porque esse trecho existe é irrelevante e só serve pra demonstrar como é um programador que gosta de fazer bikeshed: discutir coisas inúteis, sem nenhum valor, sem nenhum mérito. Você está perdendo seu tempo. Agora vamos entender como um programador de verdade olha pra esse código.</p>
<p>O objetivo a partir daqui é o seguinte: desmistificar a diferença de código de curso, código de tutorial e código de produção de empresa de verdade. Pra um iniciante que nunca trabalhou ou só trabalhou em projetos pequenos, a diferença vai ser gritante. A outra coisa é como um programador sênior pensa. Infelizmente, não tem como, num único video, com só 2 dias de análise, sem compilar, conseguir ter a visão completa. Então tudo que vou dizer são só minhas primeiras impressões.</p>
<p>Eu já disse isso em outros videos, mas aqui vale repetir: código de curso, código de tutorial, código em video no Youtube, são absurdamente diferentes de código de verdade de produção. Não tem nem comparação. A complexidade e tamanhos são impossíveis de caber em qualquer curso ou tutorial. É inviável. Literalmente, sem exagero, um curso vai te ensinar a fazer uma casinha de rato com caixa de sapato, mas todo projeto relevante de verdade é um Burj Khalifa, o arranha-céus mais alto do mundo que nunca foi construído, e toda a engenharia pra construir isso num terreno de areia, sem receita pré-pronta.</p>
<p>Outra coisa que já disse antes: software nunca acaba. Se seu produto realmente vale alguma coisa, você vai reescrever partes ou até ele inteiro, mais cedo ou mais tarde. E com esse código liberado pelo Twitter, vou finalmente poder ilustrar esses dois pontos. E esse vai ser o objetivo do video.</p>
<p>Eu peguei aleatoriamente o primeiro projeto no Github com nome de &ldquo;twitter clone&rdquo;. Tem dezenas por aí. Esse é feito em Next.js, com Typescript, Firebase, Tailwind, só &ldquo;tecnologia de ponta&rdquo;, como um amador poderia pensar. E ele faz tudo, olha só o Readme: usuários conseguem postar tweets, dar like, retweet, reply, adicionar bookmarks, adicionar imagens, seguir ou des-seguir outros usuários, updates de likes e tudo mais em tempo real. Porra, faz tudo.</p>
<p>Só que não. Isso não passa de uma caixa de sapatos frente ao Burj Khalifa. Vamos ver, com a ferramenta Tokei, vemos que esse projeto tem 158 arquivos e mais de 32 mil linhas de código. Pra um iniciante, sem dúvida, pode parecer gigantesco. Mas não é. Mesmo sendo só uma fração incompleta, o repositório do Twitter que estamos avaliando, tem mais de 4600 arquivos e mais de 330 mil linhas de código. 10 vezes mais código.</p>
<p>Mas esses projetos sequer são equivalentes. Esse tal &ldquo;clone&rdquo; é só o front-end e só as operações mais básicas. Ele jamais aguentaria a escala do Twitter em produção. O &ldquo;the-algorithm&rdquo; que foi liberado é só pra buscar os tweets que aparecem na aba &ldquo;Pra você&rdquo;. Notaram como esses clones omitem a aba &ldquo;pra você&rdquo;? Claro, porque justamente a inteligência pra buscar tweets recomendados, em tempo real, em escala, não é um problema trivial de resolver. Todo clone que você achar, ou vai omitir, ou vai ter uma versão super besta de recomendação. E nunca vão escalar pra ordem de centenas de milhões de pessoas. São todos, caixas de sapato.</p>
<p>E não tem nenhum problema nisso. O foco da maioria desses clones é ser um exercício pra iniciantes de front-end treinarem fazer interfaces parecidas com coisas que já existem. Nada mais, nada menos. Nenhum desses clones, seja clone de Twitter, clone de Instagram, clone de Pinterest, chega perto da unha dos pés das versões de verdade e são incapazes de fazer uma fração do que os originais fazem, ou de aguentar uma fração do tráfego. Essa é a realidade dos clones: são todos meros exercícios. Contanto que vocês entendam isso, não tem nenhum problema se exercitar neles. Todo mundo precisa começar do zero. E esse é o nível zero.</p>
<p>A razão dessa diferença é que é trivial fazer qualquer aplicativo funcionar só na sua máquina, com uma pessoa usando. Mesmo se você virar freelancer, normalmente trabalha em projetos que muito pouca gente usa. 10 pessoas. 100 pessoas. Mesmo mil pessoas, é extremamente pequeno. Empresas grandes como MercadoLivre, iFood, Nubank, lidam com milhões, dezenas de milhões de pessoas. No caso de Facebook, Google ou Apple, são bilhões de pessoas.</p>
<p>E nessa escala, as soluções são completamente diferentes de  todo curso e tutorial que você procurar. Eu afirmo que não existe nenhum curso que cobre casos de larga escala, simplesmente porque todos fazendo cursos nunca tiveram que lidar com esse problema. Eles também simplesmente não sabem que isso existe. Eu mesmo nunca tive que lidar com o caso excepcional de bilhões de pessoas.</p>
<p>É muito caso a caso, em cada empresa gigante, vai ser diferente. Nenhum curso conseguiria cobrir, mesmo se quisessem. Simplesmente não existe esse material disponível, e é uma das razões de porquê esse código do Twitter é relevante: porque não existe código de produção de empresa de verdade disponível pra podermos mostrar, todos são fechados.</p>
<p>Eu trabalhei em vários projetos, incluindo alguns dessa categoria grande. Mas não posso mostrar, porque nenhum de nós deve quebrar confidencialidade e propriedade intelectual de clientes. Por isso eu falo pra não confiar em nenhum programador que não tenha tido esse nível de experiência: eles nunca viram nada além da dimensão de caixa de sapato e acha que o resto do mundo é pequeno assim. E definitivamente não é. Só quem já subiu no topo do Burj Khalifa, sabe a diferença.</p>
<p>A tal aba &ldquo;Pra você&rdquo;, à primeira vista, parece super simples. Na aba &ldquo;seguindo&rdquo; mostra só tweets de pessoas que eu sigo, provavelmente em ordem cronológica. Fácil de fazer, um iniciante consegue imaginar um &ldquo;select top 100 * from tweets order by created_at desc&rdquo; da vida. Agora, no pra você, tem tweets de gente que eu sigo e de gente que eu não sigo, mas que as pessoas que eu sigo interagiram. Então se eu sigo o João e o João retwitou o tweet do Paulo, no meu &ldquo;pra você&rdquo; pode aparecer o tweet do Paulo. Só que não é tão simples assim.</p>
<p>E se quem eu sigo interagiu com 100 tweets? Quais deles deveriam aparecer pra mim? Das pessoas que eu sigo, quais delas são mais relevantes pra aparecer no topo primeiro? Por que eu vejo o like de algumas pessoas mas não de outras? Ah, será que como os posts de blogs tavam tentando explicar, é só botar um peso pra like, outro peso pra retweet e dar um &ldquo;group by&rdquo; ou &ldquo;order by&rdquo; da vida e pegar o topo? Também não.</p>
<p>Como é impossível cobrir o código todo que foi liberado, escolhi começar falando só de um pequeno trecho. Um banco de dados NoSQL de grafos, que inclusive já tinha sido liberado antes como open source, com licença Apache, chamado GraphJet, e a história por trás dele. Tudo isso foi descrito num paper de autoria de Aneesh Sharma e vários outros colaboradores, publicado em 2016.</p>
<p>Pra todo mundo que não fez ciências da computação entender, na faculdade a gente aprende sobre um troço chamado &ldquo;grafos&rdquo;. Na prática, você já deve ter visto e até desenhado. É quando se desenha caixinhas ou círculos que chamamos de nós e ligamos um nó com outro nó com um troço que chamamos de vértices. Podemos representar grafos como matrizes adjacentes ou listas adjacentes. Outra forma de representar grafos é com vetores.</p>
<p>Daí podemos classificar grafos baseados em características como número de nós ou número de vértices, edges ou bordas, direção das bordas. Tem diversos tipos de grafos como unidirecionais. Em Git, por exemplo, você já deve ter ouvido falar que é um DAG, um direct acyclic graph, ou grafo acíclico, que não é cíclico, e direto.</p>
<p>Dado um grafo, temos como navegar de um nó pra outro nó. Pense em nós como uma casa numa rua e os vértices como o caminho, talvez a rua, entre essas casas. Daí temos algoritmos pra navegar como o algoritmo do menor caminho entre dois nós, que é o hello world de grafos. Não só isso, ainda no assunto de classificação, temos nós que ficam nas bordas do grafo que tem poucos vértices e temos nós no meio dos grafos com muitos vértices.</p>
<p>Costumamos chamar esses nós de &ldquo;hubs&rdquo;. Na prática, numa rede social, chamamos de hubs, mas pessoas comuns chamam de &ldquo;influencers&rdquo;. Pessoas comuns são os nós das bordas, com poucos relacionamentos, que são os vértices.</p>
<p>Tudo na natureza pode ser representado como grafos. Como falei antes, evolução de desenvolvimento de software, pode ser representado como um DAG, um grafo de commits, com branches. Um sistema de logística e entregas é orientado num mapa com grafos, pra determinar o melhor caminho de entrega entre dois pontos. O DNA humano pode ser um grafo, onde os nós são as bases como adenina, citosina, guanina e timina, formando nucleobases. As sinapses do nosso cérebro são grafos.</p>
<p>Num ecossistema animal temos a hierarquia da cadeia alimentar, que pode ser representado como um grafo. No mercado temos grafos de empresas e relacionamentos de comprador e vendedor. E assim por diante. No caso do Twitter, que é uma rede social, os nós costumam ser as pessoas, e os vértices são os relacionamentos, quem nós seguimos, quem damos likes ou retweets, quem bloqueamos e assim por diante.</p>
<p>Se você nunca estudou ciências da computação, vai ter um pouco de dificuldade de entender o que vou explicar e certamente não vai entender nada do código do Twitter. Vou tentar simplificar o máximo que der, mas só consigo chegar até certo ponto, então, se interesse em depois ir procurar cursos como nas aulas do MIT sobre grafos. Pra operar em grafos, você vai precisar aprender coisas como Álgebra Linear e Cálculo, pelo menos o Cálculo básico.</p>
<p>E portanto sim, pra entender como uma rede social funciona e o código por trás, precisa entender esses aspectos da matemática. E pra chegar no ponto de recomendação, também precisa entender o básico de estatística e probabilidade, porque recomendação é justamente a probabilidade dos tweets que o sistema recomendar realmente te interessar a ponto de engajar, dar like ou reply, que é a base de machine learning.</p>
<p>Mesmo se você não for implementar do zero inteligência artificial, machine learning, ainda assim precisa entender essa matemática. Machine Learning é, nada mais, nada menos, do que uma coleção de algoritmos de classificação e probabilidade. Se não souber quando pode usar regressão linear, regressão logística, similaridade de cosseno, bayes ingênuo, não vai saber o que fazer.</p>
<p>Cada caso, cada tipo de conjunto de dados e o resultado que quer, depende de saber escolher quais desses algoritmos usar. E pra saber quais usar, precisa saber a matemática por trás. Senão vai usar regressão linear quando não podia e ter resultados errados, que é a coisa mais comum de ver amador fazer. Nem tudo que parece uma reta, é uma reta.</p>
<p>E no caso desse código do Twitter sim, eu encontrei que ele usa coisas como grafos, regressão logística e similaridade de cosseno. Mas estou me adiantando. Vamos voltar ao tal paper do Graphjet de 2016. Um problema, que não é trivial de resolver e não tem uma solução única, porque depende do comportamento dos usuários e dos dados disponíveis, é recomendação. Seja recomendação de produtos num ecommerce, recomendação de leitura num site de notícias, ou recomendação de tweets num Twitter.</p>
<p>Como usuários seguem usuários, naturalmente temos grafos, em particular grafos bipartidos, com usuários de um lado e tweets de outro. Os esforços de recomendação começaram no Twitter por volta de 2010, lembrando que o Twitter começou a operar em março de 2006.</p>
<p>Eu mesmo criei minha conta só um ano depois, em abril de 2007, durante a Railsconf 2008 em Portland, onde eu via todo mundo se cadastrando pra gente compartilhar onde ia ser o próximo happy hour. Lembrando que ainda não existia Messenger do Facebook, não existia Whatsapp, não existia Telegram. O único aplicativo de mensagem que todo mundo usava, era SMS.</p>
<p>De qualquer forma, quando eles começaram a experimentar a recomendar usuários novos pra gente seguir, por volta de 2010, foi com um sistema que chamaram carinhosamente de &ldquo;WTF&rdquo;, What the Fuck. Mentira, era &ldquo;Who To Follow&rdquo;. A arquitetura era como nessa figura. Segundo o paper, no seu núcleo tinha esse Cassovary, que é um motor de processamento de grafos in-memory, ou seja, que mantém os grafos em memória. Isso é importante, porque a maioria dos algoritmos conhecidos pra lidar com grafos assume ter o grafo todo em memória. Guarde essa informação.</p>
<p>Esse projeto Cassovary é open source e podemos encontrar no GitHub deles. Última atualização foi de 2021, e não é mais utilizado hoje. Continuando, esse Cassovary operava em snapshots de grafos de seguidores carregados do HDFS, que é o sistema de arquivos distribuídos do Hadoop. Quem já trabalhou com Big Data, Data Lakes, certamente já esbarrou em instalações de HDFS e no Twitter não era diferente. Só entenda que é um sistema de arquivos distribuído, como se você tivesse uma partição única espalhada em múltiplos servidores. É um nível acima de um simples RAID.</p>
<p>Os grafos de seguidores vinham de outro banco de dados chamado FlockDB que fazia a ingestão desses dados no HDFS. FlockDB acho que foi o primeiro banco de dados de grafos que o Twitter criou e soltou como open source. O que aconteceu foi: eles começaram como qualquer tech startup normal, sem saber o que o produto ia se tornar. No começo usavam um banco de dados normal, MySQL mesmo. Mas quando começaram a fazer sucesso e ver que o problema era maior que um banco relacional, começaram a pesquisar alternativas. E o primeiro resultado foi um banco de dados de grafos, o FlockDB.</p>
<p>O FlockDB dava mais performance pra operações de inserção, updates, já que não precisava das garantias ACID de um banco relacional. Tinha operações extras pra lidar com grafos, como navegar por nós e pelos vértices. E vocês tem que entender que carregar um grafo em memória pode ser pesado. Imagine sua própria conta: você tem dezenas de tweets, você segue dezenas de pessoas, essas pessoas tem dezenas de tweets.</p>
<p>Cada um desses tweets tem contagens de likes, retweets; alguns tweets são replies pra outros tweets. E pra calcular qual deles é mais relevante ou menos relevante, o ideal é ter toda essa estrutura em memória pra conseguir navegar por toda essa informação. Agora pensa num banco de dados relacional como MySQL. Como você resolveria esse problema? Qualquer solução que você imaginar em SQL, pode ter certeza, já foi testado e sabemos que não vai escalar. Essa é um dos poucos cenários que podemos afirmar que um banco de dados relacional não é adequado.</p>
<p>Segundo a página de GitHub do FlockDB, até 2010 o cluster deles armazenava mais de 13 bilhões de bordas e sustentava picos de tráfego de mais de 20 mil escritas por segundo, e mais de 100 mil operações de leitura por segundo. Mas rapidamente chegou um ponto onde isso já não era mais suficiente. Foi quando começaram a puxar esses dados pra HDFS e processando nesse novo projeto que chamaram de Cassovary, um segundo projeto em Java pra lidar com &ldquo;Big Graphs&rdquo;, grafos com bilhões de nós e vértices. Foi a época que as celebridades gigantes começaram a viver no Twitter. Pense nível Kardashians.</p>
<p>Voltando ao paper, a camada de armazenamento do Cassovary dava acesso aos grafos via queries, pesquisas baseadas em vértices. Pense um &ldquo;SQL&rdquo; especializado em grafos. E em cima disso um motor de recomendação computava as sugestões de &ldquo;Who To Follow&rdquo;, o WTF, ou &ldquo;quem seguir&rdquo;. Foi assim que nos primórdios do Twitter a ganhamos sugestões de novas contas pra poder seguir.</p>
<p>Um dos objetivos desse projeto era ver se era possível manter os grafos em memória, in-memory. A pergunta que eles não tinham resposta é: será que o crescimento do volume de dados desses grafos seria mais lento que a Lei de Moore? Se for, o preço de memória iria baratear mais rápido do que os dados cresciam, e daria pra computar tudo em memória. Que é mais simples do que ter que lidar com discos, particionamento, paging, cache e tudo mais.</p>
<p>Como o paper diz, considere um grafo com 10 bilhões de bordas: mesmo uma representação ingênua, simples, ocuparia algo na faixa de 80 gigabytes, que um único servidor consegue aguentar. Você já trabalhou com um servidor, este ano, que tem 80 gigabytes? Se sim, diga nos comentários que empresa é, pra todo mundo entender meu ponto.</p>
<p>Uma das limitações, mesmo naquela época, é que essa sugestões de recomendação de quem seguir, eram calculados em batch, uma vez por dia, não era em tempo real como é hoje. Ou seja, você só tinha recomendações novas uma vez por dia. De qualquer forma, via muita experimentação, muitos testes A/B, eles chegaram em dois conceitos que usam até hoje e que vale explicar. O primeiro se chama círculo de confiança e o segundo foi a adoção de &ldquo;SALSA&rdquo; que é Stochastic Approach for Link-Structure Analysis ou Análise de estrutura de links usando processos estocásticos.</p>
<p>Um exemplo de grafo que eu não mencionei, mas deveria ser óbvio, é a própria World Wide Web. Toda página na internet é um nó, as bordas, vértices, são links de uma página pra outra. No começo da web, já se tinha um número grande de páginas, o suficiente pra ser difícil de achar as coisas. Pense numa era antes de existir Google. A forma rudimentar que existia pra gente se achar era derivado do conceito antiquado de Páginas Amarelas.</p>
<p>Se você for muito novo talvez nunca tenha visto, mas antigamente, quando precisávamos achar um mecânico, um médico ou qualquer serviço, tínhamos um livrão físico de páginas amarelas que era um diretório com todos os serviços da cidade. Cada cidade tinha um livro diferente. Imaginem que novos negócios tinham dificuldade de serem encontrados, porque precisava esperar uma nova edição ser impressa e distribuída pra todo mundo. Páginas de classificados nos jornais meio que cobriam um pouco dessa diferença.</p>
<p>Serviços como Yahoo, Excite, Lycos e outros serviam esse papel na Web. Se eu criasse um site novo, pedia pra ser incluído num desses diretórios pra que as pessoas conseguissem me achar. Esse era o processo. Mas mesmo assim, imagine restaurantes. Qual das centenas de restaurantes numa cidade grande como São Paulo, seria relevante pra mim? Mesmo se tivesse a opção de filtrar por localização e tipo de cozinha. Ainda assim eu tinha que manualmente tentar várias. Era bem trabalhoso.</p>
<p>Portanto, resolver esse problema de relevância entre milhares de páginas web, era muito importante. E lá no meio pro fim dos anos 90 surgiram pelo menos dois estilos diferentes de lidar com isso. A maioria de vocês que estudou o mínimo de grafos talvez estejam familiarizado com o famoso paper de PageRank, literalmente ranking de páginas, inventado pelos fundadores do Google, Larry Page e Sergey Brin em 1996 quando ainda eram estudantes de Stanford.</p>
<p>Procurem no YouTube, tem dezenas de videos que explicam PageRank, mas a idéia básica é a seguinte. Começa pelo básico, o &ldquo;H&rdquo; de HTML é pra Hyperlink. Todo mundo esquece isso, mas o mais importante numa página web é que a página de alguém contenha um link pra sua página. Quanto mais sites diferentes tem links apontando pro seu site, mais relevante ele pode ser.</p>
<p>No PageRank, imaginamos um navegador aleatório, uma pessoa imaginária que vai clicando em páginas e nos links nessas páginas, aleatoriamente. Qual a probabilidade de, só navegando sem rumo, alguém cair na sua página? Esssa probabilidade é o seu score, sua pontuação. Se sua página é famosa, centenas de outras páginas contém link pra sua, a probabilidade desse navegador aleatório chegar na sua página é alta, portanto sua pontuação no ranking, no PageRank, é alta.</p>
<p>Esse foi o algoritmo usado na primeira versão do Google, o site que matou todos os diretórios estilo páginas amarelas. Porque agora era possível fazer um programa que sai vasculhando todas as páginas na web, avaliando os hyperlinks entre eles, e atribuindo pontuações nesse ranking. Daí, quando fazemos uma pesquisa, ele nos mostra primeiro as páginas mais bem pontuadas. É por isso que logo na primeira página, os primeiros links costumam ser os corretos.</p>
<p>Com isso eliminamos a necessidade de seres humanos ficarem manualmente classificando páginas em diretórios e atualizando a relevância na mão, que sempre foi demorado, trabalhoso, tedioso e com muita margem de erro. Essa talvez tenha sido a primeira categoria de trabalhadores da web que perderam seu trabalho pra um algoritmo, e não será a última. Tudo que é feito manualmente e pode ser automatizado, certamente será. Todo trabalho repetitivo é automatizável, sabemos isso desde o começo da Web.</p>
<p>Mas todo algoritmo tem seu ponto fraco e rapidamente as pessoas começaram a notar que se o lance é esse número de links pra sua página, que tal criar um monte de sites fantasma que fazem links pra uma página que você quer tornar relevante? E assim surgiram os primeiros web farms, fazendas de servidores fake, e uma galera que vendia relevância pro seu site, fazendo um monte de site falso linkar pra sua. Seja criando sites bestas em série ou invadindo sites dos outros pra colocar links.</p>
<p>Isso aconteceu só lá no começo dos anos 2000. Por isso o Google evoluiu e deixou de usar PageRank e passou a usar, por exemplo, TrustRank, que combate web spam identificando quem é site legítimo e quem não é, e descontando pontuação. Ao longo dos anos o Google sempre foi aprimorando esses algoritmos, não é um produto fixo. Parte do trabalho de agências web era ficar monitorando quando o Google lançava uma nova versão pra modificar as estratégias pra levar relevância pros seus clientes. Esses truques baratos não funcionam mais hoje.</p>
<p>Mas não era só isso. Mesmo sem spammer e gente maliciosa, as primeiras versões do PageRank ainda sofriam de um problema grave. Pra ilustrar, veja meu conteúdo, meu canal e meu blog, que é de um domínio muito específico de assuntos de tecnologia pra programadores. Talvez eu faça um video explicando em detalhes sobre o algoritmo do Twiter, exatamente o que estou fazendo agora. Pra programadores é relevante.</p>
<p>Porém, imagine que um bosta como um Felipe Neto também faça um video falando sobre o algoritmo do Twitter. Segundo o PageRank, que é um ranking global, quer você goste dele ou não, se pesquisar no Google de 1997 &ldquo;algoritmo do Twitter&rdquo;, esse video do Felipe Neto iria aparecer na frente do meu. Essa é a desvantagem de um algoritmo de ranking global como o PageRank original.</p>
<p>Mas como eu falei, as pesquisas em torno de grafos evoluíram rápido. Por exemplo, em 1999 surgiu o algoritmo HITS que significa Hyperlink-Induced Topic Search, de Jon Kleinberg. HITS opera num grafo direcionado, onde páginas web representam nós e hyperlinks são bordas direcionadas. Até aqui, igual PageRank, mas o HITS assinala duas pontuações pra cada página web: uma pontuação se é um &ldquo;hub&rdquo; e outra pontuação se é uma &ldquo;autoridade&rdquo;.</p>
<p>Hubs são páginas que linkam pra diversas outras páginas. Pense um site do Estadão ou da Folha. São hubs de notícias que linkam pra trocentas outras páginas fora. Ou influencers como o Nelipe Feto, é um hub. Já autoridades são páginas que recebem muitos links de muitos hubs. Pense a página da Receita Federal em época de imposto de renda. Todo mundo faz link pra ela, pra avisar que tá vencendo a data ou ensinando como preencher seu imposto. Mas todo mundo pode ser ao mesmo tempo um hub e uma autoridade, alguns são mais hubs, alguns são mais autoridades num domínio em específico. Por isso duas pontuações distintas.</p>
<p>Portanto, o canal de um Nelipe Feto da vida teria uma pontuação de Hub muito maior do que de autoridade, que é exatamente o perfil do influencer médio: serve pra direcionar tráfego pra todo mundo, mas ele mesmo não é autoridade de quase nada. Já caras como eu geram muito pouco tráfego pros outros, mas todo mundo me menciona e linka pra mim em assuntos de um domínio específico de programação e tecnologia.</p>
<p>Com isso, minha pontuação de autoridade, num video sobre &ldquo;algoritmo do Twitter&rdquo;, seria maior que do Nelipe e eu apareceria no topo da pesquisa, mesmo ele tendo muito mais tráfego e sendo muito mais famoso. Essa é a vantagem de algoritmos da família do HITS, se comparado ao PageRank original: ele torna esse jogo bem mais justo do que uma pontuação única global, e tenta melhorar a confusão entre influencer e autoridade. Só porque alguém tem muito seguidor, de jeito nenhum o torna autoridade de coisa nenhuma.</p>
<p>Porém, se antes, a forma ingênua era ter uma única pontuação global do PageRank, em HITS teríamos sub-grafos pro domínio ou tópico, por exemplo, programação, gastronomia, viagens, cinema e cada vértice teria duas pontuações, de hub ou autoridade, por tópico. Portanto dá pra imaginar que é mais caro computar HITS do que PageRank. Claro, o que temos hoje num Google é uma amálgama dessas técnicas e muitas outras que surgiram ao longo do tempo. O que nos leva de volta a SALSA.</p>
<p>SALSA poderia ser considerado uma evolução da idéia de HITS pra análise de links, mas uma das idéias é não precisar navegar por todos os vértices e pontuar o grafo inteiro. Em vez disso ele implementa a idéia de andarilho aleatório ou random walker. É que nem pesquisas de opinião pública. Todo mundo tá acostumado a ver agências de fake news, cof, quero dizer, imprensa tradicional, fazendo pesquisas de opinião na rua pra dizer &ldquo;ah, metade dos brasileiros considera o governo atual uma bosta&rdquo;. Mas como eles sabem disso? Eu não lembro de ninguém me perguntando isso.</p>
<p>Essa é a mágica da estatística. Podemos fazer sampling, ou seja, pesquisar só uma fração da população. Quanto mais aleatório for esse sampling, melhor. Um coisa é perguntar pra 100 pessoas só de São Paulo, outra coisa é perguntar pra 4 pessoas de cada estado. Assumindo que escolhemos bem esse grupo, dentro de uma margem de erro, podemos afirmar que a população em geral tem essa ou aquela opinião, economizando ter que perguntar pros mais de 200 milhões de cidadãos do país. É uma puta economia de 200 milhões pra 100. Entendem?</p>
<p>Mesma coisa num grafo. Eu não preciso necessariamente andar o grafo inteiro, posso escolher um grupo aleatoriamente, em vez de usar o modo iterativo original do HITS, economizando muito processamento e tendo resultados que caem em margens de erro estatisticamente aceitáveis. Pra otimizar mais isso, ele também reorganiza os nós e vértices num grafo bipartido, que ajuda a separar os papéis de hubs e autoridades.</p>
<p>Posso separar nossos nós em dois conjuntos distintos: um com páginas que linkam pra outras páginas, os hubs, e outro grupo com páginas que recebem links, as autoridades. Não vale a pena eu detalhar hoje, mas gravem esses dois conceitos de random walk e grafo bipartido pra estudar depois se tiverem interesse.</p>
<p>Expliquei tudo isso porque o paper do Twitter menciona que eles desenvolveram um algoritmo de recomendação baseado em SALSA, que é um algoritmo de random walk na mesma família de HITS, originalmente desenvolvido pra ranking de pesquisa web. O algoritmo constrói um grafo bipartido de uma coleção de websites de interesse (hubs de um lado e autoridades de outro), e cada passo no SALSA navega por duas bordas - um pra frente e outro pra trás. - agora você sabe o que esse parágrafo do paper está dizendo.</p>
<p>E ele continua dizendo: nós adaptamos SALSA pra recomendação de usuários da maneira mostrada nesta figura. O lado de hubs na esquerda é populado com o tal círculo de confiança do usuário, os usuários que ele escolheu manualmente seguir. As &ldquo;autoridades&rdquo; no lado direito são populados com usuários que esses hubs interagem. Depois que esse grafo bipartido é construído, rodamos múltiplas iterações de SALSA, que assinala pontuações.</p>
<p>Daí os vértices do lado direito são ranqueados por essa pontuação e tratados como recomendações de usuários. Os vértices do lado esquerdo também são ranqueados e interpretados como similaridade entre usuários. Baseado no princípio da homofilia, que é só um nome bonito pra dizer que pessoas com interesses parecidos tendem a se agrupar mais, podemos apresentar esse resultado como recomendações, que antigamente era uma seção chamada &ldquo;similares a você&rdquo;.</p>
<p>Resolvi dar uma detalhadinha a mais até aqui pra tentar explicar como foi o raciocínio por trás da primeira versão do motor de recomendação, mas agora vou tentar andar um pouco mais rápido, porque esse foi o sistema WTF que não existe mais. Sim, toda essa pesquisa, experimentação, durou pouco tempo. Apesar de ser um sistema bom, que funcionou bem, ele ainda não é o que conhecemos hoje. E em 2012 eles começaram a perseguir o tal projeto Cassovary que mencionei antes.</p>
<p>Pra ter recomendações ainda melhores, eles queriam usar mais sinais dos logs de comportamento dos usuários. Comportamento de likes, retweets, replies, follow e tudo mais. Mas isso torna os metadados do grafo ainda maiores e a premissa da primeira versão, que era tentar manter tudo em memória, já não cabia mais. Daí veio a decisão de tentar usar Hadoop, em particular Apache Pig que, pra quem não sabe, é tipo um compilador que produz sequências de programas MapReduce.</p>
<p>Hadoop é uma plataforma de tecnologias criadas pra lidar com o problema de Big Data no auge da Bolha da Internet no começo dos anos 2000. Pra muita gente ainda hoje, o conceito de Big Data não é muito palpável, porque ninguém lida com isso no dia a dia. Varia de produto pra produto, mas quando estamos gerando coisa como gigabytes por dia de dados, terabytes por mês, estamos em território de Big Data. O que você faz, com um banco de dados que tem centenas de terabytes? Deita e chora, porque não importa quão bom seja seus índices e queries, sua aplicação vai ficar absurdamente lenta, só pelo tamanho.</p>
<p>Nessa ordem de grandeza estamos falando que não é possível manter todos os dados numa única máquina, mesmo sendo um servidor grande, seja por limitação física ou de custo. Daí a única solução é a estratégia de Napoleão: particionar, dividir esse tanto de dados em múltiplos servidores, e daí conquistar. Por isso a base do Hadoop começa com o HDFS, que é o Hadoop File System, um sistema de arquivos distribuído.</p>
<p>Agora o problema é o seguinte: na faculdade você aprendeu a lidar com dados todos no mesmo lugar. Por exemplo, digamos que você tem uma lista de produtos e preços e queremos achar o produto mais barato, como faz? Simples, escolhe um algoritmo qualquer de ordenação como um Quicksort ou Mergesort da vida, e pega o primeiro elemento que vai ser o mais barato. Pra fazer isso, precisamos iterar elemento a elemento da lista pelo menos uma vez. Mas e agora, e se estamos com 5 servidores e essa lista foi dividida por 5?</p>
<p>Intuitivamente, poderíamos pensar, beleza, vou ter que repetir a ordenação uma vez em cada máquina. Daí no final vou ter uma lista parcial com os 5 produtos mais baratos achados em cada um dos 5 servidores, daí faço uma última ordenação nessa lista e finalmente acho o mais barato. Cada um dos servidores vai rodar em um quinto do tempo do que se fosse a lista completa, mas no final, ainda tem pelo menos mais uma operação de ordenação extra e aí tenho o resultado. Então seria tecnicamente um pouco mais &ldquo;lento&rdquo; rodar em vários servidores separados, mas vai ser menos caro.</p>
<p>Isso de rodar um algoritmo em pedaços dos dados, mapear os resultados parciais e depois reduzir na resposta final, é o que, a grosso modo, chamamos de MapReduce, que é parte fundamental de como fazer processamento de dados em Hadoop. Novamente, não vou detalhar MapReduce então anote pra pesquisar mais depois. MapReduce ficou famoso porque, depois do PageRank, é como o Google conseguiu operar em larga escala e deixou todo mundo no chinelo no começo dos anos 2000.</p>
<p>Hoje em dia é como toda grande empresa de tecnologia funciona, mas 20 anos atrás, essa forma de pensar em processamento de dados foi fundamental pra possibilitar termos produtos que aceitam quantidades massivas de pessoas e transações, literalmente ultrapassando a população total de diversos países. Tem mais gente no Twitter do que a população dos Estados Unidos, metade da população do mundo tá em produtos da Meta.</p>
<p>Por isso, pra segunda versão do motor de recomendação, o Twitter deixou o FlockDB e WTF pra trás e perseguiu o tal projeto Cassovary pra rodar em cima do HDFS do Hadoop, usando MapReduce. Mas eles tiveram vários problemas. Mesmo em 2012 já era sabido que grafos não performavam bem em Hadoop. Isso porque a maioria dos algoritmos conhecidos pra processar grafos são iterativos, lembra que falei pra guardar essa informação?</p>
<p>Pra ilustrar. Pense nos diferentes algoritmos de ordenação que aprendeu na faculdade. Alguns professores cometem o pecado de não explicar porque existem tantos e porque foram criados. Por exemplo, meu professor na faculdade mandou a gente decorar lá Quicksort, Mergesort, mas não explicou que mergesort foi criado pelo lendário Von Neumann pro problema de listas de dados que não cabiam na memória RAM da época, então começavam gravados numa fita. Daí o algoritmo vai percorrendo os elementos da fita, sem estourar a RAM, e vai gravando ordenado numa segunda fita. Se tiver essa imagem de fitas na cabeça, mergesort finalmente faz sentido. Sem isso, é um algoritmo que não faz sentido existir.</p>
<p>Em 2012 ainda não existia o equivalente mergesort pra grafos que não cabem em memória e precisam ficar divididos em partições de HDFS distribuídos em múltiplos servidores. Hoje em dia já existe um suporte maior. Pense um Apache Spark com GraphX. Mas 10 anos atrás, era um uso inédito ainda. O próprio paper descreve que eles estavam cientes dessas desvantagens. O paper menciona que o gargalo era na materialização dos vizinhos dos vértices, que requeria um enorme shuffling de dados, que é pegar dados de diversos servidores pra formar o grafo necessário pro processamento.</p>
<p>Nessa parte do paper ele referencia outro paper, o &ldquo;Discovering Similar Users on Twitter&rdquo;, onde é explicado como exatamente eles conseguiram passar as barreiras de usar Hadoop, adaptando o algoritmo de PageRank personalizado pra ambiente distribuído. Como já expliquei, esse algoritmo é largamente utilizado pra computar similaridade entre nós em um grafo, mas é iterativo e requer data shuffling frequente. Quando falo data shuffling é tipo reembaralhar dados. Pensa como isso é difícil quando seus dados estão distribuídos em diferentes servidores.</p>
<p>Pra resolver, os autores do paper propõe uma modificação ao algoritmo de PageRank personalizado chamado &ldquo;One Pass PPR&rdquo;, que reduz o número de iterações e minimiza esse reembaralhamento pra cada iteração. Nesse caso, os dados são particionados baseados nos IDs dos usuários e processados numa única passada sobre os dados, usando uma combinação de pontuações local e global. O algoritmo também usa técnicas de caching pra evitar recomputar resultados intermediários, reduzindo o número de iterações totais.</p>
<p>Além disso os autores também propõe técnicas de compressão pra reduzir o tamanho do grafo e possibilitar processamento mais rápido em sistemas distribuídos como Hadoop. É muito detalhe pra tentar explicar hoje, mas esse é o resumão desse outro paper que também recomendo darem uma olhada depois.</p>
<p>É nele que se resolve o problema de conseguir dizer que quem segue um Lebron James tem alta probabilidade de seguir um Kobe Bryant ou quem segue uma Lady Gaga costuma seguir uma Taylor Swift. E é onde nasce o framework que é utilizado até hoje e que vou retornar pra explicar, sobre a arquitetura de geração de candidatos e modelo de aprendizado baseado em regressão logística e similaridade de cosseno pra achar usuários similares no Twitter.</p>
<p>Voltando ao Cassovary, é só uma parte da solução, é quem dá acesso aos dados dos grafos em Hadoop. A solução geral é chamada de RealGraph, que é uma representação composta do grafo de seguidores e grafos de interação induzidos através de logs de comportamento. Essa representação é construída de múltiplos pipelines de funcionalidades que são extraídas, limpas, transformadas e juntadas a partir dos logs crús. O RealGraph é armazenado em HDFS. E esse nome &ldquo;RealGraph&rdquo; vamos encontrar no código em Scala do repositório que foi liberado, como podemos ver no VS Code. Toda vez que ver &ldquo;RealGraph&rdquo;, provavelmente é isso.</p>
<p>Mas esse segundo motor de recomendação ainda tinha um grande defeito, só servia pra recomendar usuários similares pra você seguir e só atualizava poucas vezes ao dia, não era nem perto de ser tempo real. Funcionava, mas ainda não é a aba &ldquo;pra você&rdquo; que tem no Twitter hoje. Pulamos do WTF de 2010, pro Cassovary, Hadoop e RealGraph de 2012, e agora saltamos pra 2014 e um novo paper que descreve algumas novas estratégias pra particionar e organizar esses grafos. Como podem ver, não era um problema trivial de resolver.</p>
<p>No caso de Twitter, e até em sistemas de mensageria em geral, existe uma particularidade nos dados: você nunca precisa do grafo inteiro dos usuários. Por exemplo, pouca gente tá interessado em ver tweets de mais de 1 ou 2 dias atrás das pessoas que segue. Então mesmo que seu influencer favorito tweet todos os dias, o que seria um volume grande de tweets pro grafo, podemos limitar uma janela de 1 ou 2 dias pra todo mundo e lidar com um sub-grafo muito menor.</p>
<p>Pensando assim não precisamos lidar com operações como &ldquo;deletar&rdquo; um tweet do grafo, por exemplo. Basta pensar em expiração. É mais ou menos o mesmo pensamento por trás de caches como Memcached ou Redis da vida, que usamos hoje. Não pense em como apagar dados velhos, estabeleça uma janela de tempo e deixa expirar sozinho, que é boa prática de qualquer tipo de caching.</p>
<p>Não só isso, mas não precisamos guardar o timestamp exato de cada tweet no grafo. Isso vem da observação que a maioria das interações, como likes, retweets, replies, costumam acontecer muito próximo do momento que o tweet foi criado. Pra maioria de nós é interessante saber que o tweet foi postado 1 hora atrás, 5 horas atrás, 1 dia atrás, mas não interessa exatamente quando. É menos um dado pra ter em memória e mais uma economia.</p>
<p>Pensando em tudo que eles aprenderam até agora, iniciariam um terceiro motor de recomendação em cima de outro projeto que eles também tornariam open source, chamado de GraphJet. Ele mantém um grafo bipartido que registra integrações de usuários a tweets durante as mais recentes &ldquo;n&rdquo; horas, com uma API super simples só com as operações que eles sabiam que realmente iam precisar, sem fazer overengineering de criar um banco de dados de grafos genérico demais. Finalmente tinham experiência suficiente pra conseguir especializar a solução.</p>
<p>O resto do paper entra em detalhes sobre como foi a estratégia de mapeamento de IDs, estratégias de gerenciamento de memória pra balancear reembaralhamento e distribuição dos dados. Em resumo, eles conseguiram chegar num modelo onde só o processamento pra encontrar usuários e tweets recomendados ficou na casa abaixo dos 10 milissegundos, que é perto do ideal pra conseguir ter resultados em real-time, onde a maior parte dos gargalos era a comunicação com os diversos sistemas de filas, como o Apache Kafka que usam até hoje.</p>
<p>Esse paper é bem bacana e recomendo lerem com muita calma e anotando cada termo que não conhecem pra pesquisar depois. Ele descreve as três gerações de motores de recomendação dentro do Twitter até 2016. Não parei pra ver se tem papers mais novos que continuam essa história, mas graças a isso o código liberado que tem vários trechos que mencionam RealGraph ou GraphJet, agora devem fazer mais sentido. Entenderam? Sem esse tipo de pesquisa, só lendo o código liberado, ninguém nunca ia entender do que se trata. Precisa do contexto histórico.</p>
<p>E isso é só a parte do motor de recomendação, existem diversas outras funcionalidades no Twitter, por exemplo, o motor de pesquisa, que é quando você faz pesquisas por palavras-chave e ele retorna tweets com esse conteúdo, que nem uma pesquisa no Google. Na figura da solução do Cassovary, que puxa dados do HDFS, que vieram do FlockDB, pra cima tem fetchers que mandam pra um serviço chamado Blender.</p>
<p>Blender foi um front-end escrito em Java em cima do Netty, que é a grosso modo o equivalente a um Node.js pra Java. Por baixo usa o lendário Apache Lucene como motor de procura. Lucene é feito em Java e a gente usa faz anos. Toda vez que um site tiver um campo de pesquisa, e os resultados forem bons, pode ter certeza que tem Lucene por trás. O produto mais famoso que é construído em cima do Lucene é o Elastic, também conhecido como Elasticsearch, ou o Solr da própria Apache. Full text search, correção ortográfica, auto complete, tudo isso já é problema resolvido por Lucene, Solr ou Elastic.</p>
<p>Só encontrei um post no blog de engenharia do Twitter e outro post de um ex-programador que trabalhou no Twitter por volta de 2012, mas além do Blender existia um outro sistema relacionado à parte de pesquisa em tempo real, que é diferente do sistema de recomendação, chamado Earlybird. E eu menciono isso porque &ldquo;Earlybird&rdquo; é outro termo que aparece bastante no código que foi liberado recentemente. Então agora você sabe.</p>
<p>Aproveitando que comecei a falar de projetos open source do Twitter, na parte do paper onde ele explica o desenvolvimento da solução GraphJet, falei que tem uma seção que fala da estratégia de IDs. Isso gerou outro projeto open source chamado Snowflake, que eu não conhecia também. Mas em dados distribuídos, IDs únicos é um problema. Por exemplo, numa tabela de banco de dados MySQL, rodando na sua máquina local, você coloca um inteiro auto incremental e esquece. Cada nova linha vai ganhando um ID como 1, 2, 3 &hellip; 10, 11, 12 &hellip; 1001, 1002 e assim por diante.</p>
<p>Ids incrementais tem diversos problemas, um deles é segurança, mas o outro é conseguir particionar esses IDs. E quando eu tenho um banco de dados distribuídos em 2 servidores? Vou só dizer que no primeiro servidor vai de 1 a 1 milhão, no segundo servidor vai de 1 milhão a 2 milhões? Isso não escala. Se você for mais esperto vai lembrar, ah, podemos usar coisas como UUID ou GUID da Microsoft, IDs de 64 bits ou 128 bits aleatórios. Ou até espaços menores de 48 bits, como MAC address de placas de rede.</p>
<p>Quanto maior o ID, mais espaço vai ocupar em disco e quando carregar em memória. E se vamos desperdiçar esse espaço, o ideal é que não seja aleatório e sirva algum propósito. Daí temos IDs semi-aleatórios. No caso da estratégia do Snowflake o ID é um número aleatório prefixado com um timestamp, um identificador do servidor onde está, e uma sequência numérica aleatória. Dessa forma consigo agrupar tweets que foram criados num determinado período de tempo, tenho a localização no cluster de servidores, mas também sei que não vai ter colisão desse ID com outro ID em outro servidor. Quer dizer, até pode ter colisão mas a probabilidade é hiper baixa, perto de zero, na prática.</p>
<p>Menciono isso porque se procurar no VS Code, olha só, Snowflake aparece em diversos lugares do código. E achamos trechos que usam a função <code>timeFromIdOpt</code> onde passamos um ID de Snowflake e ele retorna um timestamp, daí podemos fazer operações, como ver se foi criado antes ou depois de determinada data. Se estudar bancos de dados distribuídos, vai ver como existem diferentes estratégias de IDs únicos pra diferentes necessidades.</p>
<p>Um banco de dados como Cassandra usa outra estratégia, chamada TimeUUID, que é muito similar, já que também tem um componente de timestamp embutido no ID, o que facilita fazer queries de períodos de tempo e análise de time series, séries de tempo e processamento de eventos. Um DynamoDB da Amazon tem um identificador único chamado de &ldquo;chave de partição&rdquo; que é usado pra particionar dados em múltiplos servidores no cluster. A chave de partição é combinado com uma &ldquo;chave de ordenação&rdquo;, que é usado pra ordenar os dados dentro de uma partição.</p>
<p>Sem fugir demais do assunto, mas já que falei disso, preciso pelo menos explicar outro conceito. Em bancos de dados distribuídos, um problema seria se um servidor ficar mais cheio que outros servidores. Idealmente queremos balancear o volume de dados entre cada servidor. Uma das formas de fazer isso é o que chamamos de &ldquo;Consistent Hashing&rdquo;. Esse algoritmo mapeia chaves de partição, como o Snowflake do Twitter ou TimeUUID do Cassandra em nós virtuais num anel, um ring. Cada nó virtual representa um servidor físico no cluster.</p>
<p>À medida que novos nós são adicionados ou removidos do cluster, os ranges de partição são redistribuídos por todos os nós virtuais, garantindo distribuição uniforme das chaves de partição. Novamente, é um problema bem conhecido e bem resolvido nos mais diversos bancos de dados distribuídos. Eu lembro quando líamos papers da Amazon sobre isso lá por 2008 e 2009. Mas pra um estudante de ciência da computação hoje, continua sendo um problema fascinante de entender, já que o conceito é bem simples. Um ID é muito mais do que só uma etiqueta qualquer. Ela serve pra facilitar pesquisas e pra facilitar balanceamento de sistemas distribuídos.</p>
<p>E pra terminar essa parte de open source, tem outros dois projetos que são mencionados que acho importante explicar. O primeiro se chama Thrift e o segundo é Protocol Buffers ou Protobufs. Acho que muitos de vocês já devem ter ouvido falar pelo menos de Protobufs. Bem a grosso modo, pense micro-serviços ou endpoints de APIs hoje. A maioria de vocês deve estar acostumado a ter endpoints que se comunicam ou simplesmente cospem dados em formato JSON. É o hello world de APIs em todo tutorial e curso. Só que JSON não é a única forma e pra muitos casos, é a pior opção.</p>
<p>JSON é um formato excelente pra endpoints de APIs que seres humanos como eu ou você vamos manipular. É indicado pra coisas como APIs de ecommerce, onde queremos puxar coisas como principais ofertas, pra mostrar no nosso blog. Mas pra comunicação entre sistemas, por exemplo, o backend do ecommerce conversar com meios de pagamento ou logística. Ou mesmo pra comunicação entre micro-serviços dentro da mesma empresa, é um dos protocolos mais porcaria que existem.</p>
<p>Como um exemplo bem, mas bem tosco, imagine este pequeno JSON de um produto de ecommerce. Quem já trabalhou com ecommerce sabe que é bem maior que isso, mas pra ilustrar aqui no video é suficiente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;product_id&#34;: &#34;PS5-001&#34;,
  &#34;name&#34;: &#34;PlayStation 5&#34;,
  &#34;description&#34;: &#34;The PlayStation 5 is the next generation of gaming consoles, with advanced graphics, haptic feedback, and lightning-fast load times.&#34;,
  &#34;price&#34;: 499.99,
  &#34;availability&#34;: true,
  &#34;images&#34;: [
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-front.jpg&#34;,
      &#34;type&#34;: &#34;front&#34;
    },
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-back.jpg&#34;,
      &#34;type&#34;: &#34;back&#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>JSON é um protocolo texto, onde o schema vai embutido junto com cada registro. Como falei, isso facilita pra pessoas comuns conseguirem ler sem muita dificuldade. Agora imagine um schema de Protobuf assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>message Product {
  string product_id = 1;
  string name = 2;
  string description = 3;
  float price = 4;
  bool availability = 5;
  repeated Image images = 6;
}

message Image {
  string url = 1;
  string type = 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>Note, ele define exatamente que tipo de dados, portanto qual o tamanho exato, de cada campo de dados. Dessa forma não preciso de coisas como aspas, vírgulas, nada disso, posso concatenar um valor atrás do outro, porque o tamanho vai ser fixo. No final, o mesmo JSON, convertido em Protobuf vai gerar um linguição binário que, em hexadecimal, seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>0a 07 50 53 35 2d 30 30 31 12 10 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 1a 42 54 68 65 20 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 20 69 73 20 74 68 65 20 6e 65 78 74 20 67 65 6e 65 72 61 74 69 6f 6e 20 6f 66 20 67 61 6d 69 6e 67 20 63 6f 6e 73 6f 6c 65 73 2c 20 77 69 74 68 20 61 64 76 61 6e 63 65 64 20 67 72 61 70 68 69 63 73 2c 20 68 61 70 74 69 63 20 66 65 65 64 62 61 63 6b 2c 20 61 6e 64 20 6c 69 67 68 74 6e 69 6e 67 2d 66 61 73 74 20 6c 6f 61 64 20 74 69 6d 65 73 2e 20 32 25 15 01 20 f3 26 9d 3a 40 28 01 32 2b 0a 1f 68 74 74 70 73 3a 2f 2f 65 78 61 6d 70 6c 65 2e 63 6f 6d 2f 69 6d 61 67 65 73 2f 70 73 35 2d 30 30 31 2d 66 72 6f 6e 74 2e 6a 70 67 22 04 66 72 6f 6e 74</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 que a nova API com Protobuf vai cuspir. Como disse antes, ao contrário do JSON, esse formato é 100% hostil pra um ser humano conseguir ler, porém, é 100% perfeito pra um computador ler. A fase de converter o JSON numa estrutura de dados é muito mais simples. Um protobuf é muito próximo de uma struct de C, só que mais genérico pra funcionar em qualquer linguagem. O JSON do exemplo é um string que pesa quase 200 bytes. Já esse linguição, que contém exatamente os mesmos dados, pesa menos de 80 bytes. Mesmo nesse exemplo super tosco, estamos falando de uma economia de 2.5 vezes na transferência de rede, sem contar a economia de processamento.</p>
<p>Lembra que falei que o Twitter lida com milhões de tweets todo dia? Imagine se internamente, em todo sistema, de pesquisa, banco de dados distribuído, sistema de recomendação, filas de processamento, ficasse trafegando tudo em JSON. Terabytes e terabytes de banda sendo desperdiçados. Por isso o Twitter adotou o equivalente a Protobufs do Google, o Thrift do Facebook. Thrift e Protobuf é quase a mesma coisa.</p>
<p>A única pergunta que você poderia ter é: por que o Twitter não usou o Protobuf do Google então? Em vez de reinventar a roda? De fato, Protobufs foram criados antes, sendo usados dentro do Google desde pelo menos 2001, mas só foi lançado publicamente em 2008. Nesse meio tempo surgiu o Facebook, que teve problemas similares pra resolver e inventaram o Thrift, que lançaram em Abril de 2007. Por isso o Twitter só tinha a opção do Thrift em 2007. Mas pra novos projetos, especialmente que interagem com componentes como PyTorch ou Tensorflow, que também veio do Google, eles passaram a usar Protobufs e você vai achar os dois no código.</p>
<p>Thrift e Protobufs são protocolos de serialização de dados. Esse tipo de protocolo é ideal pra comunicação entre micro-serviços. Só que diferente de JSON ou XML, a forma de fazer essa comunicação não é via HTTP, como num web service, e sim via RPC ou Remote Procedure Call, que formaliza como um serviço chama uma função, ou procedure, de outro serviço. No caso do Google eles tem o gRPC que é um framework de RPC que tem como objetivo prover comunicação eficiente e escalável entre serviços na rede.</p>
<p>Twitter não usou gRPC. Finagle é o equivalente de gRPC feito pelo Twitter, que usa Thrift pra serialização de dados em vez de Protobufs. Tem os mesmos objetivos de lidar com comunicação de alto throughput, baixa latência, na rede. Finagle é implementado em cima de Netty também, usando muita funcionalidade de programação assíncrona com Futures de Scala, que é o equivalente Promises de Javascript de hoje. E novamente, o Twitter Finagle foi lançado em 2011, o gRPC do Google só sairia por volta de 2015. O Twitter estava tendo que inventar tecnologias que não existiam ainda, por isso é um case fascinante.</p>
<p>Mas claro, Google sendo um nome muito maior e mais reconhecido que Twitter, matou os projetos Thrift e Finagle. Num projeto moderno, você deveria escolher Protobufs e gRPC ou alternativas mais modernas. Hoje, o Thrift ainda existe como Apache Thrift mas da Apache temos outras alternativas como Apache Avro, que foi criado mais pro cenário de streaming de dados serializados. Ou seja, quando você abre um streaming de um serviço pra outro serviço e trafega registros serializados com Avro entre eles.</p>
<p>Apache é um nome muito reconhecido pra quem é de Java e também no espaço de data sciences e machine learning. Quem lida com Kafka e é de Java, deve ter usado Avro como opção de serialização pra jobs em vez de Protobufs ou JSON. JSON é mais pra linguagens menores, como Javascript ou mesmo Ruby e PHP. Protobufs são mais usados em soluções que envolvam tecnologias do Google, como em Android. De qualquer forma, vale a pena estudar todas. Não ache que o mundo acaba em JSON ou XML. Tem coisa muito melhor e muito mais eficiente que é usado por sistemas realmente de grande volume. Pro ecommerce da esquina, de fato, tanto faz.</p>
<p>Falando em Java, acho que vale a pena eu relembrar a parte inicial dessa história. O que aconteceu justamente entre 2007 a 2010. Essa era a época que eu era mais participativo na comunidade Ruby on Rails e, claro, Twitter era um dos exemplos de maior sucesso de Rails. Mas aí veio a notícia bombástica: o Twitter declarou que estava migrando de Ruby pra Scala. E daí veio o meme que não morre de &ldquo;Rails não escala&rdquo;.</p>
<p>A forma como o Alex Payne, um dos principais desenvolvedores do Twitter descreveu, ou melhor, não descreveu os problemas que estavam enfrentando, tudo que eu expliquei até aqui, causou má interpretação e muita gente falando muita merda. Eu conversei com outro dos principais desenvolvedores daquela época, Blaine Cook, em junho de 2008. E mesma naquela época, eu também não tinha uma visão completa dos acontecimentos. Depois leiam a entrevista original que está no meu blog.</p>
<p>Por exemplo, da forma como Alex Payne falou, deu impressão que ele estava dizendo que Ruby on Rails não suportava conectar com múltiplos bancos de dados, o que não era verdade. Sim, não havia uma chavinha fácil, mas era basicamente uma linha extra que precisava ser colocada na classe pai de todos os ActiveRecords e dava pra selecionar onde fazer a conexão. Eu e vários outros escrevemos tutoriais de como fazer isso.</p>
<p>Mas a verdade é que o problema não era esse. Ninguém de fora do Twitter tinha muita noção do volume de dados que eles estavam tendo que lidar. E isso era 2007. Lembrem-se, a Amazon AWS tinha lançado não fazia um ano. Produtos que hoje são arroz com feijão, como SQS, DynamoDB, Route53 nem tinham sido lançados ainda. Firebase ainda não existia. Porra, coisas como Redis ainda não tinha sido inventado. GitHub ainda não tinha sido inventado.</p>
<p>A verdade é que particionar dados, organizar um MySQL em shards, e conseguir fazer uma arquitetura coesa em cima disso, não era trivial, e o Twitter estava numa fase de crescimento rápido. Não é que Ruby on Rails não escalava pra escala do Twitter. Nada em Python escalaria. Nada em PHP escalaria. Node.js ainda não tinha sido inventado também, mas também não escalaria. Entenda, havia poucas opções.</p>
<p>Possivelmente a opção mais completa teria sido começar a implementar um banco de dados de grafos em cima de Erlang, algo inspirado no banco de dados Mnesia que vem no OTP de Erlang. Porém, já viram Erlang? Hoje ele ainda é pouco usado, e em 2007 menos ainda. É verdade que grandes sistemas de telecomunicações já usavam fazia anos, mas era um salto bem grande pra uma tech startup tomar.</p>
<p>Quais as alternativas? Eles precisavam de uma linguagem pra escrever componentes como sistemas de filas, coisas no nível de Kafka, lembrando que Kafka só seria lançado em 2011. Coisas no nível de Cassandra, que também só seria liberado publicamente pelo Facebook em 2008. Neo4J talvez fosse o componente mais próximo de se encaixar nos problemas do Twitter, mas era super novo, tinha sido lançado em 2007.</p>
<p>Entendem? Imagine você sendo um desenvolvedor do Twitter, vendo milhares, centenas de milhares de tweets sendo postados todos os dias, e tendo que lidar com grafos de usuários e tweets, sendo que algoritmos de grafos nunca tinham sido testados com volumes desse tipo. Numa época onde toda tecnologia de Cloud e NoSQL e Devops ainda não tinha sido inventado, onde linguagens mais comuns como Ruby, Python, PHP, Javascript, não estavam ajudando e programar em C++ ou Java, parecia ser um passo pra trás.</p>
<p>Elixir não tinha sido inventado ainda. Go não tinha sido lançado ainda. Rust não tinha sido inventado ainda. LLVM ainda era uma grande novidade, já que a Apple tava só começando a mover suas ferramentas e linguagens de GCC pra Clang, como conto no episódio da Apple e GPL. Mas havia uma única linguagem que talvez pudesse fazer sentido, a linguagem Scala, que tinha sido lançado já em 2001, pelo visionário Martin Odersky.</p>
<p>Odersky é um desses gênios incompreendidos, tanto que tirando povo da área de Java, duvido que qualquer um aqui já tenha ouvido falar dele. Mas deveriam. O cara é Ph.D de ciência da computação pela ETH de Zurique e trabalhou sob a supervisão de ninguém menos que Niklaus Wirth, o inventor de várias linguagens como Modula-2 e Pascal, e o autor do livro de estruturas de dados e algoritmos que eu usei na faculdade.</p>
<p>Odersky participou e fez contribuições no mundo Java desde a versão 1.1, tendo trabalhado no compilador, na implementação de Generics, e algumas linguagens experimentais como a Pizza e Funnel, que influenciaram o design do Java.</p>
<p>Muito antes de programação funcional ficar hypezinho de hipster sem graça, como é hoje, no começo do século, ele já vislumbrava uma linguagens que destilasse o melhor da orientação a objetos, como usar traits, que são como protocolos de Objective-C, em vez de herança excessiva, mesclado com conceitos de programação funcional. Scala já tinha higher order functions, já usava tipo Opcional, evitando os famosos NullPointerException, pattern matching e tudo que se vê em linguagens mais modernas de hoje.</p>
<p>Então era de fato a linguagem mais moderna que se podia escolher em 2007, mas rodando em cima da JVM, que era a virtual machine mais madura que existia. Dava pra criar aplicações que só seriam possíveis em C++ com uma fração da dor de cabeça. Pra isso custaria um pouco mais caro em uso de recursos do que se fosse puramente nativo, mas a JVM naquele ponto oferecia um ecossistema que ninguém mais tinha.</p>
<p>E o objetivo do uso de Scala não era pra fazer front-end-zinho, era pra fazer os diversos componentes que eu expliquei que ainda não existiam. Começou com o projeto open source Starling, que é mais ou menos um mini Kafka, alguns anos antes do Kafka. Inventaram o RealGraph e GraphJet antes de um banco de dados de grafos maduro existir. Utilizaram coisas como Hadoop, que também faz parte do ecossistema Java. Nada disso seria possível sem ser, ou em Java ou em C++.</p>
<p>Hoje em dia eles teriam muito mais opções, como o próprio Elixir, Go ou Rust, e se quisessem arriscar em linguagens mais exóticas teriam D, Nim, Zig, talvez Swift, Kotlin. Mas de novo, era 2006 pra 2007 e o relógio tava correndo. Em retrospecto, foi uma decisão muito acertada. E é por isso que o código que foi liberado é 2 terços Scala e Java. Muito das pesquisas que o Twitter fez, junto com trabalhos do Facebook, Google, Apple, Amazon, gerou o atual ecossistema de linguagens, ferramentas e plataformas pra lidar com Big Data, Data Science, Machine Learning, culminando com Deep Learning e a atual geração de inteligência artificial que todo mundo tá babando. Mas foi assim que as coisas começaram, no início do século.</p>
<p>Agora que temos uma noção melhor do contexto histórico, dos projetos e papers de pesquisa gerados, podemos começar a entender melhor o código liberado. Esse código é um conjunto de diversos sub-projetos. Se abrirmos o primeiro README na raíz do repositório, eles descreveram o que são cada um desses projetos. Por exemplo, SimClusters-ann é o serviço que retorna candidatos de recomendações de tweets dado embeddings de um cluster de similaridade.</p>
<p>Esse serviço usa o algoritmo de similaridade aproximado de cossenos. Já vou explicar isso. Segundo o README, um job constrói o mapeamento entre SimClusters e Tweets. O job grava os top 400 tweets de um SimCluster e os top 100 SimClusters pra um tweet. Pontuação de favoritos e pontuação de seguidores são dois tipos de pontuação que um tweet pode ter. Os top 100 SimClusters baseados nessas pontuações é que chamam de Tweet SimCluster Embedding.</p>
<p>A similaridade de cosseno entre dois Embeddings apresenta o nível de relevância de dois tweets num espaço de SimClusters. A pontuação varia de 0 a 1. A alta pontuação de similaridade de cosseno, maior que 0.7 significa que os usuários que gostam de dois desses tweets compartilham os mesmos SimClusters.</p>
<p>Similaridade de cosseno é um conceito de Álgebra Linear pra medir quão similar são dois vetores em termos de direção e magnitude. Eu gosto desse conceito porque expliquei isso desde pelo menos 2015 até minha última palestra pública em 2019. Quem assistiu minha palestra &ldquo;Restrição == Inovação&rdquo; vai se lembrar disso, e vou aproveitar pra usar os slides que eu usava nessa palestra.</p>
<p>Comece visualizando a página inicial do Google, o campo onde se digita palavras-chaves. Podemos digitar &ldquo;apple&rdquo; e recebemos páginas indexadas da web que tem essa palavra. Um amador que acabou de aprender SQL poderia pensar que é alguma coisa parecido com &ldquo;select urls from pages where text like &lsquo;%apple%&rsquo;&rdquo;.</p>
<p>Essa é a solução que faríamos no começo dos anos 90, antes de Larry Page e Sergey Brin inventarem PageRank, antes de Jon Kleinberg inventar HITS. Essa linha de SQL de fato vai achar todas as páginas onde aparece a palavra &ldquo;apple&rdquo;, mas não vai saber como ordenar: qual delas é mais relevante e deveria estar no topo da lista? Pra saber relevância, precisamos de Álgebra Linear.</p>
<p>Tudo depende de como você enxerga o que é uma página web. Todo mundo enxerga um documento, uma coleção de palavras, que é o texto desse documento. Digamos que nosso banco de dados só tem 3 documentos indexados, como neste slide. Pra simplificar bastante, vamos assumir que nosso vocabulário é bem limitado e os documentos só tem as palavras apple e banana. O documento 1 tem 4 apples e 1 banana, documento 2 tem 3 bananas e 1 apple, e documento 3 tem 5 bananas e nenhum apple.</p>
<p>Podemos representar esses documentos como vetores, como  neste slide. Próximo passo, criamos um índice, que é uma lista que diz em quais documentos aparece a palavra apple e quais aparece banana. Pense em algo como o índice do seu banco de dados MySQL ou Postgres ou SQL Server da vida. Agora vamos digitar a palavra &ldquo;apple&rdquo; no campo de pesquisa desse nosso Google de mentira.</p>
<p>O truque começa em representar essa pesquisa, essa query, também com um vetor. No caso apple sendo 1 e banana sendo 0. É como se fosse um documento também. Pelo índice, podemos descartar o documento 3, já que apple não aparece nenhuma vez. Mas ainda temos o documento 1 e o documento 2. Qual deles é o mais relevante pra aparecer no topo da lista? De bater o olho sabemos que é o documento 1, porque aparecem a palavra apple 4 vezes, e no documento 2 só aparece 1 vez. Mas como um algoritmo conseguiria computar isso?</p>
<p>Pra isso desenhamos esses vetores num espaço vetorial, um Vector Space. No caso só temos duas dimensões, porque só temos duas palavras no nosso vocabulário. Apple é o eixo X e banana é o eixo Y. Então o vetor que representa o documento 1 seria assim: 4 unidades pra direita no eixo X, e 1 unidade pra cima no eixo Y. Já o documento dois seria só 1 unidade pra direita no eixo X mas 3 unidades pra cima no eixo Y. Finalmente o vetor da query é só uma unidade pra direita no eixo X, só 1 palavra apple, entenderam?</p>
<p>O lance com vetores é que agora temos ângulos entre o vetor de pesquisa e os vetores de documentos. E pra calcular a similaridade de cosseno usamos dot product, que é produto escalar. Funciona assim: vamos multiplicar apple com apple, banana com banana e somar os dois. Então vetor de pesquisa é 1 e 0, 1 apple e 0 bananas e vetor do documento 1 é 4 apples e 1 banana então a conta seria 1 vezes 4 mais 0 vezes 1, que dá 4, esse é o produto escalar.</p>
<p>Daí fazemos a mesma coisa entre vetor de pesquisa, de novo, e o vetor do documento 2, então o produto escalar vai ser, 1 apple vezes 1 apple mais 0 banana vezes 4 bananas, dando 1. Como o produto escalar 4 do documento 1 é maior que o 1 do documento 2, sabemos que o documento 1 tem mais similaridade com a pesquisa e, portanto, é o mais relevante pra aparecer no topo da lista.</p>
<p>Esse exemplo é super trivial porque só usamos duas palavras, então temos só duas dimensões. Mas vamos complicar. E se tivéssemos um novo documento indexado que tenha a palavra coco? O vetor dele poderia ser 1 apple, 0 banana e 2 cocos. Todos os outros vetores precisam ser atualizados pra conter coco, mas no caso tem 0 cocos então não muda nada. Pra representar o espaço vetorial, precisamos de uma nova dimensão Z. O vetor desse novo documento seria desenhado com 1 unidade pra direita no eixo X e 2 unidades pra baixo no eixo Z.</p>
<p>E podemos calcular o produto escalar entre a pesquisa e esse novo documento também. Em geometria, a gente consegue desenhar no papel até 3 dimensões, mas claro, o vocabulário de uma língua como português é muito mais que só 3 palavras. Num dicionário como o Aurélio, vai ter mais de 500 mil palavras. Portanto podemos ter potencialmente 500 mil dimensões nesse espaço vetorial. Na realidade vai ser a quantidade de palavras únicas que tem num documento, ou página web de verdade. O script deste episódio, por exemplo, tem mais de 3 mil palavras únicas. É um vetor de 3 mil dimensões.</p>
<p>Felizmente, produto escalar é uma operação muito rápida de se calcular em qualquer computador moderno, só somas e multiplicações simples. E é assim que funciona a base de todo tipo de algoritmo de relevância e recomendação. É isso que tem por baixo da biblioteca Apache Lucene que falei antes. E se olhar a documentação de um Elastic vai ver que menciona VSM, ou Vector Space Model, que é esse gráfico que vim mostrando, muito maior do que os 3 do exemplo. E é isso que esse projeto SimClusters vai fazer, só que com vetores de usuários e tweets.</p>
<p>No caso do Twitter, esse primeiro projeto se chama SimCluster-ANN. SimCluster é similarity cluster, e esse &ldquo;ANN&rdquo; provavelmente significa &ldquo;Approximate Nearest Neighbor&rdquo; ou vizinho mais próximo aproximado. Em machine learning e data mining, pesquisa de vizinho mais próximo é o problema de encontrar o ponto de dado mais próximo em um espaço de muitas dimensões, dado um certo ponto de pesquisa, onde soluções exatas são inviáveis pelo tamanho do conjunto de dados.</p>
<p>Algoritmos de vizinho próximo aproximado, como Locality-sensitive hashing ou LSH e métodos baseados em árvore como KD-trees, oferecem uma forma eficiente de resolver esse problema fazendo um trade off de algum grau de precisão por uma melhoria significativa em performance. Então é mais uma otimização do Twitter pra ganhar performance, mais ou menos como o random walk no algoritmo de SALSA que expliquei antes.</p>
<p>Nesse sub-projeto, o conceito importante são embedding spaces, que tem como objetivo responder a pergunta &ldquo;quais tweets e usuários são similares aos meus interesses?&rdquo;. Embedding funciona gerando representações numéricas dos interesses dos usuários e conteúdo de tweets. Daí podemos calcular a similaridade entre quaisquer dois usuários, tweets ou pares de usuários e tweets num espaço de embedding.</p>
<p>Justamente um dos espaços de embeddings mais úteis do Twitter são os SimClusters. SimClusters descobrem comunidades ancoradas por um grupo de usuários influentes usando um algoritmo customizado de factorização de matrizes. Existem umas 145 mil comunidades, que são atualizadas a cada três semanas. Usuários e tweets são representados em espaço de comunidades e podem pertencer a múltiplas comunidades. Elas podem ter algumas milhares até milhões de usuários. Segundo o blog de engenharia, essa imagem representa algumas das maiores comunidades.</p>
<p>Sem entrar em muito mais detalhes, vamos bater o olho em alguns outros projetos descritos naquele arquivo README. Veja um aqui chamado &ldquo;real-graph&rdquo;. Eu falei de RealGraph antes. Veja este &ldquo;Tweepcred&rdquo;, que é o algoritmo de PageRank pra calcular reputação dos usuários. Temos esse &ldquo;recos-injector&rdquo; que é descrito como um processador de eventos em streaming pra construir os streams de entrada pra serviços baseados em GraphJet. Já sabemos o que é GraphJet, mas &ldquo;recos&rdquo; eu acho que significa &ldquo;recomendações&rdquo;, então é um injetor de recomendações pra GraphJet.</p>
<p>Pulando lá pra baixo temos esse &ldquo;light-ranker&rdquo; que é um ranqueador mais leve usado pelo índice de pesquisa, o tal Earlybird que mencionei antes, que é um projeto legado e parece que ainda é usado; o componente construído em cima de Apache Lucene. O heavy-ranker está no outro repositório &ldquo;the-algorithm-ml&rdquo;, que o blog post do Steven, que falei lá no começo, menciona e segundo o README é o responsável pela aba &ldquo;Pra você&rdquo; e tem aquela lista de pesos.</p>
<p>Nesse ponto da cadeia, depois da fase de candidate sourcing, temos uns 1500 candidatos que podem ser relevantes. Pontuação serve pra prever a relevância de cada tweet candidato e é o sinal primário pra ranquear tweets na timeline. Nesse estágio, todos os candidatos são tratados igualmente. O ranqueamento sai de um rede neural de uns 48 milhões de parâmetros que é continuamente treinada com interações de tweets pra otimizar o engajamento positivo (como likes, replies e retweets), segundo o blog de engenharia do Twitter.</p>
<p>Sendo bem honesto, eu não tenho certeza de como exatamente isso funciona ainda. Olha o diagrama que tem no próprio arquivo README que lista os projetos. Os dados vem dessas caixinhas intituladas &ldquo;Grafo Social&rdquo;, &ldquo;engajamento de tweets&rdquo; e &ldquo;dados dos usuários&rdquo;. Daí temos features, como o GraphJet, SimClusters, TwHIN que é outro projeto no repositório the-algorithm-ml, e serve pra pré-treinar os tais embeddings.</p>
<p>Daí temos o RealGraph, TweepCred e Trust &amp; Safety, que parece ser um projeto de tensorflow pra treinar modelos pra filtrar coisas como conteúdo pornográfico, adulto, ou o que ele considera como &ldquo;tóxico&rdquo;. Não sabemos exatamente porque não foi liberado o modelo e nem os dados de treino. É um projeto bem pequeno e quem entende de Tensorflow poderia dizer um pouco mais, mas não tem muito pra se ver.</p>
<p>Continuando, temos essa coluna que ele chama de Home Mixer. Daí temos 3 fases: uma que ele chama de candidate sourcing, que é literalmente &ldquo;escolher os candidatos&rdquo;, candidatos de recomendação. Esses candidatos passam pelo tal heavy ranker, que parece ser o modelo de machine learning via PyTorch. O resultado disso passa pra etapa final de heurísticas e filtros.</p>
<p>Então, pra se chegar nos tweets recomendados na aba &ldquo;Pra você&rdquo;, na etapa de sourcing já acontece diversos filtros pra selecionar os candidatos de tweets que você pode gostar de ver. Esses &ldquo;filtros&rdquo; passam pelos projetos de SimCluster, RealGraph, T&amp;S, Tweepcred, os GraphJets e são mesclados pelo projeto cr_mixer. Isso vale comentar. Os tweets recomendados, mais ou menos, são metade de tweets gerados pelas pessoas que você segue, o que ele chama de &ldquo;In-Network&rdquo; e a outra metade é de tweets de pessoas que você não segue, que ele chama de &ldquo;Out-of-Network&rdquo; e quem puxa isso é esse projeto cr-mixer.</p>
<p>Dado esse conjunto inicial de tweets, In e Out of network, aí vem a parte de recomendação propriamente dito usando o light ranker, o projeto Earlybird, e daí o heavy ranker. Mas antes de mandar pra você, ainda passa por um pipeline de filtros. Pelo projeto home mixer propriamente dito, esse visibility-filters que é onde está implementado a política de censura do Twitter.</p>
<p>Por último, tem esse projeto Navi, que parece mais recente, escrito em Rust, mas é super pequeno e não faz muita coisa. Parece mais um adaptador pra falar com os componentes de Tensorflow e PyTorch e um tal de Onnx, que eu não conhecia, mas parece que significa Open Neural Network Exchange, que é um formato open source pra representar modelos de machine learning. Foi originalmente desenvolvido pela Microsoft e Facebook em 2017 pra facilitar interoperabilidade entre diferentes frameworks de deep learning, por exemplo, entre PyTorch e Tensorflow. Mas realmente não sei como ele é usado neste projeto ainda.</p>
<p>Se tiverem interesse em saber mais, cada um desses sub-projetos tem um README também, então recomendo que comecem por eles. Mas como disse no começo, se tentar rodar <code>bazel build</code> em qualquer diretório, ele vai reclamar da falta de um arquivo de Workspace. Então, até o Twitter liberar isso, ou alguém se dar o trabalho de gerar na mão, não temos como compilar e nem de tentar rodar nada.</p>
<p>Por isso que é meio chato, porque mesmo lendo essas coisas todas na documentação e no próprio código, não temos como testar. Basta um if que a passamos batido e talvez algum desses componentes nem rodem. É muito difícil saber o comportamento exato dum sistema grande desses sem ter uma massa de dados de entrada pra ver o que realmente é cuspido na saída.</p>
<p>De qualquer forma, do ponto de vista de organização, além da falta de arquivos pra fazer build, também senti muita falta de arquivos de testes automatizados. Não tem como saber se eles tiraram antes de abrir em público, se ainda vai ser liberado, ou se realmente não existem. Espero que existam, porque dar manutenção num sistema grande desses, sem testes, seria uma grande burrice.</p>
<p>Se eles tivessem liberado os arquivos de testes, nós teríamos uma idéia muito melhor de como esse sistema de comporta, mesmo sem poder compilar e rodar. Testes servem como se fosse uma documentação viva de um sistema. Teria sido muito mais fácil confirmar tudo que analisamos.</p>
<p>Mas no geral, minha opinião é que o código Scala parece muito bem feito, dado o contexto que se trata de um legado de mais de 10 anos. Eu esperaria algo muito mais bagunçado, muito mais macarrônico. Comparado com o código do Twitch, que eu analisei em outro video quando foi vazado, eu preferiria muito mais trabalhar neste código do que no outro.</p>
<p>O básico tá bem feito: métodos curtos, baixa complexidade ciclomática, nomenclatura bem feita, nada de variáveis &ldquo;x&rdquo; ou &ldquo;y&rdquo; que não dá pra saber do que se trata. Pouca coisa hard-coded, tudo organizado em arquivos de configuração, e até que bem comentados onde precisa. Não é comum ver projetos legados com esse nível de qualidade. Não é nem de longe perfeito, mas até que me surpreendeu.</p>
<p>Obviamente, esse repositório é só um espelho incompleto do repositório de verdade, que permanece fechado dentro do Twitter, sob confidencialidade. O código que foi liberado também não pode ser usado por nenhum projeto fechado sem liberar as modificações feitas em cima desse código. É isso que a licença AGPL que eles colocaram significa: eles não querem que ninguém adicione em outro projeto e mantenha fechado.</p>
<p>Depois que soltaram esse código, um bando de imbecis ficou spamando a seção de Issues, mas teve um ou outro pull request que eles aprovaram já, nada significativo. Só a remoção daquele código de métrica que tinha o &ldquo;author_is_elon&rdquo; que imbecis adoraram ficar fazendo meme. Não teve nenhuma contribuição significativa ainda e nem espero que vá ter, até que eles liberem os arquivos de Workspace do Bazel.</p>
<p>Eu não tenho muito senso de humor pra memes idiotas e gente que acha bacana ficar pichando uma seção que deveria ser séria como a área de Issues. Quer fazer meme idiota, fica no Reddit ou no 4chan. Não tem o mínimo de etiqueta, não participa. Participação desse tipo é só idiota, não tem nenhum valor e não contribui com absolutamente nada. É só certificado de idiota mesmo.</p>
<p>Ao longo de mais de 10 anos, o Twitter fez sua parte e contribuiu tecnicamente. Os papers que falei, os projetos open source, os diversos blog posts de engenharia, inspiraram muitos outros projetos a tentar arquiteturas e tecnologias que, na época, sequer existiam ou se existiam, ninguém sabia se funcionava. Independente da sua opinião política, que é irrelevante, o Twitter certamente contribuiu ordens de grandeza mais do que qualquer um dos engraçadinhos que não sabem respeitar um trabalho de engenharia.</p>
<p>Ter acesso a esse código não me torna mais fã do Elon Musk, mas só serve pra confirmar muitas coisas que eu já sabia sobre os primeiros engenheiros do Twitter: eles foram muito bons, especialmente pra época. E pra vocês que são iniciantes, aproveitem a oportunidade pra fuçar tanto o código quanto os papers que eu citei. É assim que um produto em produção evolui. Só nesse sistema de recomendações, vimos 3 gerações diferentes de soluções, cada uma gerando mais experiência pra fazer o próximo ser melhor. Código de produção de verdade, de um produto que tem uso, nunca acaba.</p>
<p>Você que é empreendedor querendo entender mais de programação, entenda: se seu código não está evoluindo, é porque seu produto é irrelevante. O Twitter existe desde 2006, e o código nunca chegou numa versão que todo mundo tirou a mão e falou &ldquo;tá perfeito, não precisa mexer mais&rdquo;. O mesmo acontece com qualquer produto, ecommerce, elearning, fintech, todos continuam evoluindo, pivotando, ajustando e consertando pra ficar cada vez melhor. O Twitter de hoje é bem diferente do Twitter de 2007, mesmo que só pelo front-end não pareça que tenha mudado muita coisa. A cara não muda tanto quanto o backend.</p>
<p>Dá pra ficar literalmente dias, indo de arquivo em arquivo, tentando desvendar quem chama quem, quem depende de quem, em que momentos, quais serviços rodam pra quais tipos de dados. Mas é super trabalhoso fazer isso sem conseguir rodar nada e sem testes. Eu não acho que vale a pena, mas na internet sempre tem alguém com mais boa vontade de fazer trabalho chato como esse. Com apenas 2 dias olhando pra esse código, eu só consigo descrever até aqui.</p>
<p>E com tudo isso, pelo menos espero que pra vocês que sejam iniciantes, tenham tido uma perspectiva diferente do que imaginavam que seria um código de verdade de produção. Mesmo sem entender nada, deve dar pra começar a entender melhor a dimensão. Repetindo: esse repositório liberado só tem parte do código do sistema de recomendação. Não tem o front-end web, não tem o app mobile, não tem as ferramentas de administração, não tem o ferramental de monitoramento, enfim, só estamos vendo uma fração do que é feito o Twitter.</p>
<p>Antes de terminar, enquanto estava escrevendo este episódio, o canal Fireship lançou também um video curto falando disso. Não tem nada demais, mas eles lançaram um segundo especialmente sobre &ldquo;Vector databases&rdquo;. Só assistindo esse video dele, duvido quem a maioria tenha entendido. Eu expliquei o que é isso durante este video, e se você já não tinha conhecimento prévio de Álgebra Linear, provavelmente entendeu mas não compreendeu.</p>
<p>Enfim, o Fireship menciona bancos de dados modernos de vetores como Pinecone e Weaviate e disse que vai fazer um curso sobre isso. Não sei se foi piada, como eles sempre fazem, ou se vão mesmo, mas vou ficar de olho pra saber. Problema de um curso desses é o famoso &ldquo;olha como se faz um hello world, é super simples&rdquo;, mas sem entender tudo que falei hoje, você vai usar sem entender. Mas assistam lá depois. Vou deixar o link na descrição abaixo.</p>
<p>E finalmente, isso responde outra pergunta que todo mundo tem: precisa saber matemática pra ser programador? E a resposta é não. Pra fazer coisa simples, sabendo matemática básica de regra de três e porcentagem, dá pra ir se virando. No dia a dia de software pequeno, se souber pelo menos usar uma calculadora, já funciona. Agora, sem saber Álgebra Linear, Cálculo, Estatística e Probabilidade, você nunca vai trabalhar em projetos como esse do Twitter, é uma impossibilidade. Matemática é opcional, inglês é opcional, tudo depende de quão pouco você quer ter de opções. 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ê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>machine-learning</category><category>ciencia-da-computacao</category><category>akitando</category></item><item><title>[Akitando #139] - Entendendo Como Containers Funcionam</title><link>https://akitaonrails.github.io/2023/03/02/akitando-139-entendendo-como-containers-funcionam/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/03/02/akitando-139-entendendo-como-containers-funcionam/</guid><pubDate>Thu, 02 Mar 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/85k8se4Zo70"
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 vou pagar promessa. Este é o video sobre containers e Docker que venho falando faz vários videos atrás, que ia fazer. Mas eu precisava explicar a instalação, como Linux boota, como funciona virtualização, pra finalmente chegar em containers. Então este episódio vai amarrar tudo que vocês aprenderam nos videos de Linux, de armazenamento, etc e finalmente vão entender containers do jeito certo.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/85k8se4Zo70"
    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 vou pagar promessa. Este é o video sobre containers e Docker que venho falando faz vários videos atrás, que ia fazer. Mas eu precisava explicar a instalação, como Linux boota, como funciona virtualização, pra finalmente chegar em containers. Então este episódio vai amarrar tudo que vocês aprenderam nos videos de Linux, de armazenamento, etc e finalmente vão entender containers do jeito certo.</p>
<h2>Erratas<span class="hx:absolute hx:-mt-20" id="erratas"></span>
    <a href="#erratas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>5:12 - tá errado, isso é em outros comandos, como top eu acho, que mostraria 400% se tivesse usando 4 CPU. Do jeito que eu falei ficou parecendo que naquele quadrado vermelho mostraria isso, mas ali é POR cpu/thread, por isso vai só até 100%. Só em linhas de comando que mostra num número só quanto que tá se usando que apareceria, por exemplo, 400%.</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 - Intro: qual a diferença de VMs e Containers?</li>
<li>01:49 - CAP 1 - Limitando Processos - cgroups</li>
<li>07:43 - CAP 2 - Enganando Processos - namespaces</li>
<li>14:45 - CAP 3 - Recapitulando Containers - LXC, Docker, OCI</li>
<li>17:34 - CAP 4 - A Base de Containers - RunC</li>
<li>22:29 - CAP 5 - Hello World de Docker - O que tem dentro?</li>
<li>33:20 - CAP 6 - Restringindo Processos Ainda Mais - Capabilities</li>
<li>37:18 - CAP 7 - Orquestrando Containers - Docker Compose</li>
<li>41:26 - CAP 8 - Bônus do Episódio &ldquo;Tornando sua App Web mais Rápida&rdquo;  - Load Balancer com Docker</li>
<li>43:25 - CAP 9 - Orquestra de Gente Grande - K8S e Terraform</li>
<li>47:17 - CAP 10 - Outros Usos de Containers - Apps em Containers?</li>
<li>51:28 - 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/akitaonrails/episode0139"target="_blank" rel="noopener">https://github.com/akitaonrails/episode0139</a></li>
<li><a href="https://www.zsiegel.com/2022/01/17/load-balancing-in-docker-with-nginx"target="_blank" rel="noopener">https://www.zsiegel.com/2022/01/17/load-balancing-in-docker-with-nginx</a></li>
<li><a href="https://www.mo4tech.com/unshare-command-details-and-examples.html"target="_blank" rel="noopener">https://www.mo4tech.com/unshare-command-details-and-examples.html</a></li>
<li><a href="https://ericchiang.github.io/post/containers-from-scratch/"target="_blank" rel="noopener">https://ericchiang.github.io/post/containers-from-scratch/</a></li>
<li><a href="https://wiki.archlinux.org/title/cgroups"target="_blank" rel="noopener">https://wiki.archlinux.org/title/cgroups</a></li>
<li><a href="https://nodejs.org/en/docs/guides/nodejs-docker-webapp/"target="_blank" rel="noopener">https://nodejs.org/en/docs/guides/nodejs-docker-webapp/</a></li>
<li><a href="https://docs.docker.com/compose/gettingstarted/"target="_blank" rel="noopener">https://docs.docker.com/compose/gettingstarted/</a></li>
<li><a href="https://flast101.github.io/docker-privesc/"target="_blank" rel="noopener">https://flast101.github.io/docker-privesc/</a></li>
<li><a href="https://blog.alexellis.io/runc-in-30-seconds/"target="_blank" rel="noopener">https://blog.alexellis.io/runc-in-30-seconds/</a></li>
<li><a href="https://man7.org/linux/man-pages/man7/capabilities.7.html"target="_blank" rel="noopener">https://man7.org/linux/man-pages/man7/capabilities.7.html</a></li>
<li><a href="https://blog.container-solutions.com/linux-capabilities-in-practice"target="_blank" rel="noopener">https://blog.container-solutions.com/linux-capabilities-in-practice</a></li>
<li><a href="https://github.com/containers/bubblewrap"target="_blank" rel="noopener">https://github.com/containers/bubblewrap</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>O penúltimo video sobre o setup do meu PC foi pesado em configuração mas se conseguiu chegar até o fim, deve ter entendido um pouco mais sobre como virtualização funciona, em particular usando o emulador QEMU acelerado com KVM. Aquilo é virtualização: criar uma máquina virtual que roda outro sistema operacional, outra kernel, por cima.</p>
<p>Hora de pagar a promessa que vim fazendo em diversos videos. Finalmente falar um pouco sobre os tais &ldquo;containers&rdquo; que até hoje muita gente confunde com virtualização. As duas coisas &ldquo;parece&rdquo; que é rodar um programa dentro de uma máquina virtual. Mas containers são completamente diferentes de máquinas virtuais. Vamos lá.</p>
<p>(&hellip;)</p>
<p>Se fosse fazer um TLDR, uma única frase que resume a diferença, é até simples: uma máquina virtual roda um novo kernel, possivelmente numa outra arquitetura, como x86 em cima de ARM, já um container reaproveita o mesmo kernel, necessariamente na mesma arquitetura de hardware. É essa a diferença. Agora vamos ver na prática.</p>
<p>Vou começar falando só de containers de Linux. BSD, Windows, MacOS tem infraestrutura diferente pra containers. Windows tem como rodar container pra rodar apps de Windows. Isso é leve. Mas quando Windows ou MacOS precisam rodar apps de Linux, na prática o que acontece é que eles de fato sobem uma máquina virtual de Linux primeiro, como o WSL que já fiz dois videos a respeito. Portanto envolve sim virtualização. E é dentro dessa máquina que um Docker Desktop cria containers de Linux. Só em Linux nativo que containers são realmente &ldquo;leves&rdquo;.</p>
<p>Em Linux precisamos entender dois componentes fundamentais: cgroups e namespaces. Cgroups já mencionei no episódio anterior quando mostrei como isolar threads da CPU só pra máquina virtual e só pro host. Cgroups ou control groups é um mecanismo da kernel pra agrupar processos e limitar recursos como cpu e memória.</p>
<p>Existem várias formas de listar cgroups. A mais simples acho que é só dar <code>ls /sys/fs/cgroup</code>. Lembra o script chamado &ldquo;suspend&rdquo; que fiz pra limitar AllowedCPUs pro host? Usei o systemd pra limitar esse parâmetro pros cgroups init.scope, system.slice e user.slice. Vejam na listagem que todos são diretórios. Se listar um deles, vem vários arquivos como &ldquo;cpu.weight&rdquo; ou &ldquo;memory.pressure&rdquo;. É como se parametriza os recursos pra esses cgroups.</p>
<p>É tudo organizado como uma árvore, pra variar olha só árvores de novo, onde um cgroup herda configurações de outro cgroup. Honestamente, não sei os detalhes de como é configurado cada um que já vem de cada distro e imagino que cada um configura essa árvore de formas um pouco diferente. Essa listagem é no meu Manjaro. Num Ubuntu é semelhante, tem a maioria dos mesmos slices.</p>
<p>Se listarmos o que tem em system.slice, tem novos subdiretórios, um pra cada daemon que o systemd inicia, e também cgroups pra cada um dos aplicativos instalados via Snap, que vou falar um pouco mais pra frente. Pra enxergamos isso melhor podemos rodar o comando <code>systemctl status</code>. Agora temos a hierarquia toda de cgroups.  Como falei antes, olha aqui o system.slice e os sub-grupos de cada daemon. E mais pra baixo temos o user.slice onde os programas que estamos rodando como navegador, terminal e tudo mais ficam em seus próprios sub-grupos também. Todos os processos rodando estão organizados em cgroups. Você nem sabia disso, mas toda vez que executa um programa, ele vai estar atrelado a pelo menos um cgroup.</p>
<p>Lembra que quando usamos comandos com <code>ps</code> ou programas como <code>htop</code> eles pesquisam os metadados no sistema de arquivos virtual <code>/proc</code>? Por exemplo, vamos pegar o PID do meu navegador Edge. Tem vários processos, todo navegador separa cada aba num processo diferente pra isolar, ter mais segurança, e se uma aba crashear, não derrubar todas as outras junto. Podemos fazer <code>cat /proc/PID/group</code> e aqui vamos em qual sub-grupo de cgroup esse process está. No escopo do edge, dentro de app.slice, dentro de user service, dentro de user.slice.</p>
<p>Existem várias formas de controlar cgroups. Uma delas é usando o sistema de arquivos virtual disponível no diretório <code>/sys/fs/cgroups</code>. Isso é um ponto de montagem. Se fizermos <code>mount | grep cgroup</code> olha como aparece que o tipo do sistema de arquivos é &ldquo;cgroup2&rdquo;. É como se fosse um dispositivo virtual montado. Lembra que já expliquei nos episódios de Linux, como nos de Slackware, Gentoo e no que explico como funciona o boot, que existem diversos pontos de montagem especiais como &ldquo;/proc&rdquo; ou &ldquo;/dev&rdquo;?</p>
<p>Pra mostrar na prática pra que diabos saporra serve, deixa criar um script idiota em Ruby. Podia ser em Python, podia ser em Java, faça no que quiser. É só um loop infinito printando ponto pra sempre. Vamos rodar e em outro terminal abro o &ldquo;htop&rdquo;. Veja aqui no topo que meu script tá usando 100% de 1 CPU. Se eu tivesse feito suportar múltiplas threads, cada 100% seria uma CPU. Um programa que usa 4 threads inteiras ia aparecer aqui como 400%.</p>
<p>No topo do programa lista todas as threads independentes e veja que tem uma de 100% que é meu script. Mas se esperar um pouquinho vai ver que ele muda de lugar. Um programa não fica sempre fixo no mesmo thread. Pra isso serve o scheduler da kernel. Expliquei sobre scheduler no episódio de concorrência e paralelismo e é a razão de porque no episódio anterior tive o trabalho de mostrar como mandei a máquina virtual usar as threads de 16 a 31 e limitei o host a usar só de 8 a 15. Lembra deste script aqui usando systemd e configurando o parâmetro AllowedCPUs em todos os diretórios raíz de slices?</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 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 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">    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">    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">    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 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">    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">    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">    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 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>Ctrl-C pra parar o loop infinito e agora podemos criar sub-diretórios e arquivos usando comandos normais como &ldquo;mkdir&rdquo;, &ldquo;echo&rdquo; ou podemos continuar usar o SystemD pra facilitar, que é o que vou fazer agora. Vou criar um slice pra mim chamado &ldquo;akita.slice&rdquo; em &ldquo;/etc/systemd/system/akita.slice&rdquo;. Só abrir com qualquer editor de textos usando &ldquo;sudo&rdquo; e no caso quero limitar o uso de CPU em só 30% pra processos neste cgroup. Pra isso coloco este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Slice]
CPUQuota=30%</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>Posso configurar outros limites, como memória, mas pra esse exemplo, só isso já funciona. Agora vou abrir um shell dentro desse cgroup com este comando <code>systemd-run --slice=akita.slice --uid=akitaonrails --shell</code> e nessa linha de comando que abriu, vou repetir o mesmo comando <code>ruby loop.rb</code>.</p>
<p>Olha o htop. Tá vendo aqui no topo da lista? Antes o script consumia 100% de uma thread, mas como agora reiniciei a partir do meu novo cgroup,que só permiti usar 30% da CPU, a kernel obriga a usar no máximo 30% da CPU. E com isso conseguimos controlar granuladamente o que cada processo no sistema pode ou não consumir.</p>
<p>E por que eu iria querer limitar um programa forçado assim? No Wiki do site do ArchLinux tem um exemplo no fim. Ele menciona o Matlab, que é um programa de cálculo numérico e, dependendo da complexidade do cálculo, pode consumir toda a RAM e toda CPU do sistema. Se um programa usar todos os recursos, vai faltar CPU e RAM até pra kernel e o sistema inteiro vai ficar lento ou crashear. É um tipo de ataque de DoS. DoS ou Denial of Service é um programa saturar todos os recursos da máquina até tudo parar de funcionar. Mas se colocar o Matlab num cgroup onde limito CPU e RAM pra, digamos, deixar sobrar uns 10%, a kernel vai ter a possibilidade de tentar se corrigir.</p>
<p>Depois leia mais sobre cgroups no Wiki do Arch. Mas o importante foi pra explicar que existe esse recurso em todo Linux moderno. Agora vou pular pro segundo grande recurso pra limitar o que processos podem fazer, que se chama &ldquo;Namespaces&rdquo;. Quando listo o que tem em &ldquo;/proc&rdquo; consigo enxergar todos os PIDs de todos os processos rodando no sistema. Se rodar o comando &ldquo;mount&rdquo; consigo ver todos os pontos de montagem ativos no sistema. E existem comandos ou locais onde posso listar a stack de rede, IPC que é comunicação inter-processos, IDs de usuários e grupos.</p>
<p>Mas é possível esconder todos esses detalhes de um processo. Pra isso existe o comando <code>unshare</code>, literalmente &ldquo;descompartilhar&rdquo;. Num terminal normal, se executarmos o comando <code>ps aux</code> isso vai nos devolver uma lista com todos os processos ativos neste momento, e costuma ser uma lista grande num desktop normal. Vamos abrir um novo shell, mas fazendo <code>sudo unshare --fork --pid --mount-proc /bin/bash</code>. Parece nada demais né? Mas se executarmos <code>ps aux</code> olha só, cadê todos os meus processos? Sumiu? O bash agora tem PID 1.</p>
<p>É isso que se chama namespace, é um espaço separado de nomes, no caso de PIDs. Se abrir outro terminal e executar <code>ps aux</code> tá tudo aqui bonitinho e lá no final olha o comando unshare e a instância de bash rodando com um PID alto. A diferença é que o bash que acabamos de abrir não enxerga mais o mesmo &ldquo;/proc&rdquo; global. Ele tem um &ldquo;/proc&rdquo; local só pra ele, isolado e separado que começa do 1 de novo. De dentro daquele bash, vamos listar o que tem em &ldquo;/proc&rdquo;, só os dois PIDs que listou. No outro terminal normal se listar &ldquo;/proc&rdquo; tem todos os PIDs.</p>
<p>Entenderam? Namespace faz o processo puxar a informação de outro lugar. Estamos mentindo, mas ele continua executando no mesmo sistema, igual qualquer outro programa, tanto que tem um PID de verdade. Pra quem é de Javascript, uma analogia é a diferença de criar uma variável com o antigo &ldquo;var&rdquo; pra cair no escopo global da função ou usar &ldquo;let&rdquo; e manter a variável no escopo local do bloco, manja? Não é exatamente a mesma coisa, mas é parecido.</p>
<p>Com o recurso de namespaces podemos mentir pros processos pra não enxergar mais o resto do sistema do lado de fora e assim limitar o que podem fazer. Por padrão, todos os recursos do sistema operacional são globais, todo processo tem acesso a praticamente tudo que outro processo tem, salvo permissões de leitura e execução por usuário, que você tá mais acostumado a pensar, tipo, root tem acesso a tudo. Mas com <code>unshare</code> inclusive note que ele engana meu processo fazendo achar que é root. Se executarmos o comando <code>id</code> olha, sou o usuário zero.</p>
<p>Como executamos unshare com sudo, esse root tem o mesmo nível de privilégio do root de verdade, o que não é muito seguro. Vamos dar exit e executar a mesma linha de comando mas com a opção &ldquo;&ndash;user&rdquo; e pronto. Se executarmos &ldquo;id&rdquo; agora sou nobody, literalmente um zé ninguém. Mais útil vai ser rodar como root, só que não tendo acesso ao sistema de arquivos todo. Vamos consertar isso.</p>
<p>Se assistiu meus episódios de Linux, viu minha explicação sobre chroot. Um container de verdade não usa chroot, mas pra demonstração dos conceitos vai servir. Teve um palestrante que já demonstrou isso anos atrás e deixou alguns arquivos que vamos precisar. Vou deixar o link na descrição abaixo, vamos baixar o tarball &ldquo;rootfs.tar.gz&rdquo;.</p>
<p><code>wget https://github.com/ericchiang/containers-from-scratch/releases/download/v0.1.0/rootfs.tar.gz</code></p>
<p>Pronto, baixei lá no &ldquo;/tmp&rdquo; mesmo porque é só pra demonstração. Agora é só descomprimir com <code>tar xvfz</code>, esperar um segundo &hellip; e tá lá. Se listarmos o que tem, parece com a raíz do seu HD, não? É uma mini-distribuição do que chamamos de &ldquo;userland&rdquo;. Se listar o &ldquo;rootfs/boot&rdquo; não tem a kernel nem a initram que deveria. E o que fazemos com isso? É como na instalação do Gentoo, fazemos <code>sudo chroot rootfs /bin/bash</code>. Ele vai fazer &ldquo;change root&rdquo;, mudar a raíz, pra esse diretório rootfs e executar o bash dentro do sub-diretório &ldquo;rootfs/bin&rdquo; e não o meu de verdade.</p>
<p><code>ls</code> dá pau agora. Isso porque esse shell não carregou perfil nenhum, então não tem nenhuma variável de ambiente configurado. Vamos exportar o path de novo com <code>export PATH=/sbin:/bin:/usr/bin</code> e agora sim temos acesso às ferramentas nesses diretórios. É assim que sua distro Linux faz, só que isso já vem configurado automático pra você, mas na mão é assim que fazemos.</p>
<p>Essa variável provavelmente está no seu arquivo &ldquo;/etc/profile&rdquo; ou .bashrc ou .zshrc da vida. Executando <code>ls /</code> parece que não aconteceu nada, mas faz <code>ls /boot</code>, viu? Vazio. Estamos dentro do diretório &ldquo;rootfs&rdquo; que está sendo mascarado pra gente como se fosse a nova raíz. Esse shell não tem mais acesso ao meu sistema de arquivos de verdade.</p>
<p>Agora tentamos executar <code>ps</code> e dá pau. Isso porque não existe nenhum mount de diretórios especiais, como o &ldquo;/proc&rdquo; que é onde comandos como &ldquo;ps&rdquo; pesquisam processos. Veja o erro, ele manda justamente dar mount nisso. Mas agora é que vamos juntar esse comando de chroot com o unshare. Então damos exit e executamos o comando <code>sudo unshare -p -f --mount-proc=/tmp/rootfs/proc chroot rootfs /bin/bash</code></p>
<p>A opção &ldquo;-p&rdquo; é o jeito mais curto pra &ldquo;&ndash;pid&rdquo; e &ldquo;-f&rdquo; é pra &ldquo;&ndash;fork&rdquo;. Daí montamos o proc e damos chroot via unshare. Não podemos esquecer de exportar o PATH de novo. Pronto. Se fizermos <code>ls /boot</code> tá vazio. Se fizermos <code>ls /home</code>, tá vazio. Ou seja, esse não é seu sistema, ele está enxergando a raíz a partir do diretório &ldquo;/tmp/rootfs&rdquo;. Mas agora se rodarmos <code>ps aux</code>, sucesso! Só enxergamos os processos dentro do novo namespace. Não só escondemos os PIDs de todo o sistema como escondemos o sistema de arquivos e você pensa que está num sistema completamente diferente.</p>
<p>Esse é o ponto que confunde todo mundo. O comportamento dentro desse shell faz parecer que estou dentro de uma máquina virtual. Mas isso não é uma máquina virtual, não tem um hypervisor como KVM com um emulador como QEMU simulando uma máquina nova do zero e fazendo boot de um novo kernel. Por isso estou reforçando que o diretório falso &ldquo;/boot&rdquo; tá vazio, não tem outra kernel. Isso não é uma máquina virtual. E se voltarmos pra outro terminal e executarmos <code>ps aux</code> esse bash que abrimos dentro do chroot tá aqui, aparecendo normalmente como se fosse qualquer outro processo.</p>
<p>Se não ficou claro. Alt-tab pro shell dentro do chroot. Executando <code>ls /usr/bin</code> vamos abrir um programa que tem aqui. Olha só, veio um python naquele tarball, vamos executar. Esse é o REPL de python que vocês já devem ter visto. Alt-tab pro terminal normal e <code>ps aux</code> de novo. Olha só aparecendo aqui python. Se fosse uma máquina virtual, teria um  único processo, por exemplo de QEMU e só, eu não estaria vendo os processos dentro da máquina virtual misturado com os do meu sistema. E isso porque esses processos ESTÃO rodando no meu sistema normalmente, sem emulação, nem nada disso, é nativo.</p>
<p>Se não fosse assim, chamaríamos tudo de máquina virtual. Mas por isso inventamos esse termo novo chamado &ldquo;container&rdquo; justamente pra ser distinto. O processo está só sendo enganado, achando que está num outro sistema porque cortamos os olhos dele, as mãos dele, o nariz dele. E só apontamos tudo pra outros namespaces É isso que unshare e chroot fizeram com o shell do bash: isolaram num jarro, num container. Tudo que executa com unshare está na Matrix.</p>
<p>Nessa demonstração simples só escondemos os PIDs, o diretório &ldquo;/proc&rdquo; e o sistema de arquivos, mas dá pra mudar a rede, dá pra mudar todos os outros mounts como &ldquo;/dev&rdquo; ou &ldquo;/sys&rdquo;, dá pra fazer ele enxergar outros cgroups, outro clock do sistema, outros usuários e grupos, outro hostname e domainname, ou seja, o processo realmente vai achar que está em outra máquina, sem realmente estar. Ele pára de ter acesso ao resto do sistema e efetivamente roda &ldquo;isolado&rdquo;, que é todo o objetivo.</p>
<p>Namespaces são super flexíveis. Por exemplo, se isolar o container numa rede virtual, é assim que o processo num container consegue dar bind, digamos na porta 80, e um outro processo em outro container também consegue subir na porta 80, cada um numa rede isolada que daí configuro pra expor um pra fora como porta 4001 e outra como 4002. Mas também posso adicionar esses dois containers ambos no mesmo namespace de rede e assim crio uma rede virtual só entre os dois, sacaram? Pra isso serve o comando <code>nsenter</code> que é literalmente &ldquo;entrar num namespace&rdquo;, depois pesquisem.</p>
<p>Pesquisem no Google por &ldquo;container from scratch&rdquo; ou &ldquo;container do zero&rdquo; e vão esbarrar em artigos como do site da RedHat e diversos outros artigos explicando sobre <code>unshare</code>. Mas pra hoje esses exemplos que dei já servem pra continuar a explicação. As coisas começam a ficar confusas porque existem várias ferramentas que funcionam em cima dessa infraestrutura de cgroups e namespaces que expliquei. No começo, no mundo Linux surgiu Linux Containers ou LXC e depois Docker.</p>
<p>Em 2015 a empresa do Docker criou a Open Container Initiative ou OCI, um grupo com várias outras empresas pra padronizar e tornar mais portável todos os componentes que fazem containers funcionar, em particular o formato de imagens que é o formato OCI image, é o que você baixa do site dockerio.com. Os padrões estão todos na opencontainers.org e no mundo Linux a implementação desse padrão é a tecnologia &ldquo;runC&rdquo;. Vamos ver como funciona.</p>
<p>O runC precisa de um arquivo &ldquo;config.json&rdquo; que define as características do container. E vamos aproveitar aquele diretório &ldquo;rootfs&rdquo; ainda. O próprio comando runc gera esse arquivo executando <code>runc spec</code> dentro do diretório rootfs 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-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;ociVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.2-dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;process&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;terminal&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;user&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;uid&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;gid&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/bin/bash&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;env&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;TERM=xterm&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;capabilities&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;bounding&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;effective&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;permitted&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;ambient&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</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="nt">&#34;rlimits&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;RLIMIT_NOFILE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;hard&#34;</span><span class="p">:</span> <span class="mi">1024</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;soft&#34;</span><span class="p">:</span> <span class="mi">1024</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="nt">&#34;noNewPrivileges&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp/rootfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;readonly&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;hostname&#34;</span><span class="p">:</span> <span class="s2">&#34;runc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;mounts&#34;</span><span class="p">:</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="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/proc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;proc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;proc&#34;</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="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;strictatime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=755&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;size=65536k&#34;</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></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/pts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;devpts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;devpts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;newinstance&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ptmxmode=0666&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=0620&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;gid=5&#34;</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></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/shm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;shm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=1777&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;size=65536k&#34;</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></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</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></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/sys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;sysfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;sysfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ro&#34;</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></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/sys/fs/cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;relatime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ro&#34;</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></span><span class="line"><span class="cl"> <span class="nt">&#34;linux&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resources&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;devices&#34;</span><span class="p">:</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="nt">&#34;allow&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;access&#34;</span><span class="p">:</span> <span class="s2">&#34;rwm&#34;</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></span><span class="line"><span class="cl">  <span class="nt">&#34;namespaces&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;pid&#34;</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;network&#34;</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ipc&#34;</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;uts&#34;</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;mount&#34;</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</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="nt">&#34;maskedPaths&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/acpi&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/asound&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/kcore&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/keys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/latency_stats&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/timer_list&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/timer_stats&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sched_debug&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/sys/firmware&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/scsi&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;readonlyPaths&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/bus&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/fs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/irq&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sysrq-trigger&#34;</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></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 que vocês viram a demonstração com unshare, esse arquivo não deveria parecer muito alienígena. Inclusive foi por isso que fiz videos explicando Linux antes, porque precisa entender o sistema operacional pra entender como containers são configurados. Esse arquivo começa declarando a versão do padrão OCI, daí qual processo vamos rodar, que é o bash, e com qual usuário e grupo, no caso zero quer dizer root.  Olha como já configura o PATH pra mim. &ldquo;CWD&rdquo; é Current Working Directory que é o diretório onde o programa vai abrir.</p>
<p>Capabilities pula que vou explicar depois. Daí &ldquo;root&rdquo; quer dizer a raíz do sistema de arquivos, que vai ser dentro daquele /tmp/rootfs onde fizemos chroot. Hostname é o nome da máquina, que é definido pelo namespace UTS, assim o container pode ter um nome de máquina diferente do nome da máquina de verdade.</p>
<p>No atributo de mounts aparece a mesma coisa que fizemos com a opção &ldquo;&ndash;mount-proc&rdquo; do comando unshare, onde mandamos dar mount no sistema de arquivos especial de processos. Assim, dentro do container não vai mais enxergar os pids de verdade do lado de fora. Mas aqui tá mais completo porque vai dar mount pra &ldquo;/dev&rdquo;, pros pseudo terminais em &ldquo;/dev/pts&rdquo;, pra memória compartilhada que é &ldquo;/dev/shm&rdquo;, as filas de mensagem pra comunicação entre processos em &ldquo;/dev/mqueue&rdquo;, o &ldquo;/sys&rdquo; que espelha funcionalidades e informações da kernel e finalmente o mount de cgroup que mostrei no começo do video. Tudo isolado dos de verdade do lado de fora do container. Estamos deixando esse jarro o mais realista possível pro processo dentro.</p>
<p>E por fim quais namespaces vou isolar também que são os mesmos do unshare. Pid sabemos que esconde os processos, mas temos network pra rede virtual, ipc que é a fila pra comunicação interprocessos, uts que permite mudar o hostname interno e mount que permite criar mounts diferentes do seu sistema.</p>
<p>Com tudo configurado, podemos executar, a partir de rootfs, <code>sudo runc start hello</code>. Hello você pode substituir por um nome qualquer pra esse container, contanto que seja um nome único, daí podemos criar quantos containers quisermos. Como aconteceu antes quando rodamos unshare com chroot, caímos no bash dentro do jarro, com todos os seus sentidos sequestrados. RunC está mentindo pro bash, vamos ver?</p>
<p>Se digitarmos <code>id</code> estamos como root. Se executarmos <code>ls /boot</code>, tá vazio, estamos dentro do diretório rootfs como nova raíz de arquivos. Melhor ainda, se digitarmos <code>mount</code>, temos só os pontos de montagem que definimos no <code>config.json</code>. Ele não enxerga nenhum dos pontos de montagem de verdade do meu sistema. E se digitarmos <code>ps aux</code>, como antes, só enxergamos o bash que está rodando. É como se fosse uma máquina de verdade vazia. Estamos dentro da Matrix, mas repetindo, isso não é uma máquina virtual. Container, na prática é outro nome dado a um processo em execução com menos acessos que um processo normal, mas no fundo é só isso: a execução de um processo.</p>
<p>O que eu falei no começo do episódio? Vou repetir: Se fosse fazer um TLDR, uma única frase que resume a diferença, é até simples: uma máquina virtual roda um novo kernel em qualquer arquitetura, um container reaproveita o mesmo kernel, nas mesma arquitetura. É essa a diferença. Mas agora vamos ver na prática. Estou repetindo o tempo todo que &ldquo;/boot&rdquo; está vazio. Não tem outra kernel, estamos compartilhando a mesma kernel. Hypervisors compartilham o hardware de verdade, como CPU, GPU e dispositivos pra máquinas virtuais que rodam qualquer outro sistema operacional, qualquer nova kernel isolados um dos outros. Posso rodar Windows do lado de BSD. Mas um container compartilha o mesmo kernel que existe já carregado. Ele não pode rodar sistemas operacionais diferentes, só outra distro Linux.</p>
<p>Vamos voltar pro que eu disse no começo: como Windows e MacOS fazem pra ter containers de Docker de Linux? Eles sim, primeiro precisam usar o hypervisor deles, o Hyper-V ou o Hypervisor.framework pra bootar um Linux de verdade e em cima desse Linux rodar o RunC pra conseguir ter containers de Linux. Por isso Docker é sempre mais pesado em cima de Windows do que direto num Linux. Numa distro Linux não tem a necessidade de rodar um hypervisor antes. Entenderam essa diferença? É muito importante, porque sua performance e uso de recursos como RAM sempre vai ser pior no Docker pra Windows ou pra Mac. Sempre, não tem exceção.</p>
<p>Mas eu falei Docker e fiquei mostrando RunC, qualé? É porque depois que a OCI foi formada, o Docker, Podman, Kubernetes, todos que usam containers, provavelmente usam RunC por baixo, que é a infraestrutura que usa cgroups, unshare e outras coisas que vem em todo Linux. Mas se tem RunC então pra que precisamos de Docker?</p>
<p>A parte importante do Docker não é mais a capacidade de executar containers. Hoje em dia isso vem nativo na kernel do Linux através do RunC que mostrei. O grande lance de Docker é o ecossistema de imagens, o DockerHub. Como é o tutorial de hello world de Docker logo depois que instala? <code>docker run hello-world</code>, vamos executar no terminal. Ele imprime esse texto e termina. O que aconteceu?</p>
<p>A ferramenta começa checando no registry local se tem uma imagem com nome &ldquo;hello-world&rdquo;. Como não tinha, deu &ldquo;pull&rdquo;, como se fosse o comando <code>git clone</code> ou mais especificamente, <code>git pull</code>. Já explico porquê, mas na prática baixou o binário do site hub.docker.com. Podemos ir no site deles e ver o registro dessa imagem lá, tão vendo? Na dúvida ele baixa da tag &ldquo;latest&rdquo;. É o equivalente à branch &ldquo;main&rdquo; ou a antiga &ldquo;master&rdquo;.</p>
<p>Essa imagem baixada é que nem aquele nosso diretório &ldquo;/tmp/rootfs&rdquo; que eu tinha baixado o tarball, onde demos chroot. Com essa imagem o runC sobe um container. Pra ver o container que executou basta rodar o comando <code>docker container ls --all</code>. Sem essa opção a lista seria vazia porque o container executa e termina. Com essa opção &ldquo;all&rdquo; ele mostra mesmo os terminados. Lembra no comando &ldquo;runc&rdquo; que dei o nome de &ldquo;hello&rdquo; pro container? No comando de <code>docker run</code> poderia passar a opção &ldquo;&ndash;name&rdquo; mas como não fiz isso ele deu um nome aleatório pro container.</p>
<p>Agora uma coisa que a maioria que já usou Docker nunca deve ter feito. Vamos executar o comando <code>docker export NOME_CONTAINER &gt; rootfs.tar</code>. Sacaram? Podemos criar um diretório novo &ldquo;mkdir -p /tmp/rootfs2&rdquo; e fazer <code>tar xvf rootfs.tar --directory /tmp/rootfs2</code>. Agora damos &ldquo;cd&rdquo; pra dentro e não tem quase nada lá, alguns diretórios pra fazer mount de procs e outras coisas e esse executável &ldquo;hello&rdquo; que podemos executar diretamente com &ldquo;./hello&rdquo; e olha só, a mesma saída que vimos antes. É isso que o <code>docker run</code> executou e é assim que podemos pegar o conteúdo de uma imagem de Docker. Uma imagem de Docker não é muito diferente de um mero tarball.</p>
<p>Vamos entender melhor essa tal imagem. Pra começar, de volta no site do DockerHub vamos escolher a imagem do banco de dados Postgres. Agora vamos abrir do lado outro navegador com o arquivo Dockerfile no Github do repositório docker-library. Se clicarmos na tag &ldquo;latest&rdquo; podemos ver o que ele chama de layers. Sem ir linha a linha, só de bater o olho, conseguem ver onde um lado bate com o outro? Por exemplo, no Dockerfile tem esse comando &ldquo;ENV GOSU_VERSION&rdquo;. O comando ENV de Dockerfile serve pra configurar uma variável de ambiente, equivalente ao comando <code>export</code> num Bash. Todos esses &ldquo;/bin/sh&rdquo; são esses comandos RUN e assim por diante.</p>
<p>O que é um Dockerfile? É como se fosse um script de comandos que diz passo a passo como montar a tal imagem de Postgres. Voltando pra primeira linha, começa com o comando FROM. Não precisamos começar uma imagem do zero, podemos começar com uma que já existe e construir por cima. É como se fizéssemos <code>git clone</code> de um projeto e começássemos a adicionar coisas e ir fazendo <code>git commit</code>.</p>
<p>A grosso modo, cada comando do Dockerfile, esses em letra maiúscula no começo da linha, vão gerar um dos layers que vemos aqui no site do DockerHub. Com exceção de layers de configurar variável de ambiente, que é irrisório, a maioria dos comandos modifica ou baixa arquivos e por isso ocupa espaço. É que nem commit de Git. Por isso é comum num Dockerfile ter comandos RUN que executam vários comandos de shell concatenados, que nem logo no começo que fazemos <code>apt update; apt-get install ...; rm -Rf</code>. Por que?</p>
<p>Se o <code>rm -Rf</code> estivesse separado em outro comando RUN embaixo, o comando do <code>apt-get install</code> ia baixar e instalar pacotes, como em qualquer Debian ou Ubuntu que você usa. Digamos que instalou 50 megabytes, tô chutando. Mas vem um lixo opcional que não preciso, daí no comando seguinte rodo <code>rm</code> e apaga 10 megabytes. Mas a imagem vai ter um layer com 50 megabytes. Porque o <code>rm</code> ficou num layer separado.</p>
<p>Em vez disso eu baixo os 50 mega, na sequência já apago, e aí fecho a camada com 40 megabytes. Por isso fica tudo dentro do mesmo comando RUN, como se fosse num mesmo commit, entenderam? Quanto menos comando RUN no Dockerfile, melhor. Olha esse outro comando RUN embaixo, um monte de linhas um atrás do outro, e o resultado final de executar tudo é que vai no layer da imagem.</p>
<p>Essa é a importância da OCI que o Docker fundou. Ela define esse formato chamado imagem OCI. Dentro dessa imagem, que é um binariozão parecido com um zipão, tem alguns arquivos e diretórios obrigatórios. Como um <code>index.json</code> que tem o nome, ID, tamanho, lista de layers e histórico. Tem o <code>oci-config.json</code> que é o equivalente ao <code>config.json</code> do RunC que mostrei antes e, o mais importante, o diretório &ldquo;blobs&rdquo; que é parecido com diretório &ldquo;.git/objects&rdquo; de um repositório Git.</p>
<p>Ah, vocês não assistiram meu video de Git né? Lá eu expliquei que o importante é só o que tem no diretório &ldquo;.git&rdquo;, todo o resto pode jogar fora porque dá pra recriar tudo com o que tem aqui. Se listarmos <code>ls .git/objects</code> vem esse monte de subdiretórios. Vamos listar um deles e encontramos um arquivo com nome que é um SHA1. Esse é o SHA1 do commit de git. Os blobs da imagem de Docker é similar só que otimizados pra layers binários numa árvore, enquanto Git é otimizado pra árvore de commits de texto.</p>
<p>Lembram do formato QCOW2 que mostrei no episódio anterior? O QEMU Copy-on-Write versão 2 que é a imagem de HD virtual que o QEMU usa e onde instalamos o Windows virtualizado? É similar. O formato de imagem OCI também suporta copy-on-write. Que é o mesmo recurso que tem disponível em sistemas de arquivos modernos como o BTRFS. Um snapshot de BTRFS é similar com snapshot de QCOW2 que é parecido com layers de imagem OCI. COW permite criar layers só realmente com a diferença dos blocos que mudaram num arquivo.</p>
<p>Sabe diff que fazemos em Git e ele grava no commit só as linhas que foram adicionadas e as que foram removidas e não o arquivo todo? COW é como se fosse diff de arquivo binário. E lembra que eu venho repetindo em todos os videos da mini-série de armazenamento como arquivos são nada mais do que um conjunto de blocos binários organizados numa árvore? Se um arquivo tem 100 megabytes, divididos em 100 blocos de 1 megabyte, digamos que só 10 blocos desse arquivos foram alterados. Na próxima camada, ou commit, não precisa repetir os 100 megabytes inteiros do arquivo, só os 10 blocos modificados ocupando 10 megabytes. É por isso que imagens de Docker não são pesados.</p>
<p>Pra construir uma imagem nova só precisamos ter o arquivo Dockerfile como esse do Postgres e rodar o comando <code>docker build</code>. Se resolvermos mudar o Dockerfile a partir da metade, e repetirmos o comando <code>docker build</code>. Todas as camadas geradas pela primeira metade de comandos do arquivo permanecem as mesmas, jogamos fora as últimas camadas e ele executa os comandos a partir da metade. Por isso que quanto mais pro começo de um Dockerfile você mexer, mais o <code>docker build</code> vai ter que trabalhar. Se mexer só na última linha, só ela que vai ter que rodar.</p>
<p>Então uma imagem de OCI ou Docker é um conjunto de camadas binárias, como commits de um git. Pra montar dentro do container o Docker usa um sistema de arquivos chamado overlay2 que é um Union Filesystem. Union significa que unifica as diversas camadas num sistema de arquivos único. O container em si não sabe que tem essas camadas todas por baixo. Isso só importa pra construção da imagem, não pra rodar.</p>
<p>Eu menti agora pouco pra vocês. RunC é a infraestrutura de containers no Linux que sistemas como Kubernetes e Podman usam, mas o Docker propriamente dito na verdade usa outra coisa, que é o &ldquo;containerd&rdquo;. Pense assim, RunC tem menos funcionalidades, é mais simples, mais leve e pode rodar em userland, ou &ldquo;rootless&rdquo;, sem permissões de root ou daemons. Já containerd tem mais funcionalidades e foi feito pra funcionar com um daemon que sobe como root.</p>
<p>Por isso que quando se instala Docker, todo tutorial vai te falar pra executar &ldquo;systemctl start docker&rdquo; e habilitar o serviço pra subir no boot com &ldquo;systemctl enable docker&rdquo;. E isso pode se tornar um problema de segurança. Se sair pesquisando sobre buracos no Docker, vai achar várias formas de exploit dependendo de como o container foi configurado pra subir. O caso mais besta é subir um container com um comando como esse: <code>docker run -tid -v /:/mnt/ alpine</code>. Estão enxergando o erro?</p>
<p>O problema é essa opção &ldquo;-v&rdquo; que é pra mapear volumes. Lembra que quando estamos dentro da imagem o processo dentro não enxerga nada do sistema de arquivos fora? Pois bem, no mundo real o container é mais útil se ele tem &ldquo;algum&rdquo; acesso ao mundo externo. É melhor ser o Neo, saber que o mundo real existe, e ter superpoderes dentro da Matrix e um deles é conseguir enxergar arquivos do sistema de verdade.</p>
<p>Essa opção &ldquo;-v&rdquo; monta um diretório do lado de fora pra um diretório do lado de dentro, no caso do exemplo vai montar a raíz de verdade do meu HD pra um diretório &ldquo;/mnt&rdquo; no container. Mas isso é uma burrice, nunca faça um ponto de montagem tão amplo. Como o daemon que roda o container, o containerd que o Docker usa, sobe como root, ele tem permissão pra editar qualquer arquivo do sistema de verdade a partir desse ponto de montagem.</p>
<p>Daí é game-over, posso fazer o que quiser, inclusive editar o arquivo &ldquo;/etc/passwd&rdquo; pra injetar um novo usuário com permissão de root na máquina de verdade e invadir ela. Eu acho que ninguém em sã consciência jamais subiria um container de Docker num servidor de produção apontando pra raíz do HD do servidor. Só um sem noção faria isso. Seria certificado de estupidez.</p>
<p>É uma das razões de porque muitos de nós preferimos usar Podman em vez de Docker. Podman foi feito pra ter todos os mesmos comandos, mesmas opções e mesma sintaxe de Docker. Então comandos como <code>docker build</code> ou <code>docker run -it bla bla</code> posso rodar com <code>podman build</code> ou <code>podman run bla bla</code>. Se quiser posso fazer um alias no shell chamado docker que aponta pro podman e tudo vai funcionar praticamente igual. Podman usa o mesmo arquivo Dockerfile com a mesma sintaxe, a única diferença é aquele comando FROM no começo do arquivo.</p>
<p>Por exemplo, no caso do postgres começa com &ldquo;FROM debian&rdquo;. Pro Podman suportar precisa reescrever como &ldquo;FROM docker.io/debian&rdquo;. Só especificar o site da imagem porque ele suporta outros repositórios em vez de só o dockerhub. Outra diferença é que Docker é construído em cima do containerd, mas Podman usa o RunC como mostrei antes. Diferente do containerd não precisa ter um daemon rodando como root. Na verdade RunC, e por consequência o Podman, tem modo &ldquo;rootless&rdquo;. Ou seja, nem preciso iniciar com &ldquo;sudo&rdquo;. Portanto o container consegue rodar sem privilégio nenhum e por padrão é mais seguro. Veja documentação deles de como funciona esse modo porque não vou detalhar hoje.</p>
<p>E falando em segurança e permissões, pra concluir, faltou falar daquele bloco no arquivo do <code>config.json</code> que falei pra pular, lembram? Capacidade ou Linux Capabilities. Toda vez que se fala em permissões em Linux, automaticamente pensamos em coisas como os comandos &ldquo;chown&rdquo; pra mudar a propriedade de algum arquivo ou &ldquo;chmod&rdquo; pra mudar um arquivo pra ser só de leitura por exemplo. Ou, usar ou não usar &ldquo;sudo&rdquo; pra executar alguma coisa. E é isso, acabou.</p>
<p>Só que não. De cara vocês já aprenderam sobre cgroups e como eles limitam os recursos do processo, ou namespaces e como o sistema mente pros processos, independente se tem permissão de root ou não. E tem muito mais em cima. Linux capabilities é a habilidade da kernel de dar ou remover capacidades com o comando <code>setcap</code>.</p>
<p>Vou tentar não descer em detalhe desnecessário demais, mas num terminal o primeiro comando pra entender capabilities é o <code>capsh</code>. Execute num terminal e vemos dois campos no começo: Current e Bounding Set. Current vazio significa que um usuário normal começa sem nenhuma capacidade por default, mas tem várias capacidades que podem ser habilitadas, que é essa lista enorme chamada de Bounding Set.</p>
<p>Existem dois tipos de capacidades em Linux: capacidades efetivas e capacidades permitidas. Capacidades efetivas determinam quais operações um processo pode performar, e capacidades permitidas são quais podem ser habilitadas ou desabilitadas. Bounding Set é um sub-conjunto de capacidades permitidas. E quem controla esse set é o comando <code>prctl</code>, depois pesquise porque não quero entrar em detalhes sobre granularidade de segurança de Linux hoje.</p>
<p>Próximo comando vai ser <code>grep Cap /proc/$BASHPID/status</code>. Essa variável BASHPID é o pid do bash. Aí aparece essa lista bizarra, mas é fácil. O primeiro são inherited capabilities, ou capacidades herdadas no caso zero. Segundo são capacidades permitidas que eu falei, zero. Terceiro capacidades efetivas que também expliquei, zero. Finalmente capacidades bounding set.</p>
<p>Com o comando <code>capsh --decode=</code> esse hexadecimal e a lista é, obviamente, a mesma que vimos no comando <code>capsh --print</code> antes. Isso fica guardado no objeto proc do processo. E esse hexadecimal é um bit set. Um conjunto de bits. Se você não conhece a técnica de guardar propriedade num bit dentro de um inteiro e pegar os valores com uma máscara binária, um bit mask, pesquise. É essencial pra economizar bits em coisas como protocolos de rede.</p>
<p>De qualquer forma veja de novo aquele bloco de capabilities em <code>config.json</code>, agora você sabe o que é efetivo ou permitido. Esses cap_alguma coisa tem que ler no manual de capabilities. Só digitar <code>man capabilities</code> no terminal. Pronto, cap_audit_write é capacidade de escrever no log do sistema, cap_kill é capacidade de enviar sinais tipo SIGTERM ou SIGKILL e cap_net_bind_service é pra conseguir dar bind em portas privilegiadas abaixo de 1024, como a porta 80 sem precisar fazer sudo.</p>
<p>Com isso vocês tem pelo menos noção agora do que são containers do ponto de vista da kernel do Linux. Eles não são máquinas virtuais. É simplesmente um jarro que a kernel cria ao redor de um processo que roda nativamente pra restringir o que ele pode ver ou acessar, fazendo esse processo acreditar que está sozinho em outra máquina. A kernel usa cgroups e capabilities pra limitar os recursos e capacidades que esse processo tem acesso e usa namespaces pra limitar os sentidos desse processo, fazendo enxergar coisas como pids, mounts, rede, e tudo mais que são falsos, dentro da Matrix.</p>
<p>Docker é implementado em cima de containerd que é um serviço que precisa rodar como root. Podman e Kubernetes são implementados em cima de RunC que é mais leve e sem necessidade de um daemon auxiliar. Falando nisso, pode ser que Kubernetes ou Terraform confundam pra quem nunca mexeu. Qual a diferença disso pra Docker ou Docker Compose?</p>
<p>Vamos lá. Dockerfile vocês já entenderam, servem pra construir imagens OCI. Docker Compose é hiper útil, tem o Podman Compose que é exatamente a mesma coisa pra quem escolher usar Podman. Ambos começam com um arquivo <code>docker-compose.yml</code>. Deixa eu dar um exemplo besta aqui. Digamos que está trabalhando num projeto em Node. Comece criando um Dockerfile que constrói a imagem do seu projeto Node com seguinte 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-docker" data-lang="docker"><span class="line"><span class="cl"><span class="c"># Use the latest LTS (long-term support) version of Node.js as the base image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">docker.io/node:lts</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Set the working directory to the project root</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Copy the package.json and package-lock.json files to the working directory</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> package*.json ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Install the dependencies</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm install<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Copy the rest of the project files to the working directory</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Expose the app&#39;s port</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">3000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Run the app when the container is started</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span> <span class="s2">&#34;npm&#34;</span><span class="p">,</span> <span class="s2">&#34;start&#34;</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>Começamos a partir da imagem do Node versão lts em vez do mais novo que é latest, lembram? Projetos em produção precisam de estabilidade e não da versão mais nova que pode ser a mais bugada. Sempre use a mesma versão de tudo na sua máquina de desenvolvimento e nos servidores de produção e isso começa pela image de docker.</p>
<p>Dentro do container, o código fonte do seu projeto vai ficar nesse WORKDIR, em /app.  Daí copiamos o arquivo package.json pra dentro do container e rodamos <code>npm install</code> pra criar o <code>node_modules</code> e baixar todas as dependências. Terminamos de copiar todos o códigos fonte do projeto com esse comando COPY. Além disso esse container vai expôr pra fora a porta 3000 que é onde o servidor de node vai dar bind quando rodar o comando que está em CMD, no caso <code>npm start</code>.</p>
<p>Não esquecer de também criar um arquivo <code>.dockerignore</code> praquele comando COPY não copiar o <code>node_modules</code> que está no seu ambiente de desenvolvimento. Dentro do container já rodamos <code>npm install</code> então não precisa copiar tudo de novo. É importante entender onde ficam os arquivos em cada ambiente.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>node_modules
*.log</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>Só com isso já dá pra fazer <code>docker build . -t seuusuario/seuprojeto</code> E já dá pra rodar com <code>docker run -p 3000:3000 seuusuario/seuprojeto</code>. Digamos que seu projeto precisa ter Redis e Postgres instalado. Isso costuma ser um problema, porque se você for um programador bem ocupado e experiente, na sua máquina deve ter diversos projetos. E alguns mais antigos podem depender de Postgres mais antigo, outros de Postgres mais novo. Mas se instalar na sua máquina via o gerenciador de pacotes da sua distro, sempre vai estar com a versão mais nova só.</p>
<p>Pra ter várias versões de node, muita gente usa o NVM ou o meu favorito que é o ASDF, mas e pra ter várias versões de postgres, de redis, de mysql, de kafka e todos esses outros componentes? E é pra isso que entra o Docker Compose. Este é um exemplo de arquivo <code>docker-compose.yml</code> que também ficaria na raíz do seu projeto:</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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000:3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/redis:latest</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>Docker Compose orquestra e sobe vários containers Docker que ele chama de services. O primeiro é sua aplicação.  Ele começa com o atributo build sendo ponto, que é o diretório atual. Vai subir a imagem que seria construída com o Dockerfile que tá no seu projeto nesse diretório. Daí configuramos o projeto com variáveis de ambiente, por exemplo dizemos que DATABASE_HOST se chama &ldquo;db&rdquo;. Normalmente você colocaria &ldquo;localhost&rdquo;, mas cada serviço é como se estivesse rodando numa máquina separada graças ao Namespace UTS que permite que cada um tenha seu hostname separado, por isso o service de Postgres vai se chamar &ldquo;db&rdquo;.</p>
<p>Logo embaixo começa o bloco &ldquo;db&rdquo; onde dizemos que é pra baixar a imagem <code>docker.io/postgres:latest</code> e configuramos o usuário e senha, que é o mesmo que o serviço de node da sua aplicação vai usar. Mesma coisa no terceiro service que chamamos de &ldquo;redis&rdquo; e baixa imagem docker.io/redis. Latest é a versão mais nova, mas num projeto de verdade é bom fixar uma versão em específico pra não confundir.</p>
<p>Veja também que o serviço app, que é sua aplicação, tem um bloco dizendo &ldquo;depends_on&rdquo;, ou seja, o docker compose só vai subir sua aplicação depois que os serviços de postgres e redis carregarem. E pronto. Mesmo que você seja um programador que acabou de entrar no projeto, depois de fazer <code>git clone</code> do repositório, é só rodar <code>docker compose up</code> e pronto! Vai subir todos os serviços e ao final pode ir no navegador a apontar pra localhost:3000 e que vai estar tudo de pé e funcionando. O ideal é manter essa configuração atualizada, porque o que mais acontece é ficar ajustando só na sua máquina e não subir a atualização pro repositório, daí pessoas novas entram na equipe e baixam uma configuração desatualizada e dá vários problemas. Mantenha tudo atualizado.</p>
<p>De bônus, lembra do video de otimização web onde explico sobre coisas como NGINX? Podemos simular o que eu falei naquele video com docker compose. Não é uma coisa que faríamos num projeto de verdade mas só pra ilustrar como aquele diagrama conceitual viraria código 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app_1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app_2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/redis:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">load_balancer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/nginx:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./nginx.conf:/etc/nginx/nginx.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app_1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app_2</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>Os serviços app, redis e postgres são iguaizinhos o do exemplo anterior mas agora adicionei um &ldquo;load balancer&rdquo; que puxa a imagem do nginx. Lembram que expliquei naquele video que ele é quem realmente responde na porta 80? E pro nginx funcionar precisa ter um arquivo <code>nginx.conf</code> no seu projeto que instruímos o docker compose copiar pra dentro do container de nginx. Esse arquivo diz o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>user                 nginx;
worker_processes     auto;

events {
    worker_connections  65535;
    multi_accept        on;
}

http {
    charset              utf-8;
    sendfile             on;
    tcp_nopush           on;
    tcp_nodelay          on;
    client_max_body_size 4m;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    map $remote_addr $proxy_forwarded_elem {

        # IPv4 addresses can be sent as-is
        ~^[0-9.]&#43;$        &#34;for=$remote_addr&#34;;

        # IPv6 addresses need to be bracketed and quoted
        ~^[0-9A-Fa-f:.]&#43;$ &#34;for=\&#34;[$remote_addr]\&#34;&#34;;

        # Unix domain socket names cannot be represented in RFC 7239 syntax
        default           &#34;for=unknown&#34;;
    }

    map $http_forwarded $proxy_add_forwarded {

        # If the incoming Forwarded header is syntactically valid, append to it
        &#34;~^(,[ \\t]*)*([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?(;([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?)*([ \\t]*,([ \\t]*([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?(;([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?)*)?)*$&#34; &#34;$http_forwarded, $proxy_forwarded_elem&#34;;

        # Otherwise, replace it
        default &#34;$proxy_forwarded_elem&#34;;
    }

    upstream www {
        least_conn;
        server app_1:3000;
        server app_2:3000;
    }
    
    server {
    
        listen 80;
    
        location / {
            proxy_pass http://www;

            proxy_http_version                 1.1;
            proxy_cache_bypass                 $http_upgrade;
            
            # Proxy headers
            proxy_set_header Upgrade           $http_upgrade;
            #proxy_set_header Connection        $connection_upgrade;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header Forwarded         $proxy_add_forwarded;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host  $host;
            proxy_set_header X-Forwarded-Port  $server_port;
        }
    
    }
}</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 bloco server, tem a configuração do servidor que vai escutar na porta 80 e fazer o tal proxy reverso pra <a href="http://app"target="_blank" rel="noopener">http://app</a> e app está configurado acima, são os hostnames app_1 e app_2 na porta 3000 de cada container. Lembrem que containers tem rede virtual separada, namespace de portas separado. Então cada um, quando carregar, se penduram na porta 3000 e o nginx vai fazer round robin entre esses dois servidores.</p>
<p>app_1 e app_2 herdam do serviço app que é sua aplicação em Node. Aqui estamos subindo duas instâncias pra ter balanceamento de carga entre os dois. Note que o serviço load_balancer depende desse app_1 e app_2 estarem de pé, e esses dois, por herdarem de app, dependem de redis e postgres estarem de pé e é assim que o docker compose sabe quem tem que subir primeiro pra tudo estar funcionando. E pronto! Isso é uma pequena demonstração de uma aplicação node com dois processos sendo balanceados por um nginx.</p>
<p>Docker Compose foi feito pra infraestrutura bem simples, em particular pra rodar na sua máquina de desenvolvimento e facilitar a vida de programadores. Ele até serve pra fazer deploy em servidores pequenos, tipo numa Digital Ocean da vida. Antigamente tinha um projeto chamado Docker Machine que servia pra conversar com provedores de servidores, mas acho que ninguém usa mais.</p>
<p>Veja como nesse arquivo, pra subir duas instâncias da aplicação, precisou manualmente digitar app_1, app_2, colocar eles no depends_on do load_balancer. Imagina se precisasse subir 10 instâncias. Ou se precisasse configurar replica de Postgres. Ou se precisasse configurar uma fila e vários servidores separados de jobs assíncronos como falei no video do ChatGPT. Esse arquivo não escala, ia ficar complicado ainda mais porque também não sabe só aumentar ou diminuir só os containers de web. Ele não tem noção do que está rodando nos servidores já e só dar manutenção.</p>
<p>É pra isso que entra Kubernetes e Terraform. Kubernetes é um conjunto de tecnologias de infraestrutura elástica. A parte que se aproxima de Docker é o que ele chama de pods, que são containers que rodam com RunC. Mas o Kubernetes faz bem mais, configura VPC que são redes privadas virtuais. Eu posso criar uma rede isolada só pros containers web, outra rede isolada separada só pra servidores de jobs, por exemplo. Sabe lidar com balanceador de carga, o Ingress, adicionar e remover pods dinamicamente, e sabe que está ou na AWS ou no Google Cloud ou na Azure e com isso consigo adicionar serviços exclusivos como o RDS da AWS como banco de dados ou o Cloud SQL do Google Cloud. É bem mais flexível e, por isso, mais complicado.</p>
<p>Entenda, um IaaS ou Infraestrutura como Serviço, como a AWS, me fornece os componentes básicos de infraestrutura. O Kubernetes é melhor pra lidar mais com os pods, os containers, depois que já tem a infra de máquinas e rede pronta. Pra subir essa infra inicial, podemos ou fazer na mão via interface web, o que não recomendamos, ou orquestrar com Terraform.</p>
<p>Por exemplo, máquinas virtuais que o AWS chama de EC2. Esses servidores tem tamanhos diferentes, por exemplo t2.nano que só tem 1 vcpu ou t2.xlarge de 4 vcpus ou a1.metal com 16 cpus, ou m4.16xlarge de 64 vcpus, cada um com um preço diferente por hora. Tem planos diferentes pra S3, pra RDS, pra SQS, e assim vai. E isso só na AWS. Pra organizar tudo, usamos Terraform. Deixa mostrar só alguns trechos de um exemplo de arquivo de configuração, que pode ficar super comprido.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Set up the provider for the desired cloud provider
provider &#34;aws&#34; {
  region = &#34;us-east-1&#34;
}

# Create a VPC to host the resources
resource &#34;aws_vpc&#34; &#34;app_vpc&#34; {
  cidr_block = &#34;10.0.0.0/16&#34;
}

# Create a public subnet in the VPC
resource &#34;aws_subnet&#34; &#34;public_subnet&#34; {
  vpc_id            = aws_vpc.app_vpc.id
  cidr_block        = &#34;10.0.1.0/24&#34;
  availability_zone = &#34;us-east-1a&#34;
}

# Create an Internet Gateway and attach it to the VPC
resource &#34;aws_internet_gateway&#34; &#34;app_igw&#34; {
  vpc_id = aws_vpc.app_vpc.id
}

# Create a route table and a route for the public subnet
resource &#34;aws_route_table&#34; &#34;public_route_table&#34; {
  vpc_id = aws_vpc.app_vpc.id
  route {
    cidr_block = &#34;0.0.0.0/0&#34;
    gateway_id = aws_internet_gateway.app_igw.id
  }
}

# Associate the public subnet with the route table
resource &#34;aws_route_table_association&#34; &#34;public_subnet_association&#34; {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route_table.id
}

# Create an EC2 instance to host the Node.js application
resource &#34;aws_instance&#34; &#34;app_server&#34; {
  ami           = &#34;ami-12345678&#34;
  instance_type = &#34;t2.micro&#34;
  vpc_id        = aws_vpc.app_vpc.id
  subnet_id     = aws_subnet.public_subnet.id
  key_name      = &#34;app-key&#34;
  user_data     = &#34;${file(&#34;user_data.sh&#34;)}&#34;
}

# Create a security group for the EC2 instance
resource &#34;aws_security_group&#34; &#34;app_server_sg&#34; {
  name        = &#34;app_server_sg&#34;
  description = &#34;Security group for the application server&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;0.0.0.0/0&#34;]
  }
}

# Create an RDS instance for Postgres
resource &#34;aws_db_instance&#34; &#34;app_db&#34; {
  engine               = &#34;postgres&#34;
  engine_version       = &#34;11.5&#34;
  instance_class       = &#34;db.t2.micro&#34;
  name                 = &#34;app_db&#34;
  username             = &#34;app_user&#34;
  password             = &#34;app_password&#34;
  vpc_security_group_ids = [aws_security_group.app_db_sg.id]
  vpc_id               = aws_vpc.app_vpc.id
  subnet_group_name    = &#34;app_db_subnet_group&#34;
}

# Create a security group for the RDS instance
resource &#34;aws_security_group&#34; &#34;app_db_sg&#34; {
  name        = &#34;app_db_sg&#34;
  description = &#34;Security group for the application database&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;10.0.1.0/24&#34;]
  }
}

# Create a security group for Redis
resource &#34;aws_security_group&#34; &#34;app_redis_sg&#34; {
  name        = &#34;app_redis_sg&#34;
  description = &#34;Security group for Redis&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 6379
    to_port     = 6379
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;10.0.1.0/24&#34;]
  }
}

# Create an Elasticache cluster for Redis
resource &#34;aws_elasticache_cluster&#34; &#34;app_redis&#34; {
  engine            = &#34;redis&#34;
  cluster_id        = &#34;app_redis&#34;
  node_type         = &#34;cache.t2.micro&#34;
  num_cache_nodes   = 1
  security_group_ids = [aws_security_group.app_redis_sg.id]
  vpc_id            = aws_vpc.app_vpc.id
}

# Create an Nginx load balancer
resource &#34;aws_lb&#34; &#34;app_lb&#34; {
  name            = &#34;app_lb&#34;
  internal        = false
  security_groups = [aws_security_group.app_lb_sg.id]
  subnets         = [aws_subnet.public_subnet.id]
  load_balancer_type         = &#34;application&#34;
  idle_timeout               = 60
  enable_deletion_protection = false
}

# Create a security group for the Nginx load balancer
resource &#34;aws_security_group&#34; &#34;app_lb_sg&#34; {
  name        = &#34;app_lb_sg&#34;
  description = &#34;Security group for the application load balancer&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;0.0.0.0/0&#34;]
  }
}

# Create a target group for the Nginx load balancer
resource &#34;aws_lb_target_group&#34; &#34;app_tg&#34; {
  name     = &#34;app_tg&#34;
  port     = 80
  protocol = &#34;HTTP&#34;
  vpc_id   = aws_vpc.app_vpc.id
  target_type = &#34;instance&#34;
}

# Register the EC2 instance with the target group
resource &#34;aws_lb_target_group_attachment&#34; &#34;app_tg_attachment&#34; {
  target_group_arn = aws_lb_target_group.app_tg.arn
  target_id        = aws_instance.app_server.id
  port             = 80
}

# Create a listener for the Nginx load balancer
resource &#34;aws_lb_listener&#34; &#34;app_listener&#34; {
  load_balancer_arn = aws_lb.app_lb.arn
  port              = &#34;80&#34;
  protocol          = &#34;HTTP&#34;
  default_action {
    type             = &#34;forward&#34;
    target_group_arn = aws_lb_target_group.app_tg.arn
  }
}</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>Veja, o Terraform sabe que o provedor é AWS, e em qual região geográfica, no caso leste dos Estados Unidos, tipo Nova Iorque. Veja configuração de rede privada, subnets, gateway, tabela de rotas, e mais.</p>
<p>Daí contrato um servidor EC2, tamanho t2.micro, e configuro pra rede que tinha declarado antes. Embaixo configuramos grupo de segurança. Depois tenho configuração de banco de dados, depois de Redis que é o serviço que a AWS chama de Elasticache, depois tem o load balancer. E esse arquivo de exemplo é bem simplório e incompleto. Entre Kubernetes e Terraform, tem bastante detalhe pra realmente deployar uma aplicação em produção que esteja configurado de forma eficiente e com segurança.</p>
<p>Numa configuração de verdade, se tudo estiver direitinho declarado, seria só digitar <code>terraform init</code>  pra fazer download e instalar os plugins necessários. Daí <code>terraform plan</code> pra criar o plano do deployment pra esse provedor e a gente checar se tá tudo realmente certo. Coisa que já tá instalada ele não vai instalar de novo, vai só reconfigurar e instalar o que falta.  E no final fazer <code>terraform apply</code> pra realmente contratar os serviços, configurar e colocar nos lugares certos na infra.</p>
<p>Video de hoje não é sobre devops mas já que estou falando de containers e serviços, achei legal só mostrar um exemplo pra quem nunca viu. Mas o ponto é: compare uma configuração longa de Terraform com um simples Docker Compose. Nem chega perto, porque o objetivo das duas ferramentas é diferente. Um é pra orquestrar uma infra de verdade, a outra é pra rodar só na sua máquina pra facilitar desenvolvimento. Pra configurar uma AWS use Kubernetes e Terraform. Pra configurar serviços na sua máquina de desenvolvimento, use Docker Compose. Todo bom projeto deveria ter isso.</p>
<p>Pra finalizar, não vou mostrar mas queria só explicar outras tecnologias de containers que existem. Expliquei sobre imagens OCI de Docker, mas tem outro projeto que recomendo que pesquisem chamado OSTree. Lembra que falei que uma imagem meio que parece montado com commits binários de um Git? Existe de fato um projeto que é um Git de sistema de arquivos, o OSTree. A idéia é a seguinte: imagina que você tem 100 servidores numa AWS da vida, coisa grande. Imagina se sai atualização do Ubuntu Server que roda lá, o que você vai fazer? Mandar rodar <code>apt update; apt upgrade -y</code> e rezar pra não dar pau?</p>
<p>Na verdade não, vai montar imagens com ostree. O sistema de arquivos de todo servidor vai ser como um Git gigante. Daí sai atualização, você atualiza só em uma imagem. O OSTree nos servidores vai baixar a nova imagem e trocar a imagem antiga pela imagem nova. É tipo dar um git pull e trocar pro branch mais novo com um git checkout manja? É uma explicação grosseira, mas é bom saber que isso existe.</p>
<p>Outra coisa é sobre capacidades. Além de Linux capabilities que expliquei antes existem outra formas de limitar acesso a recursos da máquina, especialmente no contexto de segurança. Sabe em aplicativo Android ou iOS hoje em dia que pergunta se você quer dar acesso da camera pra um app ou acesso à sua localização e coisas assim? No mundo Linux isso é responsabilidade de componentes como Seccomp de Secure Computing Mode, que é outra funcionalidade da kernel do Linux pra restringir chamadas de sistema. Ele normalmente é usado em combinação com outros mecanismos de segurança, mas o seccomp em si oferece a infra na kernel pra aplicar filtros nos programas. Se alguém tentar fazer alguma coisa que não deveria, a kernel recusa.</p>
<p>Em cima disso temos projetos como AppArmor e SE-Linux. Olhando só por cima, eles se complementam. Ambos servem pra forçar políticas de segurança nos programas. O AppArmor permite especificar que recursos, como arquivos, dispositivos e sockets de rede um programa tem permissão pra usar. SE-Linux significa Security Enhanced Linux que também força políticas, por exemplo, pra acesso a hardware externo. É ele quem pode proibir um aplicativo de usar sua webcam.</p>
<p>Além disso, do jeito que falei, parece que todo container vai usar Namespaces de Linux com o comando unshare, mas existem outras formas de fazer containers, como o projeto Bubblewrap que é um container mais simples e usa outro recurso da kernel do Linux que são namespaces unprivileged e seccomp-bpf.</p>
<p>Mencionei ostree, seccomp, apparmor, se-linux e bubblewrap porque se você juntar todos eles num único projeto, começa a surgir com coisas como Flatpak. Sabe, aquele instalador de aplicativos? Tecnicamente é como se fosse um Docker ou Podman, mas feito pra funcionar melhor com aplicativos, especialmente aplicativos gráficos, com janela e que precisam acessar seus arquivos, USB e se comunicar com outros programas, precisando ter acesso a serviços como dbus. Tem gente que fica pensando em usar Docker pra instalar um Gimp ou LibreOffice. Não né, pra isso serve Flatpak.</p>
<p>Flatpak roda aplicativos como Spotify, Discord, Zoom, dentro de containers semi-isolados. Menos isolados que um container de Docker, mas um pouco mais seguros do que se fosse instalado direto via apt ou pacman. E o Snap de Ubuntu é similar, um gerenciador de containers. Em vez dos pacotes serem tipo zips como expliquei no video de Slackware, Flatpak e Snap baixam imagens, como imagens de Docker, só que em vez de imagem OCI é OSTree.</p>
<p>Entenda que Flatpak e Snap são primos de Docker e Kubernetes. As tecnologias por baixo também dependem da kernel do Linux e sempre caímos em cgroups, namespaces, capabilities e algum formato de HD virtual dentro de um linguição de bytes binários que chamamos de imagens. Espero que tenha dado uma luz no entendimento de containers e espero que ninguém mais tenha a pachorra de confundir containers com máquinas virtuais. Se ficaram com dúvidas mandem nos comentários abaixo, 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>containers</category><category>linux</category><category>devops</category><category>akitando</category></item><item><title>[Akitando] #138 - Meu Começo de Carreira Durante a Bolha - 1997 a 2002, Bug do Milênio, Crash</title><link>https://akitaonrails.github.io/2023/02/14/akitando-138-meu-comeco-de-carreira-durante-a-bolha-1997-a-2002-bug-do-milenio-crash/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/02/14/akitando-138-meu-comeco-de-carreira-durante-a-bolha-1997-a-2002-bug-do-milenio-crash/</guid><pubDate>Tue, 14 Feb 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/4_59jz7BOMA"
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;Já tinha feito videos sobre antes de 1997 e sobre depois de 2002, ficou faltando contar justamente sobre como foi meu começo de carreira, a transição da faculdade pros primeiros empregos, de agência a pontocom, de CD-ROMs pra Web, tudo que eu passei no final dos anos 90 até a virada do século, incluindo o Bug do Milênio.&lt;/p&gt;
&lt;p&gt;Muitos agora começando carreira ou vendo o atual Crash da Bolha Tech estão inseguros, talvez sirva como perspectiva pra dar uma noção de que crises são cíclicas e qual é a melhor posição pra se estar pra passar ileso todas as vezes.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/4_59jz7BOMA"
    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>Já tinha feito videos sobre antes de 1997 e sobre depois de 2002, ficou faltando contar justamente sobre como foi meu começo de carreira, a transição da faculdade pros primeiros empregos, de agência a pontocom, de CD-ROMs pra Web, tudo que eu passei no final dos anos 90 até a virada do século, incluindo o Bug do Milênio.</p>
<p>Muitos agora começando carreira ou vendo o atual Crash da Bolha Tech estão inseguros, talvez sirva como perspectiva pra dar uma noção de que crises são cíclicas e qual é a melhor posição pra se estar pra passar ileso todas as vezes.</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:41 - CAP 1 - Relembrando 1997 - Retrospectiva</li>
<li>05:37 - CAP 2 - Meu Primeiro Emprego numa Agência - CD-ROMs</li>
<li>14:03 - CAP 3 - Minha Primeira Comida de Rabo - Sendo Estagiário</li>
<li>18:42 - CAP 4 - Minha Primeira Vez Aprendendo &ldquo;Backend&rdquo; - Microsoft</li>
<li>22:09 - CAP 5 - Combatendo o Bug do Milênio - Y2K</li>
<li>30:24 - CAP 6 - Meu Primeiro Merge e Layoff - A Primeira Pontocom</li>
<li>34:42 - CAP 7 - Entrando numa Agência &ldquo;Big 5&rdquo; - F/Nazca</li>
<li>37:55 - CAP 8 - A Primeira Vez Procurando Emprego - PSN</li>
<li>41:50 - CAP 9 - A Segunda Vez Procurando Emprego - SAP</li>
<li>44:54 - CAP 10 - O que me Motivava? - Perspectivas</li>
<li>46:45 - CAP 11 - O seu Colega Contrataria Você? - Verdadeiro Networking</li>
<li>52:10 - CAP 12 - Varar Noites?? - DISCLAIMER</li>
<li>54:00 - CAP 13 - Mas Akita: O que você recomenda pra mim? - O que o Akita NÃO Fez?</li>
<li>58:42 - 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>
<p>Playlist: Minha História (<a href="https://www.youtube.com/watch?v=ii5Q2fCl8C0&amp;list=PLdsnXVqbHDUe50Aqsa-i_NW42AkuByQZa"target="_blank" rel="noopener">https://www.youtube.com/watch?v=ii5Q2fCl8C0&list=PLdsnXVqbHDUe50Aqsa-i_NW42AkuByQZa</a>)</p>
</li>
<li>
<p>Playlist: Eu Avisei (<a href="https://www.youtube.com/watch?v=wpPv1dJWjDs&amp;list=PLdsnXVqbHDUehzKjiRruy--gncHz9Injy"target="_blank" rel="noopener">https://www.youtube.com/watch?v=wpPv1dJWjDs&list=PLdsnXVqbHDUehzKjiRruy--gncHz9Injy</a>)</p>
</li>
<li>
<p>Playlist: Carreira (<a href="https://www.youtube.com/watch?v=A2-yU3YjB1U&amp;list=PLdsnXVqbHDUcAWAN3cXHWnM9JEw2gjIN9"target="_blank" rel="noopener">https://www.youtube.com/watch?v=A2-yU3YjB1U&list=PLdsnXVqbHDUcAWAN3cXHWnM9JEw2gjIN9</a>)</p>
</li>
<li>
<p>Macworld 1997: The return of Steve Jobs (<a href="https://www.youtube.com/watch?v=IOs6hnTI4lw"target="_blank" rel="noopener">https://www.youtube.com/watch?v=IOs6hnTI4lw</a>)</p>
</li>
<li>
<p>LinuxMall - A história de uma das primeiras lojas virtuais do Brasil (<a href="https://www.youtube.com/watch?v=BGVmQEpKTSI"target="_blank" rel="noopener">https://www.youtube.com/watch?v=BGVmQEpKTSI</a>)</p>
</li>
<li>
<p>The iPod Revolution (<a href="https://www.youtube.com/watch?v=FQwzI4kBG1o"target="_blank" rel="noopener">https://www.youtube.com/watch?v=FQwzI4kBG1o</a>)</p>
</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>Pra quem acompanha o canal desde o começo, deve ter notado que eu contei um pouco da minha carreira na área com videos como Meus Primeiros 5 Anos que fala um pouco de 1990 até 1995 que foi quando comecei na faculdade. Daí o video sobre o Livro &ldquo;Estrada do Futuro&rdquo;, falando um pouco sobre 1996. No video da minha Máquina do Tempo falo da importância de backups e alguns trabalhos que eu fiz por volta de 1996 a 1998. Teve os 2 videos curtos da Sua Linguagem é Especial? Onde falo sobre dois pequenos cases técnicos por volta do ano 2000.</p>
<p>Teve o video de Você não entende nada de Enterprise, onde falo sobre SAP, que foi meu período de consultoria entre 2002 a 2007. Finalmente, o video da História do Ruby on Rails que cobre um pouco entre 2007 a 2016. Mas ficou um buraco dessa história entre mais ou menos 1996 a 2002. Justamente o período final da Primeira Grande Bolha da Internet que explodiu no ano de 2001, junto com as Torres Gêmeas. O que eu estava fazendo naquela época?</p>
<p>(&hellip;)</p>
<p>Fiquem até o final do video que tenho alguns comentários a fazer sobre minha playlist de Eu Avisei, em particular o video sobre o estouro da Bolha de Tecnologia recente. Se quiser entender mais os princípios financeiros por trás dessa bolha, não deixe de assistir a playlist inteira. Até já aviso que esse video pode soar um pouco auto ajuda, especialmente pro fim, mas é o mais honesto que eu posso ser comparando minha situação 25 anos atrás com a situação de agora e talvez dar alguma perspectiva ou inspiração. Depois vocês me digam.</p>
<p>Mas vamos lá. O período entre 1996 até 2002 foi provavelmente quando eu mais aprendi tecnologias que não são mais usadas hoje em dia. A idéia é tanto um pouco de nostalgia pra vocês que estão na faixa dos 40 anos, mas também perspectiva pra vocês que estão na faixa dos 20 anos. O que o Akita estava fazendo depois da faculdade?</p>
<p>Pra quem não lembra, 1997 foi o ano que morreu a Princesa Diana, foi o maior rebuliço isso. Eu nem acompanhava mas lembro que falavam disso toda hora. Também foi o ano em que a J.K. Rowling publicou o primeiro livro do Harry Potter. Eu nem sabia disso, só fiquei sabendo que esses livros existiam mais perto de quase lançar o primeiro filme. Outra coisa engraçada, foi o ano que o Mike Tyson mordeu fora a orelha do Evander Holyfield, quem não lembra disso? Falando nisso, pro povo que curte perder tempo com documentário da Netflix, foi o ano que o O.J. Simpson foi julgado culpado.</p>
<p>E pra gente de tecnologia, foi o ano que o Steve Jobs anunciou sua volta pra Apple Computers, que na época tava negativo 1 bilhão de dólares, a dias de fechar as portas. Conseguem imaginar isso? A empresa mais valiosa de tecnologia hoje, em 1997 estava praticamente falida. Os anos 90 foram muito divertidos.</p>
<p>Enquanto isso, eu tinha 20 anos, indo pro 3o ano de faculdade. Quem assistiu os episódios anteriores vai lembrar que eu tava trabalhando como monitor na sala de computadores do CCE, aprendendo tudo que podia sobre UNIX, Internet, essa novidade chamada Web. Ao mesmo tempo, assim como a maioria de vocês de 20 anos, eu tava de saco cheio da faculdade. No meu caso, passava muito pouco tempo na minha faculdade mesmo, no IME. Eu preferia gastar meu tempo na sala de computação do CCE que ficava atravessando a rua, na Politécnica, e meus amigos eram todos da Engenharia Elétrica. Eu fazia Ciência da Computação e nessa época já quase não tinha nenhum amigo no meu curso mesmo.</p>
<p>Todo dia eu ficava deslumbrado com as novidades que surgiam. Estávamos no meio pro fim da transição da arquitetura de 16-bits pra 32-bits com os Pentiums. Estávamos no fim da transição de ambiente texto do MS-DOS pros gráficos do Windows, OS/2 e no caso de UNIX eu aprendi que existia Solaris, Irix. Estávamos entendendo como criar aplicações distribuídas nessa tal de Internet em vez de programas isolados feitos no meu antigo Clipper ou FoxPro. Novas linguagens estavam começando, como Java, Delphi. E naquela época não existia nada na Web ainda que chegasse perto de redes sociais.</p>
<p>Não existia Reddit, não existia Stackoverflow, não existia blogs, não existia Google. O máximo que a gente tinha eram sites que manualmente indexavam outros sites como o Yahoo ou Lycos, e as primeiras tentativas de indexação automática com sites como WebCrawler ou o famoso Altavista. Mas comparado com hoje, era hiper rudimentar. Pesquisar pra comprar componentes de computador, por exemplo, a gente ainda fazia via página de classificados nos jornais.</p>
<p>Foi naquela sala de computador da Politécnica, que tinha um quadro de avisos onde encontrei o anúncio do meu primeiro emprego. Uma agência pequena de novo esquisito, Kropki, não muito longe dali, de um professor da Escola de Comunicação e Artes na USP, a ECA, que era na mesma rua, passando a Politécnica, o IME que é minha faculdade, daí vinha a FEA que é a Faculdade de Economia, Administração e Contabilidade, daí era a ECA. Pouco antes da famigerada FFLCH, que recomendo passar longe. Enfim.</p>
<p>A agência em si, era caminhando pra fora do campus da USP, atravessando a ponte da Cidade Universitária em direção à Praça Panamericana. Uma caminhada de uns 5km a partir da minha faculdade, ou uns 40 minutos a pé. Na época eu considerava isso perto o suficiente pra ir a pé mesmo. Nessa época às vezes pegava o carro do meu pai emprestado, mas andava muito a pé e de ônibus mesmo, então era importante não ser longe demais e em São Paulo, 5km é o que chamamos de &ldquo;aqui do lado&rdquo;.</p>
<p>Fui até lá, e acabei sendo contratado como estagiário. Lá era uma agência que tinha meia dúzia de funcionários, uns 3 ou 4 designers que mexiam em Photoshop, alunos da ECA, ajustando capas de jogos de videogame, pra antiga Tectoy. Eles faziam as capas dos jogos de Megadrive, e tinham alguns outros clientes. Eles queriam entrar no mercado de multimídia e Web, mas eram mais usuários de Adobe do que qualquer outra coisa e precisavam de alguém de tecnologia, e esse alguém seria eu.</p>
<p>Lá tinha uns 3 ou 4 Mac Quadra ou Centris, não lembro exatamente. A linha feia bege da era do MacOS System 7 e 8, o período negro da Apple sem o Steve Jobs. Mesmo assim eu achava fascinante, afinal era a primeira interface gráfica comercial de sucesso e o Windows 95 ou 98 daquela época ainda era super cru em comparação. Infelizmente eu era o único que ia ter que usar um PC com Windows justamente pra experimentar as coisas novas saindo pra essa tal de Web.</p>
<p>De curiosidade, os Mac Quadra daquela época acho que eram os últimos modelos anteriores à migração pra PowerPC então ainda usavam os processadores Motorola 68000, como o 68040 de 33Mhz, talvez uns 16 Megabytes de RAM, HD externo SCSI. Foi a primeira vez que vi SCSI e zip drives da Iomega. O meu PC era mais novo e mais potente, um Pentium MMX acho que já de 100 Mhz, com uns 16, talvez 32 megabytes de RAM. Monitor Super VGA de 1024 por 768 pixels, um pouco mais do que o que chamamos de HD hoje em dia, que é 720p. RAM nos anos 90 era super caro, talvez a parte mais cara do computador.</p>
<p>Também de curiosidade, meu salário de estagiário naquela época foi de uns 500 reais. Ajustado pelo IPCA seria equivalente hoje a uns 2 mil reais. Super justo. Eu tava empolgado pra mexer mais na Web, mas queriam que eu aprendesse a fazer CD-ROMs. Na prática eu tava empolgado pra mexer em qualquer coisa que fosse no computador, e estava sendo pago pra isso, tanto fazia pra mim com o que eu ia ter que mexer.</p>
<p>Entenda. Não existia LinkedIn, nem Twitter, nem nada. Não existia eventos de tecnologia pra programadores. No máximo eventos corporativos de Microsoft ou Oracle. Meus colegas da faculdade estavam todos procurando programas de trainée numa Oracle da vida, ou bancos. Ninguém que eu conhecia tava indo trabalhar em agência. Eu deveria estar preenchendo formulários de programas de treinamento na indústria automobilística, seguros ou saúde, pra aprender a mexer em mainframes e Cobol. Mas eu nunca nem procurei saber como esse mercado funcionava. Eu achei esse emprego, tava meio de saco cheio da faculdade, liguei o &ldquo;foda-se&rdquo; e só fui.</p>
<p>Pra vocês terem uma idéia do que significava isso de CD-ROM, eu instalei um Windows Me num emulador chamado DOXBox-X, altamente recomendado se quiser rodar programas e até jogos de DOS daquela época, roda super bem mesmo num PC modesto de hoje. Uma empresa de tecnologia que era super reconhecida naquela época se chamava Macromedia, que fazia esse troço chamado Director. Director quando abre tem o que ele chama de stage, tipo um palco. Embaixo tem uma timeline.</p>
<p>Podemos abrir essa janela de casting, que é o nome que se dá ao grupo de atores escolhidos pra uma peça. O Director tem esse nome porque usa essa metáfora de um teatro, palco, atores, casting. Enfim, podemos inserir diversos tipos de objetos como bitmaps, clipes de video e no caso estou criando um objeto de vetor, semelhante ao que se faria com CorelDraw ou Adobe Illustrator. Olha só, aparece aqui na janela de casting. Agora posso arrastar pro stage, pro palco.</p>
<p>Daí posso criar keyframes, mover o objeto um pouco, ir mais pra frente, criar outro keyframe, mover o objeto mais um pouco e ir fazendo assim. Agora voltamos pro começo da timeline, damos play e voilá, uma animação super crua. E se isso pareceu familiar pra alguns de vocês, o Director começou sendo um software pra exportar executáveis pra gravar em CD-ROM, mas com a Web não ia demorar pra passar a exportar num formato distribuível pela Web instalando plugin em navegadores da época como o Netscape ou as primeiras versões do famigerado Internet Explorer. Era o formato Shockwave.</p>
<p>Com a evolução rápida da Web, a Macromedia simplificou o Director, focou mais na parte vetorial, as ferramentas mais simples de animação e chamou de Macromedia Flash. Essa é a origem do Flash, mas estou me adiantando. Deixa eu voltar pro Director. Assim como em Flash, todo mundo sabe que tinha uma linguagem derivada de Javascript pra programar interatividade, chamada ActionScript. O Director também tinha uma linguagem, só que ainda mais rudimentar, declarativa, chamada Lingo.</p>
<p>Por exemplo, posso adicionar um script aqui num dos frames pra pausar. Pronto, rebobino a fita, dou play de novo e ele pára aqui. Se tem o comando &ldquo;pause&rdquo;, deve ter o comando &ldquo;resume&rdquo;. Posso adicionar um novo comportamento nesse sprite, no evento &ldquo;mouseUp&rdquo; que é quando eu termino de apertar o botão do mouse, posso adicionar &ldquo;resume&rdquo; usando essa ajuda rudimentar aqui que lista todas as funções do Lingo. Resume sprite 1. Dou play de novo, ele pausa, clico no ator e &hellip; erro.</p>
<p>Handler not defined. Maldito erro genérico, Javascript deve ter se inspirado em Lingo. Bom, vamos procurar no Stackoverflow então. Só que em 1997 não tinha Stackoverflow, nem Google, nem fóruns de discussão. Eu tinha um livro impresso. O software tem essa documentação local aqui. E era isso. Nem se eu perguntar hoje pro ChatGPT ele sabe me dar uma boa resposta. E agora em 2023 obviamente não lembro mais o que significa isso. A única coisa que me ocorre é uma gambiarra. Já que resume não funciona, estou aqui no frame 6, então vou dar comando pra pular pro próximo frame e torcer pra continuar. Go to frame 7. Como eu disse é uma linguagem declarativa.</p>
<p>Rebobina, dou play, pausa, clico no ator e boom, ele continua como eu queria. E meu dia a dia era assim: mensagens de erro que não diziam coisa nenhuma, ficar fuçando a documentação e tentando coisas até conseguir fazer funcionar. Imagino que não é muito diferente do que a maioria de vocês fazem hoje em dia, só que eu tinha bem menos opções de onde pesquisar erros. Foi fazendo assim que consegui compilar meu primeiro CD, que foi esse aqui do lançamento do Volks Parati 4 portas.</p>
<p>Se não me engano foi uma campanha pra dealerships, pra treinamento dos vendedores de concessionárias, mas enfim, é como se fosse um site web estático, mas em formato de CD-ROM. &ldquo;E por que não um site de uma vez?&rdquo;, alguém poderia perguntar. Por que internet em 1997 era altamente opcional. A maioria da população ainda nem sabia o que era isso. Mesmo em pequenas empresas, no máximo o povo tinha conexão discada via iG ou Terra ou America Online, ou UOL. E a conexão média era de 19 kilobits por segundo.</p>
<p>Uma imagem de 160 pixels por 150 pixels, metade do que seria a tela de um Nintendinho, com 7 bits de cor, dava uns 20 kilobytes. Imagine múltiplas imagens dessa. Levava vários segundos a minutos pra montar uma tela como essa via web. Por isso a Web de 1997 era bem mais rudimentar do que era possível fazer num CD-ROM, que já vinha com todas as imagens, clipes de video, áudio e tudo mais local, num CD de 650 megabytes. Streaming de video e áudio era impossível naquela época. Só que todo mundo pagou uma grana investindo nos tais kits multimídia, como da Creative Labs, e queriam ver videos e áudio. O que a gente fazia em CD era impossível de fazer na Web.</p>
<p>Assistam meu video sobre compressão, de 25 TB pra 5 gigabytes, onde explico sobre cores, algoritmos de compressão e tudo mais. Boa parte do meu trabalho era converter Jpegs com 8 bits de cor em GIFs com paleta de cor menor pra fazer versão web de algumas coisas. Naquela época, o mais comum eram monitores VGA de 800 por 600 pixels, que seria o nosso &ldquo;4K&rdquo; considerando que o padrão era tipo 320 por 240 num sistema operacional texto como MSX, Commodore ou consoles como Super Nintendo ou Megadrive. E com 256 cores.</p>
<p>Olha alguns dos sites que eu fazia naquela época, vou só passar aqui alguns slides de fotos de tela, mas deve dar pra dar uma noção de quão limitado era a tecnologia da época. Hoje em dia vocês estão acostumados no mínimo a Full HD que é 1080p e pelo menos 8 bits de cor com milhões de cores possíveis, ou HDR com 10 ou 12 bits de cor em 4K nos monitores mais avançados. Pular de 80 colunas por 24 linhas em monitor de 240p, pra VGA colorido em 768p, é como vocês pulando de Full HD pra 4K hoje em dia.</p>
<p>O CD-ROM mais complicado que eu fiz, foi o projeto do lançamento do Fiat Marea. Esse foi particularmente difícil porque a Fiat ainda tava mudando detalhes como opcionais do carro, cores de tecidos, e coisas assim enquanto a gente tava montando o CD. Os designers tinham que fazer coisas como mudar a cor dos tecidos nas fotos antigas direto no Photoshop, porque não ia dar tempo de tirar novas fotos em estúdio com os novos materiais. Foi a primeira vez que eu vi Photoshop sendo usado pra coisas desse tipo e isso explodiu minha cabeça naquela época. Foi vendo isso que decidi aprender mais sobre essa parte gráfica e aprender Photoshop.</p>
<p>Se eu montasse o CD do jeito tradicional, como eu mostrei no começo, até que não era um projeto difícil. Mas eu era um aluno de ciências da computação aprendendo coisas como Delphi, Java e esse troço novo na época chamado Orientação a Objetos. E eu ficava tentando adicionar objetos em tudo. Delphi era basicamente Pascal com objetos, tanto que chamava Object Pascal. Mas eu usava ainda o Turbo Pascal 6 de DOS, que não tinha classes, mas tinha objetos rudimentares, e eu queria adicionar suporte a pelo menos encapsulamento e talvez herança nessa linguagem rudimentar de Lingo pra Director.</p>
<p>Tá vendo esse menu principal? Ele aparece em todas as seções do CD. Cada seção o Director recomenda organizar como um arquivo separado, daí um evento num arquivo basicamente carrega outro arquivo. Seria como um anchor em HTML que tem a URL pra carregar outra página. Eu queria fazer um objeto reusável entre todos, o que vocês chamariam hoje de componente. Obviamente isso é um caso de uso não documentado, que não tinha em nenhum livro. E óbvio que deu um monte de bug que eu tive que varar muitas noites pra resolver. E quando digo muitas, quero realmente dizer MUITAS noites sem dormir.</p>
<p>Lembro até hoje que por minha culpa, quase não deu pra lançar o CD. Entenda: hoje em dia se alguma coisa dá pau, fácil, só subir um commit de correção, deployar e em alguns minutos tá corrigido. Mas um CD exige um compromisso que nenhum de vocês vê mais hoje: uma vez prensado, não tem como corrigir os CDs distribuídos. Esses CDs do Fiat Marea iam ser distribuídos em revistas da Editora Abril como Quatro Rodas e Playboy, e uma vez queimado o CD Gold, o master, não tem mais como corrigir.</p>
<p>O processo todo já tava atrasado de qualquer jeito porque a Fiat não parava de fazer modificações de última hora que exigia mudar textos da especificação dos modelos, opcionais, e tudo mais, e em cima disso toda vez que a gente ia apresentar pro gerente de contas da Fiat, minha programação dava um bug inesperado. No último dia, literalmente na última hora, lembro de compilar a última versão, gravar num CD na agência, pegar o carro, tipo 10 horas da noite e ir até a Zona Norte de São Paulo, onde ficava a antiga Sonopress, a fábrica que prensava os CDs em massa.</p>
<p>Pensa, eu era estagiário, tinha 20 anos, fui sozinho com o CD embaixo do braço pra mostrar pro gerente de contas mais rabugento, mau educado e nervoso da face da Terra, roendo todas as unhas das mãos e dos pés, porque depois de tantos dias de bugs, eu tava com minha confiança por um fio. E por sorte, nos 45 do segundo tempo, navegamos por todo o CD e funcionou sem bugs.</p>
<p>Ali ele deu ok pra Sonopress prensar. Lembro até hoje, saindo com ele, no estacionamento, ele me parou e ventilou tudo que tinha que ventilar na minha cara. Eu não lembro de uma palavra do que ele disse, só que eu fiquei ali, sei lá, 11 horas da noite, paralizado no meio do estacionamento vazio, só ouvindo. Foi a primeira comida de rabo sério que tomei. Vocês acham que eu sou rabugento nos videos? Volta aqui depois que experimentarem terem saliva voando na sua cara de alguém gritando.</p>
<p>Eu queria ter uma moral da história, tipo que depois disso eu me tornei mais cuidadoso, mais prudente, que me ajudou de alguma forma, mas tem uma coisa que descobri recentemente que talvez explique como reagi na época: eu fiz teste preliminar com psicólogo e de fato, parece que eu tenho um certo grau de Asperger, tipo um autismo leve. Eu compartimentalizo. Nada se torna gatilho pra eu desistir ou fazer alguma coisa por conta de algum evento traumático ou importante de alguma forma. Eu compartimentalizo, embrulho, enterro e sigo em frente. Não tenho idéia de como outras pessoas lidam com isso, porque uma pessoa mais sensível provavelmente teria abandonado tudo naquele dia.</p>
<p>Eu já tive que ouvir outras pessoas ventilando mais do que seria profissional, mas naquele nível, felizmente foi a primeira e última vez. Eu não sei fazer as coisas como as outras pessoas esperam que eu faça. Se eu coloco uma coisa na cabeça, quero tentar fazer, independente do trabalho que vou ter. Foi o que falei no video de Por que fazer o que Ama, só que não. Eu não faço as coisas que eu gosto, eu faço as coisas porque eu gosto de saber se consigo fazer.  Depois que consigo, perco o interesse, e procuro outra coisa.</p>
<p>Na faculdade eu tava empolgado com UNIX, com Delphi, com Java,  HTML, Javascript, não muito diferente do que muitos de vocês hoje devem estar vendo. Talvez Python, que ainda não era popular na época. Mas na agência me pediram pra aprender a fazer CD-ROM com Director. Eu nunca tinha ouvido falar nisso até entrar lá, nunca tinha planejado gastar 1 ano fazendo CD-ROM. Mas quando ouvi essa proposta, coçou os dedos. Eu queria saber se conseguia fazer. E não só fazer, mas fazer de formas que não era comum. O preço disso era que eu ia varar noites, tomar comida de rabo, mas eu entregava. Essa sempre foi minha motivação: entregar.</p>
<p>Em paralelo eu fazia os sites estáticos, em HTML puro. Aliás, na época não existia CSS ainda, tá? Formatação era usando tags como &ldquo;font&rdquo; e atributos de estilo inline com &ldquo;style&rdquo;. Aos poucos fui mexendo em mais tecnologias Web. A internet começou a ficar um pouco mais rápida, pulamos pra modems de 56 kilobits por segundo, depois pra 128 kilobits quando o Speedy, Ajato e outros provedores de banda larga começaram a chegar. Daí migrei de Director pra Flash, e comecei a aprender ASP.</p>
<p>Por volta de 1998 eu fiz um pequeno trabalho pra construtora Camargo Correa que envolvia indexar vários documentos PDF que eles tinham pra fazer tipo um motor de busca, um search engine, usando o antigo Microsoft Index Server que depois acho que virou Indexing Services. Isso tinha no IIS que vinha nos antigos Windows NT 4. A idéia era fazer um pequeno site de intranet, que seria acessado só internamente, pra conseguirem pesquisar rapidamente a biblioteca de PDFs deles.</p>
<p>De novo, 1998 foi quando o Google tinha acabado de ser fundado, literalmente funcionando da garagem. Nessa época já existiam várias tecnologias pra pesquisar conteúdo. Todo mundo rapidamente começou a perceber que com a microinformática sendo largamente adotada, documentos digitais começando a ganhar peso, o volume de conteúdo aumentava exponencialmente e precisávamos dar um jeito de organizar tudo e facilitar pesquisa.</p>
<p>Eu sabia disso em 1998? Não tinha nem idéia, só viria a começar a usar Google talvez no ano 2000. Eu era assim: &ldquo;ow, tem que fazer um site pra pesquisar documentos&rdquo;. Eu sabia como? Não tinha nem idéia, daí ia fuçar como resolver. Microsoft era uma das poucas empresas que tinha boa documentação online no MSDN. Assim esbarrei em Index Server. Pra isso precisava aprender ASP, daí lá vou eu aprender ASP e ao mesmo tempo tinha que saber Microsoft SQL Server. Eu sabia o que era SQL? Não. Lá vou eu aprender. Tudo pra fazer esse um projetinho.</p>
<p>A gente já ouvia falar da tal da Oracle, que na época era uma das maiores gigantes no mundo de tecnologia com seu banco de dados que eu sabia toda grande empresa usava. Eu nunca tinha visto um, só sabia que era difícil. E não era como se desse pra ir no site e baixar. A licença custava milhares de dólares, vinha em pacote físico, com livros, CDs e tudo mais. Não era acessível pra meros mortais como eu. E foi aí que a Microsoft foi inteligente, porque um SQL Server, eu conseguia baixar, aprender e usar, tudo online. E foi assim que comecei a aprender sobre SQL.</p>
<p>Comecei com o SQL Server 6.5 e de curiosidade, o SQL Server começou como um port do antigo banco de dados da Sybase pra OS/2, em 16 bits, em 1989. Saiu a versão 4.2 pra NT em 1993 e o 6.0 saiu em 1995. A versão 7 sairia um pouco depois que eu comecei a aprender. Foi a versão que eu mais usei.</p>
<p>De qualquer forma, a vida daquela pequena agência tava chegando no fim por volta de 1998. Acho que trabalhei lá só uns 2 anos e nesse período aprendi HTML, Director, Flash, comecei a aprender ASP, SQL Server. Mas pelo jeito o dono não soube administrar bem a empresa, ela foi diminuindo, até que só sobrou eu e mais um outro funcionário e passamos a trabalhar da sala de professores na faculdade, na ECA. Fiquei pouco tempo lá, porque um amigo meu me chamou pra trabalhar na empresa dele. Como parecia que não tinha mais pra onde ir mesmo, resolvi aceitar.</p>
<p>Essa empresa foi a STI, um dos primeiros provedores de internet do Brasil. Começou como uma BBS e evoluiu pra internet. E lá trabalhava meu vizinho. A gente se conhecia desde adolescente e aprendemos a mexer em computador juntos. No episódio dos primeiros 5 anos eu falo dele, o Thiago Madeira, que teve sua própria BBS antes de ir pra STI enquanto eu ia pra faculdade e foi ele que tinha o contato que me colocou como monitor da sala de computação da USP pra baixar pirataria pra BBS dele. Contei essa história no outro video.</p>
<p>Enfim, já era quase meio de 1999 e a empresa tinha um problema. Por anos toda a administração era controlada por um software feito por um dos co-fundadores, no antigo Clipper de DOS, que eu conhecia e programava no começo dos anos 90. Ele controlava tudo: cadastro de cliente, faturas, cobranças, suporte técnico e  mais. Programas em Clipper eram feitos pra funcionar com um único usuário numa única máquina, mas ao longo dos anos 90 criaram várias gambiarras pra compartilhar o arquivo de banco de dados via redes, como em infraestrutura da antiga Novell Netware.</p>
<p>Dava pra ver que era a clássica empresa que nasceu em garagem e cresceu rápido: o servidor já tava perto do último suspiro, era um PC de gabinete aberto, com um ventilador apontado pra ele, ligado em rede pra todo mundo da empresa conseguir acessar. A forma de fazer isso era bloqueando o arquivo de banco de dados, fazendo alguma operação, daí desbloqueando pro próximo poder usar. Essa é a forma mais rudimentar de banco de dados que existia, mas funcionou por anos, até a empresa ter dezenas de funcionários e milhares de clientes.</p>
<p>Mas esse não era o maior problema, porque eles tinham dinheiro de sobra pra colocar tudo num hardware mais moderno. O problema era que Clipper foi feito nos anos 80, numa época onde 640 kilobytes de RAM era o máximo. Onde a gente literalmente escovava e economizava bits onde dava, em particular nos tipos de data. Pra que gravar 1980 se só gravar 80 já era suficiente? Economizava 2 bytes por registro fazendo isso. Daí na hora de mostrar era só concatenar &ldquo;19&rdquo; na frente e pronto. Quer saber quantos anos se passaram de 1990 até 1998? Só fazer 98 menos 90. Ponto.</p>
<p>Mas em breve, teríamos contas assim: 2000 menos 1998 dá quanto? Não dá pra fazer 00 menos 98. Dá pau. Esse é o famoso Bug do Milênio ou Y2K como chamamos hoje. A solução correta era recompilar num Clipper mais novo, que tinha correção de ano 2000, vasculhar o código-fonte onde faz contas com datas e checar se não tem nada hardcoded com 2 bytes e ir ajustando. Daí fazer uma versão mais nova que funcionaria depois do ano 2000.</p>
<p>Só que era uma empresa de tecnologia, nova, com pessoas novas, com um co-fundador técnico, coisa rara na época. Foda-se, vamos reescrever tudo em tecnologias mais modernas. Na época, era justamente pra fazer tudo em 32-bits, com a moderníssima linguagem ASP, pra funcionar em cima de SQL Server, num Windows NT, coisa profissa. Obviamente eu fiquei empolgado, afinal, era justo as coisas que eu tava começando a aprender por causa do último projeto na agência. Alguém vai me pagar pra eu continuar aprendendo? Bora.</p>
<p>O problema: tinha menos de 6 meses pra fazer as coisas funcionarem, já estávamos por volta de junho de 1999. Lembrem-se que existem coisas como plano semestral. Tem partes que já iam começar a falhar se fôssemos imprimir boletos no futuro. A partir de agosto de 1999 já ia passar a barreira pra janeiro de 2000. Eu não tinha idéia do trabalho que algo assim poderia dar, se tivesse talvez não tivesse concordado e sugerido um plano mais convencional como tentar ajustar em cima do código legado mesmo. Mas eu era um moleque de 22 anos que só queria a oportunidade de mexer com coisas novas.</p>
<p>Por sorte a equipe que foi formada era muito boa. Eu não era o melhor mas certamente era o mais arrogante, não tenha dúvidas disso. Na minha cabeça tudo dava. Eu não lembro mais de todos os detalhes, mas precisávamos fazer ferramentas de atendimento pra todo o pessoal do suporte técnico que atendia os clientes, tipo ferramenta de help desk, mais um banco de conhecimento com todos os truques que se sabia pra ajudar os novos atendentes. Precisava fazer ferramenta da parte financeira, consolidação de contas, impressão de boletos, gerenciar vendas e por aí vai. Era tipo um mini ERP dessa empresa.</p>
<p>Falando em Bug do Milênio, muita gente de fora achava que ia chegar 31 de dezembro de 1999 e na virada do ano tudo ia quebrar. Inocentes, tudo já ia começar a quebrar pelo menos 6 meses antes, em alguns lugares antes ainda. Que nem o caso de boletos que eu falei. Como você imprime um boleto datado pra janeiro de 2000? As empresas não estava às cegas assim não, todo mundo já tava tendo que lidar com esse problema muitos meses antes do que a população em geral imaginava.</p>
<p>Lembro até hoje de outro episódio &ldquo;what the fuck&rdquo; que passei. Se não estou enganado, todo mês imprimiam os boletos pra mandar por correio pra todos os milhares de clientes. E não lembro detalhes, mas justamente acho que se o cara contratava um plano semestral, já mandavam os 6 boletos de uma só vez. E por isso já por agosto ia ter o problema do ano 2000 que acabei de explicar. Em determinado momento de 1999 as pessoas já iam receber boletos datados pra pagar no ano 2000.</p>
<p>Lembrando também que falei que o sistema antigo em funcionamento era em Clipper, ou seja, com banco de dados que é só um arquivão gigante em formato DBF, sem servidor intermediando. Também falei que era um arquivão compartilhado numa rede Novell, que rodava em DOS com sistema de arquivos FAT16. Quem já lidou com isso sabe: esse sistema de arquivos não tem absolutamente nenhum tipo de funcionalidade de prevenção e correção de erros. Basta cair a luz uma vez pra começar a dar muita merda. Dados corrompidos era rotina.</p>
<p>Precisávamos fazer engenharia reversa desse arquivo pra dar carga no SQL Server. Só que à medida que abríamos esse arquivo dava pra ver que tinha registros corrompidos no meio do caminho. Povo de data science sabe o que significa &ldquo;limpar dados&rdquo;. Essa foi minha primeira experiência usando SQL como se não houvesse amanhã pra limpar e várias queries pra garantir que tava tudo certo. Diferente de data science, que se tiver algum errinho não costuma ser um grande problema porque dá pra lidar com margens de erro, quando falamos de contabilidade, não pode ter uma vírgula fora do lugar. Ninguém pode receber cobranças erradas. Qualquer pequeno volume de erros significava congestionamento no atendimento pra resolver.</p>
<p>Daí finalmente fizemos uma primeira versão que ia imprimir os novos boletos pela primeira vez. Não lembro, talvez agosto ou setembro de 1999. Fiz um monte de queries, ainda tinha uns bugs que eu não tava satisfeito, mas chegamos no momento de &ldquo;acho que tá bom o suficiente&rdquo;. Mandamos rodar e as impressoras no andar de baixo começaram a imprimir. Pensa que ia levar a noite inteira imprimindo pro serviço de entrega ir buscar as caixas na manhã seguinte.</p>
<p>O escritório do STI ficava numa cidade chamada Santana de Parnaíba, depois de Alphaville aqui de São Paulo, quase 30 quilômetros de distância da casa dos meus pais onde eu morava. 45 minutos de carro sem trânsito ou 2 horas, que era o que eu levava todo dia pra voltar. Eu passei o trânsito todo da volta com pulga atrás da orelha. Cheguei em casa, jantei e tal, deitei pra dormir e foi aí que me ocorreu: tinha um bug que ia foder todos os boletos!</p>
<p>Lembro que levantei no susto, peguei o carro sei lá, meia noite e fui correndo pro escritório. Eu tive meu momento de &ldquo;parem as máquinas!!!&rdquo; E passei a madrugada tentando corrigir o tal bug. Queria lembrar o que era exatamente, mas era um bug lazarento. Eu acho que consegui resolver e finalmente deu pra continuar imprimindo a tempo de acabar pra entrega de manhã. Eu tenho poucas memórias daquela época. Acho que deu certo porque não lembro de repercussões tão negativas. A gente sabia que podia ter um certo grau de erro, mas era importante não ser demais pra não congestionar o suporte.</p>
<p>Meus primeiros 10 anos seriam uma sucessão de eventos desse tipo: pegar desafios que eu não tinha idéia de como fazer, varar noite tentando resolver, causar muitos bugs que não sabia como corrigir, e arrancar os cabelos tentando descobrir. Muito me admira que eu ainda tenho cabelo. Enfim, acho que chegamos muito perto de conseguir substituir o sistema antigo pelo novo, mas aí aconteceu um evento inesperado: a STI foi adquirida por uma outra empresa maior, a PSINet.</p>
<p>Por causa desse merge mudou muita coisa, não sei se nosso sistema foi usado ou não ou se iam adotar outro, mas foi onde vi meu primeiro layoff, demissão em massa. Num merge de várias empresas, acaba que muitas posições ficam redundantes, daí demitiram muita gente de uma só vez. Ouvi falar que muitos ficaram descontentes, saqueram o escritório, levaram computador, monitor e coisas assim.</p>
<p>No meu caso foi diferente, porque minha equipe, que era próxima dos co-fundadores, foi levada pra montar outra empresa. Eu não tinha outra coisa pra fazer, fui junto. Montaram outro escritório e me pediram pra pegar um produtinho que eles compraram pra envio de email e falaram pra eu fazer uma interface gráfica pra ela. O problema que tinha que ser em PHP. Eu passei meses mexendo em ASP e tava super fluente, mas beleza, lá vou eu aprender outra coisa. Ainda bem que eu tinha aprendido um pouco de UNIX e Linux na faculdade porque agora ia precisar mexer com Linux e Apache.</p>
<p>Esse produtinho foi batizado de Internet Email Grátis ou ieG, um dos primeiros produtos independentes, que não era atrelado a um provedor, pra dar email grátis pra qualquer um. Pense um Gmail rudimentar, 6 anos antes do Gmail. Eu tava explorando a idéia de uma interface gráfica Web que seria parecido com um aplicativo nativo de Windows de verdade, daí fiz uma versão Web do Outlook Express, antes do Outlook pra Web sair de verdade. Ponto de comparação naquela época acho que já tinha Hotmail.</p>
<p>Esse produto foi fácil, daí formou uma nova equipe e nos pediram pra fazer outro produto: um Geocities brasileiro. Pra quem não sabe o que é Geocities, era um produto web pra qualquer um conseguir ter sua própria homepage pessoal gratuita. A contrapartida era deixar colocar um banner no topo pra monetizar. A nossa ia se chamar HomePage Grátis ou HPG. Quem começou a mexer com internet no começo dos anos 2000 vai se lembrar de HPG. Esse produto explodiu, todo mundo tinha um site hpg.com.br/alguma coisa. Pensa, era uma época que ainda não era difícil conseguir domínios de 3 letras.</p>
<p>Você podia andar num shopping center e ver a URL hpg da loja na vitrine, ou em outdoor, ou pintado nos ônibus. Esse produto explodiu. Eu participei da primeira versão, feita com PHP e Perl. E foi assim que aprendi Regular Expressions, porque todo mundo ficava tentando achar jeitos de burlar os banners que a gente colocava e eu passava o dia fazendo Regex pra tirar os truques. Povo posta no fórum do UOL e eu ficava vendo qual era o novo truque e fazia uma nova regex pra filtrar o HTML e tirar, por exemplo, o javascript que eles colocavam. Nem precisava tirar tudo, bastava fazer o javascript do cara quebrar pra ele tirar.</p>
<p>Eu saí quando começaram a fazer a funcionalidade de templates de sites, pra facilitar quem não sabia escrever HTML do zero. Eu tinha 23 anos e na minha cabeça esse produto não fazia sentido. Mesmo com banners era muito caro manter o site de todo mundo de graça. Infraestrutura era muito mais cara naquela época do que agora. Mas eu também tava sendo muito cético muito cedo. Eu fiz tudo isso em menos de 6 meses. Mas um conhecido da época daquela primeira agência que fechou foi trabalhar pra uma agência grande de verdade, a F/Nazca, e me convidou pra ir com ele. Dado que eu tava cético com o modelo de negócios, tinha visto os layoffs da PSINet antes, resolvi pular fora do barco.</p>
<p>Seis meses depois o HPG foi vendido pro iG, o que elevou o iG acho que do sexto lugar no ranking pra segundo lugar só atrás do UOL na época. Sabem o que eu falei nos videos sobre startups de engordar o porco? Pra mim isso era uma novidade naquela época, foi a primeira vez que vi o modelo de negócios de gastar muito sem dar lucro, criar hiper relevância, pra ser adquirido caro por alguém maior. Hoje em dia isso é o arroz com feijão das startups. Mas mesmo assim, isso dá certo pra menos de 1% de todo mundo que tenta. Mas os co-fundadores eram todos pessoas bem conectadas no mercado por conta da experiência da STI e do merge com a PSINet. Não eram amadores. Mas eu não conhecia eles pessoalmente, então não tinha noção disso.</p>
<p>Mas tudo bem, eu não tinha stock options nem nada, não ia ganhar nada com a venda de qualquer jeito. Eu era só um programador e acho que tomei uma boa decisão, especialmente porque nesse próximo lugar também ia poder fazer coisas diferentes. A F/Nazca era uma das maiores agências do Brasil na época onde as grandes eram nomes como W/Brazil ou Almap. Hoje são WMcCann, Publicis, Africa, DPZ&amp;T. A Publicis acho que comprou a F/Nazca anos atrás, mas em 2019 o fundador Fabio Fernandez, o &ldquo;F&rdquo; em F/Nazca, saiu e acho que iam fechar. O fim de uma era.</p>
<p>E o Fabio Fernandez era publicitário extravagante clichê da época. De novo, era ano 2000, estávamos caminhando rapidamente pro pico da Bolha da Internet. Ninguém sabia que era uma bolha ainda, especialmente porque ninguém sabia direito o que fazer na Web, muito menos agência publicitária. E pra ser o diferentão o Fernandes quis montar uma equipe tipo uma Tropa de Elite da Web, fora dos escritórios da agência. Era uma equipe pequena, nem meia dúzia de pessoas, focadas só em Web, e a gente ia ficar num escritório virtual no World Trade Center de São Paulo.</p>
<p>Pra ter uma noção do que eu quero dizer com extravagante, ele fez um anúncio pra empresa inteira na forma de cartas deixadas na mesa de todo mundo, dizendo que estavam demitidos. Aí acho que ia ter um evento no mesmo dia pra anunciar que na verdade tava todo mundo demitido da &ldquo;antiga&rdquo; agência publicitária tradicional mas estavam todos recontratados na &ldquo;nova&rdquo; agência Web. Entenderam? Puta piada de mau gosto que acho que rolou vários processos trabalhistas depois. Mas imaginem eu, era meu primeiro dia, e a primeira coisa que ouvi foi que tava demitido e recontratado. Eu achei engraçado na época, mas eu era só um moleque.</p>
<p>Enfim, nessa equipe eu trabalhei em algumas peças em Flash primeiro. Tive que desenferrujar meu Flash e fazer duas peças de campanha que eles iam mostrar em Cannes. Eram umas coisas bem idiotas. Só de exemplo, uma das peças era uma campanha das tintas Suvinil falando sobre suas novas máquinas de misturar tintas pra você ter a cor que quiser.</p>
<p>A idéia era o banner ficar fixo na tela e na verdade o navegador inteiro se mexer pra todos os lados simulando a máquina misturando a tinta. Isso era uma época que Javascript dava controle de tudo do navegador, incluindo a posição no desktop da pessoa. Imagina abrir um site e as janelas ficarem se mexendo do nada. Ainda bem que não pode mais fazer isso.</p>
<p>Enfim, meu chefe nessa época era um gêniozinho um ano mais novo que eu e que manjava muito de tecnologia, o Bruno Mosconi. Foi com ele que aprendi o conceito de que livro não é despesa, é investimento. Ele deixava a gente comprar os livros que precisasse. É uma prática que até hoje eu tenho na minha empresa por causa disso. Mas hoje em dia o acesso a informação desse tipo é muito mais fácil, tem de graça. Na época não tinha. Era ano 2000, Google mal tinha 2 anos ainda.</p>
<p>Lembro que foi a primeira vez que mexi com Python também por causa de um site de astrologia de um cliente, que ele queria implementar num gerenciador de conteúdo chamado Zope. Pra quem é de Python, o Zope depois se tornaria Plone. E foi com ele também que ouvi falar de Ruby pela primeira vez, mas não usei naquele ano ainda. F/Nazca foi o lugar onde trabalhei por menos tempo, só uns 3 ou 4 meses. No HPG também foi pouco, menos de 6 meses. De novo, minha memória não tá muito boa mas acho que essa operação Web não tava dando certo aí eu tive que procurar outro lugar.</p>
<p>Essa foi a primeira vez que realmente tive que procurar emprego. Postei meu currículo na minha homepage do HPG, até tive contatos por lá. Recrutador vasculhando qualquer site existe desde aquela época. Mas de novo, foi por causa dos contatos que fui fazendo ao longo desses primeiros anos que me levaram a algumas boas entrevistas e acabei entrando numa pontocom, que é como chamávamos as startups da época, que foi a PSN, ou Panamerican Sports Network.</p>
<p>Eu contei sobre eles nos episódios de Sua Linguagem Não é Especial, mas recapitulando, a empresa maior era a Hicks, Muse, Tate and Fürst. Pra quem é de futebol, vai lembrar que era a dona do Corinthians e do Cruzeiro no ano 2000. Embaixo tinha sites como a PSN.com junto com o canal de TV a cabo PSN, patrocinava o Alan Prost na Fórmula 1. Pois é, eu fui webmaster do Corinthians. Depois assistam esses outros episódios.</p>
<p>Lembro que cometi minha primeira grande gafe que todo iniciante arrogante faz. Logo na primeira semana, fui olhar o código deles e era um go-horse atrás de outro go-horse. O código foi feito por uma terceirizada americana e foi a primeira vez que entendi que só porque é gringo não quer dizer que é melhor. Puta código zoado da porra. Pensa um ASP macarrônico com HTML misturado com Basic tudo em arquivões de milhares de linhas. Escrevi um e-mail super textão com page downs e page downs de críticas e mandei pra todo mundo sem pensar. Descobri só quando tava pra sair que por causa daquele e-mail todo mundo me odiava no começo. Eu só repeti o que todo mundo já sabia. Uma nova equipe já tinha assumido a zona e já tava consertando, eu que peguei o bonde andando. Fica a dica: Não seja essa pessoa.</p>
<p>Na PSN eu fiquei pouco tempo também, até dezembro de 2001, cerca de 1 ano e meio. E foi lá que tive a oportunidade de ir pros Estados Unidos pela primeira vez, pra conhecer a equipe que trabalhava de Miami. Pra ter uma noção de como faz tempo, não existia celular com internet, pra me localizar eu tinha que imprimir mapas do site da prefeitura da cidade. E pra tirar foto eu tive que comprar uma câmera descartável numa farmácia e mandar revelar. Acabei perdendo essas fotos. Eu só voltaria a viajar pros Estados Unidos quando entrei na Locaweb em 2008, 7 anos depois. Mas em 2001 aconteceram outros dois episódios que me marcaram.</p>
<p>O primeiro foi em setembro de 2001 quando cheguei de manhã cedo no escritório e tava todo mundo na frente das TVs e eu fiquei confuso &ldquo;o que aconteceu?&rdquo; Aí vieram me contar &ldquo;você não viu?? jogaram um avião no World Trade Center&rdquo;. E eu paralizei e falei &ldquo;aqui do lado??&rdquo; Lembrando que na F/Nazca trabalhei de um escritório no World Trade Center, de São Paulo, que fica no bairro do Brooklin Novo, na famosa avenida Berrini que muitos já consideraram o Vale do Silício do Brasil. A PSN ficava num prédio a alguns quarteirões de distância. Pensa meu alívio quando falaram &ldquo;não, o de Nova Iorque!&rdquo; ahhhhh então tá, cadê meu café?</p>
<p>E o segundo episódio foi o famoso Crash da Bolha da Internet no mês seguinte, em outubro. Na verdade o pico da NASDAQ foi em março de 2001, onde tinha subido absurdos 400% desde 1995 e em 2001 caiu quase 80% de uma só vez. Ela ainda tentou subir de novo no meio do ano, mas aí veio a segunda perna do crash e a partir daí não parou mais de cair até quase o fim de 2002. Eu não tinha idéia do que isso significava. Na PSN nos disseram que descobriram que alguns executivos tavam roubando, negócios ruins, e o fechamento do canal de TV, das operações na Web. Eu quase apaguei a luz do escritório por último.</p>
<p>Eu não era muito informado com as notícias. Não acompanhava nada de mercado financeiro. Pra mim, essa bolha que começou em 1995, quando eu entrei na faculdade, parecia tudo normal. Só depois do fechamento abrupto da PSN que me toquei que na verdade eu tava no meio de uma bolha e nem tinha me dado conta. Notaram que desde 1996 eu fui indo de trabalho em trabalho sem nenhuma real dificuldade? Eu ia conhecendo pessoas, elas gostavam do meu trabalho, me indicavam ou me chamavam pro próximo negócio e eu só ia seguindo a vida assim.</p>
<p>E essa seria a segunda vez que eu ia ter que procurar emprego. Fiquei só uma semana desempregado. Mesmo com o crash da bolha, não significa que acabou todos os empregos. É isso que eu quis dizer no video do Crash da Bolha de agora também. Toda empresa, seja ela de tecnologia ou não, a partir de agora ia precisar manter essa tecnologia e evoluir, mesmo que fosse mais devagar.</p>
<p>De novo, mandei alguns emails e quem respondeu foi o Antelio, que eu falo no episódio de SAP, engenheiro que conheci naquele projetinho da agência na Camargo Corrêa. Ele saiu, virou consultor SAP e me chamou pra trabalhar. Ele ia me ensinar SAP e eu ia ensinar Web pra eles. Convenientemente, o escritório da consultoria era no mesmo prédio onde ficava a PSN que tinha acabado de fechar, só mudei de andar. Eu passei mais de meia década nessa região da Berrini.</p>
<p>Lembram do projetinho da STI pra resolver o problema de ano 2000? Obviamente ela não foi a única empresa com esse problema. Literalmente toda empresa que adotou tecnologia dos anos 80 e 90 tiveram que ajustar seus sistemas, ou investir em modernizar e substituir por coisas novas com eu fiz. Mas em empresas gigantes, não dava pra refazer tudo do zero. E foi aí que empresas como SAP ou Oracle ganharam terreno no Brasil. Foi a era de ouro das consultorias pra implementar novos ERPs não só pra resolver o problema no bug do milênio como implementar novos processos e tecnologias. Eu conto um pouco disso no episódio de SAP, depois dêem uma olhada.</p>
<p>E lá ia eu aprender mais uma plataforma que nunca tinha imaginado que ia ter que aprender: SAP. Mas tudo bem, de 1996 até  fim de 2001 eu tive que aprender Delphi, HTML, Javascript de primeira geração, Flash, ASP, PHP, Perl, SQL Server, MySQL, Apache, um pouco de Python. O que é um pouco de SAP? E nesse período entre 2002 a 2006 eu ia voltar a usar tecnologias Microsoft como ASP e finalmente usar Java comercialmente.</p>
<p>Durante a depressão na área de novas tecnologias que ia durar de 2002 a 2008 eu fiquei focado em mercado corporativo com integração de SAP com Web, depois migrei pra tecnologias open source com Ruby on Rails em 2006 e me preparando pra pegar a nova onda que eu nem sabia que ia recomeçar com mais força por volta de 2008. Foi quando começou a aparecer Twitter, Uber, Airbnb, Pinterest, Foursquare, Spotify, Netflix e todos os nomes que vocês conhecem hoje.</p>
<p>De novo, não é porque teve crash da bolha que a tecnologia parou. Foi entre 2002 a 2008 que as sobreviventes se consolidaram. Google e Amazon viraram gigantes nesse período de seca. Foi nesse período que blogs e redes sociais nasceram. Muitos tentaram e morreram, Orkut, MySpace, e aí apareceu o Facebook, depois o Instagram. Mas o ecossistema maior mesmo só apareceria depois do Crash da Bolha Imobiliária de 2008. A correção desse crash criaria o ambiente propício de juros baixos e dinheiro fácil pra investimento que seria o combustível pra bolha que duraria até agora. De novo, assista minha playlist &ldquo;Eu Avisei&rdquo; onde explico detalhes de porque.</p>
<p>Eu não sou um guru, não prevejo o futuro, nem nada disso, eu sou um animal muito simples: eu gosto de tecnologia, alguém quer me pagar pra mexer com tecnologia, eu vou, só isso. Foi só isso durante toda minha carreira. Veja: minha carreira nasceu numa época que precede o conceito de influencers, de cursos online, de redes sociais. O primeiro evento de programação que eu participei foi a RailsConf nos Estados Unidos em 2008. Eu já tinha 31 anos e fazia 13 anos que entrei na faculdade. Só aí passei a interagir mais com as comunidades de desenvolvimento de software. É o que eu conto no video da História do Ruby on Rails.</p>
<p>Eu entendo o problema de todos vocês hoje: sofrem de information overload. Tem informação demais, pior, muita informação conflitante e ruim mesmo. Um influencer diz pra seguir caminho X, outro influencer diz pra seguir caminho Y, o grupo do Discord fala que caminho X é uma bosta, pessoal do Telegram fala que caminho Y é uma bosta, na sua timeline do Twitter tem caminhos de A a Z e nenhum consenso. Eu consigo imaginar o turbilhão de incertezas infladas que tem na sua cabeça nesse momento.</p>
<p>Pare um segundo e pense: eu, Akita, estou perto de aposentar uma  carreira que começou em 1990. Informação naquela época era muito cara, de difícil acesso. Porra, se eu não tivesse me dedicado sério a aprender inglês, como conto no video sobre inglês, não teria conseguido fazer nada do que resumi nesse video. Tudo que eu aprendia só tinha disponível em inglês, não tinha a opção de português. Conseguem imaginar isso?</p>
<p>Mais do que isso: só conhecia outros programadores que trabalhei junto nessas empresas e projetos. Não conhecia outros programadores de fora, então não tinha nenhum modelo, ninguém pra me influenciar e, felizmente, ninguém que eu achava que precisava seguir. Eu simplesmente fazia as coisas por pura intuição.</p>
<p>Mais felizmente ainda: não existiam coaches falando bullshitagem sobre coisas como networking. Eu aprendi a fazer networking de verdade. Por exemplo, na época da STI e HPG tenho dois exemplos: tinha dois programadores júniors como eu na equipe. Um se chamava Fernando Nemec. Depois do HPG ele foi trabalhar como programador na recém criada parte web do jornal Folha de São Paulo. Hoje ele é o diretor de tecnologia da Folha Online.</p>
<p>O outro se chamava Adriano Caetano. Depois do HPG ele abriu seu próprio ecommerce, um dos primeiros do Brasil, chamado LinuxMall. Eu comprei muitos CDs de Linux com ele. Hoje ele é Sócio Diretor da VTEX, uma das primeiras plataformas de ecommerce e uma das maiores do Brasil. Coincidentemente saiu um video dele contando essa história no YouTube, vou deixar o link na descrição.</p>
<p>Vocês ficam olhando pra caras como eu e esquecem que o júnior da sua própria equipe, colega da sua faculdade, vai ser o próximo founder, CTO, CEO da startup inovadora de amanhã. Ele confia no resultado do seu trabalho hoje o suficiente pra te chamar pra trabalhar com ele? Se você não consegue impressionar nem o júnior que trabalha do seu lado, porque você acha que eu me interessaria? Só porque você fez mandou um email engraçadinho no LinkedIn? Ou porque ficam só no blá-blá falando que é esforçado? Sério?</p>
<p>É difícil resumir mais de 5 anos em um video curto como esse, mas meu ponto era mostrar o seguinte: principalmente no começo de carreira, eu nunca me defini como &ldquo;programador ASP&rdquo; ou &ldquo;programador Java&rdquo; ou &ldquo;administrador de SQL Server&rdquo;. Minha especialidade não era nenhuma dessas ferramentas. Sem planejar muito, eu só me especializei em resolver problemas de tecnologia. Minha arma secreta foi Aprender a Aprender. Eu simplesmente gosto de tecnologia e de resolver problemas com tecnologia.</p>
<p>Nunca parei pra pensar: &ldquo;será que esse tal de ASP vai ter carreira pros próximos 10 anos?&rdquo; ou pior &ldquo;será que esse tal de Director e CD-ROM é uma boa carreira pros próximos 10 anos?&rdquo;. Eu literalmente aprendia e produzia numa plataforma nova todo ano durante esse período. Se em 1996 eu tivesse pensado &ldquo;puts, esse tal de ASP é muito novo, não dá pra ter uma carreira nisso, vou ficar no Clipper, vou ficar no Director&rdquo;, e daí se toda vez eu esperasse 2 ou 3 anos &ldquo;pra ver se ia vingar&rdquo;, teria perdido a primeira Bolha da Internet, pior, teria sido mais uma vítima do crash.</p>
<p>Eu passei totalmente ileso. Quando migrei de pontocom com ASP pra mundo corporativo com Java e SAP, na minha cabeça isso não era um passo pra trás ou um downgrade. Era só mais uma página. Porque eu não era definido pela linguagem, ou modelo de negócio. Vejam como o Java que fui aprendendo aos poucos desde 1996 viria a se tornar útil em 2002 quando entrei em consultoria. Vejam como o ASP que aprendi de sopetão em 1999, deixei de lado pra aprender PHP em 2000, voltaria em 2001 na PSN. Não existe uma receita. E não existe falta de oportunidade, existe falta de vontade de pegar pra resolver. Ah, vou fazer um curso, quando eu aprender daí vou procurar. Aí já é tarde demais, o bonde já andou e você ficou no fim da fila.</p>
<p>No episódio da Sua Linguagem Não é Especial, eu mostrei como não ficava também só fazendo o que o trabalho exigia. Em 2000 eu tava um pouco empolgado com .NET que tava pra sair, acho que tava em Beta nessa época. No trabalho ninguém ia usar .NET, mas fiz minha própria versão de um mini framework ASP.NET só que em ASP clássico, só pra ver se dava. Que não foi diferente de quando tentei aplicar orientação a objetos no Lingo de Director alguns anos antes. Eu não fazia o que o livro dizia, só queria ver o que dava pra fazer. Na minha cabeça nunca teve o conceito de &ldquo;não é assim que todo mundo faz&rdquo;. Eu sempre tô cagando e andando pro que &ldquo;todo mundo&rdquo; faz.</p>
<p>Notem outra coisa: em nenhum momento eu cito cursos. Porque eu nunca fiz nenhum. Primeiro que não existia Udemy nos anos 90. Segundo porque intuitivamente entendia: porra, Java saiu ano passado, obviamente não tem curso pra isso. Flash acabou de sair enquanto eu tô aqui fazendo Director, óbvio que não tem curso pra isso. Eu nunca esperava que fosse ter um curso. Eu via que tinha um livro, em inglês, ia na Livraria Cultura no Conjunto Nacional da Avenida Paulista, a única que eu sabia que dava pra encomendar livro importado, esperava um mês pra chegar e era isso. Amazon foi só a partir do ano 2000.</p>
<p>Outra coisa: quando falei que tava aprendendo sobre orientação a objetos. O livro de Design Patterns do Gang of Four tinha acabado de ser lançado em 1994. UML também tinha acabado de ser inventado. Essas coisas ainda não eram consenso. Existiam várias metodologias antes da Rational. Eu, por exemplo, tinha um livro de uma metodologia chamada Fusion. Mas pelo jeito não fez sucesso porque como isso precede o Google, nem tem registro se pesquisar a respeito.</p>
<p>O Manifesto Ágil seria assinado em 2001 que é quando eu termino minha história nesse video. Tudo sobre o assunto só seria publicado depois. O ponto é: eu, aluno de faculdade em 1995, não tinha como saber que essas coisas se tornariam relevantes anos depois. O você de hoje tentando escolher a ferramenta &ldquo;ideal&rdquo; não tem idéia do que realmente vai ser importante daqui 5 anos. Não tente adivinhar agora, é perda de tempo. E não, nenhum guru, influencer, coach, nem mesmo eu, tenho como dizer o que vai ser relevante daqui 5 anos especificamente pra sua carreira, porque a carreira de cada um vai ser diferente.</p>
<p>E antes que alguém vá encher o saco na bolhadev, quando eu falo que varava noite na época, não foi pra vangloriar que trabalhar a noite é admirável. Pelo contrário, eu varava noite porque eu não era bom e prometia mais do que conseguia entregar. Eu só era sem noção mesmo. Isso é pessoal, não recomendo ninguém copiar. Vai de cada pessoa. Com cabeça de hoje, talvez tivesse dito mais &ldquo;não&rdquo;. Mas por outro lado talvez não tivesse aprendido tanta coisa tão rápido.</p>
<p>Com 20 anos, meu objetivo não era ter qualidade de vida, era aprender tudo que pudesse aprender porque minha única motivação era resolver problemas novos. Cada pessoa tem motivações diferentes, nunca use minha história como parâmetro pra sua. As épocas são completamente diferentes. Vocês tem mais opções hoje do que eu tinha nos anos 90.</p>
<p>Falando do povo comentar, não sei se notaram, mas nenhuma das empresas que eu trabalhei nesse período existem mais. Eu literalmente saía pra outro trabalho porque onde eu tava ia fechar. Uma vez só eu saí por conta própria, mas de 1996 até 2002 esse foi o padrão. Foi bizarro, já tava achando que eu tinha pé frio. Magina, mandar meu currículo e o recrutador ver que nenhum lugar existe mais. Mas não foram só as empresas que eu passei, no Crash, várias outras foram fechando ao longo do tempo. Muitas das empresas que a gente conhecia naquela época não existem mais. É a vida.</p>
<p>De qualquer forma, se pudesse mudar alguma coisa que fiz desde os anos 90, não mudaria nada. São as experiências que passei, boas ou ruins, que formam o que sou hoje. Foi fazendo merda, aprendendo a consertar e persistindo, que me definiu. Agora cada um de vocês tem que achar o que define vocês, e não vai ser me copiando. Os anos 90, infelizmente, acabaram. Os anos 2000 acabaram. Os anos 2010 acabaram. Os anos 2020 acabaram de começar, é nesse contexto que você existe.</p>
<p>Finalmente, alguns poderiam ficar impressionados. Pode parecer que fiz muita coisa nesse meu começo de carreira. Mas nem tanto, num outro extremo, lembram que falei que 1997 foi quando Steve Jobs retornou pra Apple? Pois bem, nesse meio tempo, ele limpou a casa, tirou a Apple da beira da falência, modernizou a linha de hardware lançando os famosos iMacs coloridos, finalmente conseguiu matar o antigo MacOS 9 e lançar o MacOS X que é a base dos Macs até hoje, e no ano que a bolha estourou, lançou o iPod, que mudaria a indústria de música pra sempre. Tem um documentário chamado The iPod Revolution que conta exatamente esse trecho da história, depois assistam, é fascinante.</p>
<p>Muito ou pouco é relativo. Parem de ficar se comparando, que coisa chata. Façam sua própria carreira, do seu jeito, e evoluam cada dia pra não se arrepender do dia anterior, só isso. Eu não virei o Steve Jobs, e tá ótimo, o que eu consegui fazer por mim durante minha carreira me deixa satisfeito. Poderia ter feito muito mais. Sempre dá pra fazer mais. Mas Akita, o que você recomenda pra mim? É a pergunta que eu mais recebo, e repetindo pela enésima vez, veja meu video de Não Terceirize suas Decisões e a playlist de carreira.</p>
<p>Eu sei que mesmo dizendo isso vocês ainda vão continuar me perguntando o que devem fazer, ou falar que vão tentar me copiar, mesmo eu falando não. Mas vamos aos fatos: eu disse pra vocês fazerem imersão de inglês em casa, substituir tudo por inglês. Acabei de mostrar a diferença que fez no meu começo de carreira. Como anda o inglês de vocês?</p>
<p>Agora o mais óbvio, em vez de perguntar o que eu fazia, que tal perguntar o que eu não faria? Uma coisa que me lembrei escrevendo esse episódio é que a primeira balada que eu fui foi só por volta de 2002 ou 2003. Foi quando comecei a beber socialmente também. Lembrem, entrei na faculdade em 1995. Fui em uma festa na faculdade, uma única cervejada. Não participei de nenhum evento, festa ou balada de 1996 até 2002, que é o período que cubro neste video.</p>
<p>Por que? Primeiro porque não tinha dinheiro. Eu começar a sair coincide com minha transição de júnior pra pleno e ganhar salário de consultor corporativo. Faz muita diferença. Além disso tinha tanta coisa tão interessante acontecendo no trabalho toda hora, cada hora um bug mais cabeludo pra resolver, eu passava minhas noites dormindo no escritório. E não porque me mandavam, mas porque eu não queria ter que perder tempo indo pra casa e tendo que voltar depois. Meu PC do escritório na época era sempre melhor que meu PC de casa. E não tinha laptop acessível, então tinha que usar lá mesmo. Eu preferia ficar lá.</p>
<p>E mais, que séries eu maratonava na TV nos anos 90? Literalmente não consigo me lembrar de nenhuma. Depois que entrei na faculdade, quase não assistia TV. Recomecei a ver séries só lá por 2004 ou 2005 com Os Sopranos e 24. Lembram que falei que não tinha nenhum influencer que seguia? Vocês perdem horas e horas diariamente acompanhando gente desinteressante e irrelevante que não contribui em absolutamente nada na sua carreira. Eu não tive isso, portanto tive a vantagem de não perder meu tempo com gente inútil. Eu só gastava tempo comigo mesmo.</p>
<p>Teria sido melhor se essas coisas existissem naquela época? Google sim, sem sombra de dúvida, pra pesquisar as coisas mais rápido. Stackoveflow também. Todo o resto, principalmente a bolhadev? Absolutamente inútil. A opinião aleatória dos outros é inútil. Ninguém sou eu. Eu teria conquistado menos da metade do que fiz nesse período.</p>
<p>Podcasts de 3 horas, vai se fuder. Em 3 horas eu li a documentação inteira de uma nova linguagem. 3 horas todo dia? Porra. Quando você vai no banheiro? Ah, mas é quando eu tô no metrô voltando pra casa ou no trânsito. Ouça audiobooks de livros clássicos, de mais de 10 anos que ainda são relevantes hoje, não ebookzinho de influencer bosta que nem tem 10 anos de carreira direito. Já leu algum livro do Stroustrup, criador do C++? Ou do Dennis Ritchie, criador do C? Por que ainda não leu os originais em vez de ler as cópias baratas? Fica só falando de IA, IA, já leu alguma coisa do Marvin Minsky? Não né.</p>
<p>E esse foi o episódio do meu começo de carreira, que coincidiu com a ascenção e queda da primeira Bolha da Internet. E como podem ver, eu continuei crescendo sem parar. A próxima fase, pra quem quiser ver como foi, é o video de SAP, depois o video de Ruby on Rails. E por hoje é isso aí, se ficaram com dúvidas mandem nos comentários abaixo, quem quiser relembrar mais coisas dos anos 90 não deixem de mandar também, 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>carreira</category><category>pessoal</category><category>mercado-de-tecnologia</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] #136 - Python? Java? Rust? Qual a Diferença? | Discutindo Linguagens</title><link>https://akitaonrails.github.io/2023/01/18/akitando-136-python-java-rust-qual-a-diferenca-discutindo-linguagens/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/01/18/akitando-136-python-java-rust-qual-a-diferenca-discutindo-linguagens/</guid><pubDate>Wed, 18 Jan 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/MI9cdxETA4c"
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 vou falar sobre linguagens que você goste ou use, mas vou fazer isso do meu jeito: escovando bits e explicando como muita coisa funciona por baixo de Python, Javascript e outras linguagens que talvez você não sabia antes de concluir na segunda metade onde discuto onde cada linguagem pode ser melhor aproveitada e porque.&lt;/p&gt;
&lt;h2&gt;Capítulos&lt;span class="hx:absolute hx:-mt-20" id="capítulos"&gt;&lt;/span&gt;
&lt;a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;01:36 - Cap 1 - Perl e Regex | Anos 90&lt;/li&gt;
&lt;li&gt;06:18 - Cap 2 - Estilo C e ICU | strftime&lt;/li&gt;
&lt;li&gt;10:25 - Cap 3 - Tudo de Python é em C! | Linguagem &amp;ldquo;Grude&amp;rdquo;&lt;/li&gt;
&lt;li&gt;17:54 - Cap 4 - Tudo de Node.js é em C! | LibUV&lt;/li&gt;
&lt;li&gt;22:31 - Cap 5 - Compilado vs Interpretado | ABI de C&lt;/li&gt;
&lt;li&gt;28:21 - Cap 6 - Interoperabilidade: Marshalling/Unmarshalling | FFI&lt;/li&gt;
&lt;li&gt;36:21 - Cap 7 - Onde cada Linguagem é mais Forte? | Rust&lt;/li&gt;
&lt;li&gt;38:09 - Cap 8 - Sistemas Distribuídos | Apache Java&lt;/li&gt;
&lt;li&gt;42:41 - Cap 9 - Produtividade &amp;gt; Performance? | Apps Comerciais&lt;/li&gt;
&lt;li&gt;48:31 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python/cpython/search?p=2&amp;amp;q=ifdef&amp;#43;MS_WINDOWS"target="_blank" rel="noopener"&gt;https://github.com/python/cpython/search?p=2&amp;q=ifdef+MS_WINDOWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/cpython/blob/a87c46eab3c306b1c5b8a072b7b30ac2c50651c0/Modules/_ssl.c"target="_blank" rel="noopener"&gt;https://github.com/python/cpython/blob/a87c46eab3c306b1c5b8a072b7b30ac2c50651c0/Modules/_ssl.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pandas-dev/pandas/blob/main/requirements-dev.txt"target="_blank" rel="noopener"&gt;https://github.com/pandas-dev/pandas/blob/main/requirements-dev.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/numpy/numpy/wiki/Building-with-MSVC"target="_blank" rel="noopener"&gt;https://github.com/numpy/numpy/wiki/Building-with-MSVC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scipy/scipy"target="_blank" rel="noopener"&gt;https://github.com/scipy/scipy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jupyter/notebook"target="_blank" rel="noopener"&gt;https://github.com/jupyter/notebook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openjdk/jdk/search?l=c%2B%2B&amp;amp;p=99"target="_blank" rel="noopener"&gt;https://github.com/openjdk/jdk/search?l=c%2B%2B&amp;p=99&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libuv/libuv"target="_blank" rel="noopener"&gt;https://github.com/libuv/libuv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tensorflow/tensorflow"target="_blank" rel="noopener"&gt;https://github.com/tensorflow/tensorflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tensorflow/tfjs"target="_blank" rel="noopener"&gt;https://github.com/tensorflow/tfjs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pytorch/pytorch"target="_blank" rel="noopener"&gt;https://github.com/pytorch/pytorch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nodejs/node/search?q=libuv"target="_blank" rel="noopener"&gt;https://github.com/nodejs/node/search?q=libuv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/async-await-retry"target="_blank" rel="noopener"&gt;https://www.npmjs.com/package/async-await-retry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/11996988/how-good-is-oniguruma-compared-to-other-cross-platform-regexp-libraries"target="_blank" rel="noopener"&gt;https://stackoverflow.com/questions/11996988/how-good-is-oniguruma-compared-to-other-cross-platform-regexp-libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/jni"target="_blank" rel="noopener"&gt;https://www.baeldung.com/jni&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/blog/found-zookeeper-king-of-coordination"target="_blank" rel="noopener"&gt;https://www.elastic.co/blog/found-zookeeper-king-of-coordination&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/akitaonrails/ObjC_Rubyfication"target="_blank" rel="noopener"&gt;https://github.com/akitaonrails/ObjC_Rubyfication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zaiste.net/posts/shell-commands-rust/#"target="_blank" rel="noopener"&gt;https://zaiste.net/posts/shell-commands-rust/#&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/MI9cdxETA4c"
    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 vou falar sobre linguagens que você goste ou use, mas vou fazer isso do meu jeito: escovando bits e explicando como muita coisa funciona por baixo de Python, Javascript e outras linguagens que talvez você não sabia antes de concluir na segunda metade onde discuto onde cada linguagem pode ser melhor aproveitada e porque.</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:36 - Cap 1 - Perl e Regex | Anos 90</li>
<li>06:18 - Cap 2 - Estilo C e ICU | strftime</li>
<li>10:25 - Cap 3 - Tudo de Python é em C! | Linguagem &ldquo;Grude&rdquo;</li>
<li>17:54 - Cap 4 - Tudo de Node.js é em C! | LibUV</li>
<li>22:31 - Cap 5 - Compilado vs Interpretado | ABI de C</li>
<li>28:21 - Cap 6 - Interoperabilidade: Marshalling/Unmarshalling | FFI</li>
<li>36:21 - Cap 7 - Onde cada Linguagem é mais Forte? | Rust</li>
<li>38:09 - Cap 8 - Sistemas Distribuídos | Apache Java</li>
<li>42:41 - Cap 9 - Produtividade &gt; Performance? | Apps Comerciais</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><a href="https://github.com/python/cpython/search?p=2&amp;q=ifdef&#43;MS_WINDOWS"target="_blank" rel="noopener">https://github.com/python/cpython/search?p=2&q=ifdef+MS_WINDOWS</a></li>
<li><a href="https://github.com/python/cpython/blob/a87c46eab3c306b1c5b8a072b7b30ac2c50651c0/Modules/_ssl.c"target="_blank" rel="noopener">https://github.com/python/cpython/blob/a87c46eab3c306b1c5b8a072b7b30ac2c50651c0/Modules/_ssl.c</a></li>
<li><a href="https://github.com/pandas-dev/pandas/blob/main/requirements-dev.txt"target="_blank" rel="noopener">https://github.com/pandas-dev/pandas/blob/main/requirements-dev.txt</a></li>
<li><a href="https://github.com/numpy/numpy/wiki/Building-with-MSVC"target="_blank" rel="noopener">https://github.com/numpy/numpy/wiki/Building-with-MSVC</a></li>
<li><a href="https://github.com/scipy/scipy"target="_blank" rel="noopener">https://github.com/scipy/scipy</a></li>
<li><a href="https://github.com/jupyter/notebook"target="_blank" rel="noopener">https://github.com/jupyter/notebook</a></li>
<li><a href="https://github.com/openjdk/jdk/search?l=c%2B%2B&amp;p=99"target="_blank" rel="noopener">https://github.com/openjdk/jdk/search?l=c%2B%2B&p=99</a></li>
<li><a href="https://github.com/libuv/libuv"target="_blank" rel="noopener">https://github.com/libuv/libuv</a></li>
<li><a href="https://github.com/tensorflow/tensorflow"target="_blank" rel="noopener">https://github.com/tensorflow/tensorflow</a></li>
<li><a href="https://github.com/tensorflow/tfjs"target="_blank" rel="noopener">https://github.com/tensorflow/tfjs</a></li>
<li><a href="https://github.com/pytorch/pytorch"target="_blank" rel="noopener">https://github.com/pytorch/pytorch</a></li>
<li><a href="https://github.com/nodejs/node/search?q=libuv"target="_blank" rel="noopener">https://github.com/nodejs/node/search?q=libuv</a></li>
<li><a href="https://www.npmjs.com/package/async-await-retry"target="_blank" rel="noopener">https://www.npmjs.com/package/async-await-retry</a></li>
<li><a href="https://stackoverflow.com/questions/11996988/how-good-is-oniguruma-compared-to-other-cross-platform-regexp-libraries"target="_blank" rel="noopener">https://stackoverflow.com/questions/11996988/how-good-is-oniguruma-compared-to-other-cross-platform-regexp-libraries</a></li>
<li><a href="https://www.baeldung.com/jni"target="_blank" rel="noopener">https://www.baeldung.com/jni</a></li>
<li><a href="https://www.elastic.co/blog/found-zookeeper-king-of-coordination"target="_blank" rel="noopener">https://www.elastic.co/blog/found-zookeeper-king-of-coordination</a></li>
<li><a href="https://github.com/akitaonrails/ObjC_Rubyfication"target="_blank" rel="noopener">https://github.com/akitaonrails/ObjC_Rubyfication</a></li>
<li><a href="https://zaiste.net/posts/shell-commands-rust/#"target="_blank" rel="noopener">https://zaiste.net/posts/shell-commands-rust/#</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 é dia de fazer um episódio que vinha postergando faz anos: falar sobre as principais linguagens de programação em uso no momento. Quem acompanha o canal sabe que evito ao máximo mostrar código dessa ou daquela linguagem, de vez em quando mostro uns trechos de javascript porque sei que a maioria sabe um pouco pelo menos, e em muitos casos uso pseudo-código que não existe quando o foco é o conceito.</p>
<p>Eu tenho algumas linguagens favoritas, obviamente Ruby, um pouco de Elixir, Crystal, todas linguagens meio parecidas em sintaxe, embora  diferentes por baixo, e mesmo assim  acho que nunca mostrei código delas em nenhum video. Mas hoje quero compartilhar algumas curiosidades técnicas e opiniões pessoais que muitos assistindo provavelmente nunca pararam pra pensar. A intenção não é fazer prós e contras, falar bem ou mau de qualquer linguagem. A idéia é fazer você enxergar sua linguagem favorita com outros olhos.</p>
<p>(&hellip;)</p>
<p>Já vou avisar: meu objetivo não é dizer que linguagem X é melhor que linguagem Y nem nada disso. Então os macacos de auditório de torcida organizada podem se retirar. Programação não é pra isso e código não liga pra sua &ldquo;lealdade&rdquo; a essa ou aquela tecnologia. Na verdade tecnologia e inovação recompensam a deslealdade. Quem conhece mais tecnologias diferentes sempre vai ser melhor e na verdade esse video é pra mostrar isso. Iniciantes vão ter seus níveis de ansiedade indo pras alturas porque vou mostrar um monte de coisas que nunca viram. Não se aflijam, anotem os termos que não conhecem, estudem, depois voltem pra assistir de novo.</p>
<p>A primeira coisa que quero levantar é o seguinte: Python. Nos últimos anos aumentou rápido de popularidade, principalmente por causa de projetos como Jupyter Notebooks, Pandas, Tensorflow, PyTorch e outros projetos relacionados a data science e machine learning. Se você se interessa por essas duas áreas, acho que é seguro dizer que maioria dos cursos e artigos a respeito vai usar uma ferramenta feita em Python. Por isso parece que Python é a linguagem que se deve aprender, certo?</p>
<p>Não está errado e não me entenda mau: apesar de não ser minha linguagem favorita, não tenho nenhum problema com Python. O que quero demonstrar hoje é mais dar uma perspectiva pé no chão. Primeiro de tudo, na minha opinião, Python estava no lugar certo na hora certa. Nos anos 90 a linguagem de script mais popular no nascente mundo de Linux foi Perl.</p>
<p>Acho que muitos iniciantes aqui nunca ouviram falar, mas muitos scripts de configuração e administração de sistemas Linux eram escritos em Perl. E quando a web começou a surgir, as primeiras aplicações interativas web, o que conhecemos hoje como programação back-end, era ou em C ou em Perl, porque essa linguagem era mais flexível pra lidar com strings, com um HTML, por causa da sua principal funcionalidade: Regular Expressions ou Regex.</p>
<p>Mais especificamente PCRE ou Perl Compatible Regular Expressions. Quem programa em Objective-C ou Swift pra iOS e Mac e já tentou usar regex, deve ter notado como é um pouco diferente do regex compatível com Perl. Isso porque a Apple, a Microsoft usam regex com sintaxe baseado em ICU que é International Components for Unicode. Eles são parecidos mas tem pequenas diferenças que podem confundir quem não sabe que existem dois padrões diferentes de regex.</p>
<p>Enfim, por causa de regular expressions, Perl sempre foi muito usado pra processar textos, logs, até coisas como sequências de DNA em pesquisa de genoma e coisas assim, já que envolve muito pattern matching de sequências. Porém, na virada do século, o pessoal de Perl quis dar passos maiores que as pernas. Eu lembro que queriam desenvolver um novo Perl, o Perl 6, como uma virtual machine que suportasse múltiplas sintaxes de linguagem por cima, o projeto Parrot.</p>
<p>Esse ciclo de desenvolvimento levou anos e nunca chegou no fim. O Perl 5, que foi a última grande versão de Perl ficou estagnado e sem novidades por muito tempo. Enquanto isso outras linguagens evoluíram mais rápido e de forma mais pragmática, e uma delas foi o Python. Não sei se foi a RedHat quem adotou primeiro, mas o fato é que com a incerteza no futuro do Perl, acredito que muitos começaram a migrar pra Python.</p>
<p>Python também tinha uma sintaxe e ergonomia muito mais modernas. Perl não era originalmente orientado a objetos, embora tivesse como gambiarrar classes com <code>bless</code>. Era uma linguagem dinâmica de tipagem fraca, então bugs por causa de coerção de valores e tipos errados não era incomum. Mas Perl influenciou todas as linguagens de script que vieram depois, incluindo Python, Ruby e o mais óbvio, PHP. De onde vocês acham que variáveis com cifrão vieram? De Perl.</p>
<p>O principal legado de Perl, na minha opinião, foi popularizar a viabilidade de linguagens dinâmicas pra aplicações web e popularizar o uso de regular expressions pra pattern matching de textos. Várias linguagens até hoje usam a sintaxe específica de Perl, como em Ruby, Node.js, acho que Go também porque usa a biblioteca &ldquo;re2&rdquo; que é compatível com PCRE.</p>
<p>Na realidade, nem todas são 100% compatível com Perl. Por exemplo, em Python, se usar a biblioteca &ldquo;re&rdquo;, o método <code>match</code> tem comportamento como se toda regex começasse com crase, que em Perl significa dizer pra começar o matching do começo do string. Ele também não tem o operador global &ldquo;/g&rdquo;, pra isso se usa o método &ldquo;re.sub()&rdquo;.  São pequenas diferenças, mas pode confundir quem conhecia regex de Perl, por exemplo, se costuma usar Vim como editor, que suporta regex Perl.</p>
<p>De qualquer forma, linguagens que quase totalmente suportam a sintaxe de regex de Perl são Python, Javascript, PHP, Ruby. Como falei antes, a biblioteca &ldquo;re&rdquo; de Python é só uma casca pra biblioteca Oniguruma, que em japonês significa carro do demônio, sei lá porque. A gem Oniguruma de Ruby é a mesma coisa. A classe RegExp de Javascript também. Oniguruma é uma das melhores e mais rápidas bibliotecas de regex que existem. Mas não é a única. Linguagens como C#, C++, Go usam outra biblioteca, a &ldquo;RE2&rdquo;. Não confundir com Resident Evil 2.</p>
<p>Acho que nem a Oniguruma e nem a RE2 forçam a sintaxe de Perl. Eles oferecem o motor, mas os detalhes da sintaxe, cada linguagem pode configurar como quiser. Por exemplo, C# tem a opção de usar &ldquo;verbatim string literal&rdquo; que é escrever a regex com arroba na frente, assim não precisa escapar caracteres especiais dentro do regex com barra ao contrário.</p>
<p>Algumas linguagens não usam o estilo Perl e sim a sintaxe ICU, de International Components for Unicode. Exemplos disso são a classe <code>java.util.regex</code> de Java, a classe <code>icu::RegexPattern</code> de C++, a classe <code>NSRegularExpression</code> de Swift da Apple, as funções &ldquo;preg_*&rdquo; de PHP. Eu pessoalmente gosto mais da sintaxe Perl. Quando precisava usar ICU pra programar mobile de iOS em Swift ou Objective-C, lembro que vira e mexe tropeçava na sintaxe.</p>
<p>11 anos atrás fiz um pequeno experimento em Objective-C pra importar o Oniguruma e adicionar sintaxe de regex de Perl pra programar iOS. É uma das poucas coisas que ainda sobrou no meu GitHub. Maioria dos meus códigos sempre foi pra projetos de cliente por isso tenho bem pouca coisa aberta. Essa foi uma delas. 11 anos atrás foi 2011, pouco mais de 2 anos depois que a App Store abriu oficialmente.</p>
<p>Além do regex não ser compatível com Perl, formatação de data e hora também não era compatível com a biblioteca Time de Linux. Sabe a função strftime? Onde podemos pedir pra formar data e hora usando uma string de template como &ldquo;%d/%m/%Y %H:%M:%S&rdquo;?</p>
<p>Em Objective-C e depois Swift, usando a classe <code>DateFormatter</code> com o método <code>dateFormat</code>, o mesmo template fica assim: &ldquo;dd/MM/yyyy HH:mm:ss&rdquo;. É bem diferente e isso me incomodava porque Ruby usa o mesmo padrão de C mas de novo, Objetive-C e Swift usam o padrão ICU.</p>
<p>Diferente de Regex onde a sintaxe Perl ainda é forte em muitas linguagens em vez do formato ICU, no caso de formatação de data e hora, o formato ICU é mais forte do que strftime de C. C++, Python, Ruby, Elixir ainda usam o formato de C, mas Swift, Java, C#, até PHP e JavaScript usam o formato ICU. Tecnicamente, por serem padrões internacionais de Unicode, o formato ICU é quem deveria ser considerado o &ldquo;correto&rdquo;, mas por direito, formatos derivados do mundo Unix e Linux continuam sendo usados.</p>
<p>E de novo, naquele projetinho de biblioteca que fiz pra Objective-C, além de importar Oniguruma, também implementei uma versão de formatador de data e hora usando a função &ldquo;strftime&rdquo; da biblioteca &ldquo;time&rdquo; de C. Assim eu poderia usar os formatos de Linux e contrariar o formato ICU. E por que? Só pra mostrar que era possível.</p>
<p>E eu contei essas duas histórias pra ilustrar um ponto. Não sei se vocês entendem que muita coisa de Javascript ou Python não são escritos em Javascript ou Python e sim em C. A classe RegExp de Javascript ou &ldquo;re&rdquo; de Python são meras casquinhas mas por baixo chamam bibliotecas escritas em C.</p>
<p>Eu digo isso porque muitas linguagens são escritas nelas mesmas. O compilador de Rust é escrito em Rust. O compilador de Go é escrito em Go. Muito do OpenJDK de Java é escrito em Java. Linguagens compiladas costumam muitas vezes ser escritos nelas mesmas. Isso não torna automaticamente uma linguagem melhor ou pior. Apenas facilita pros desenvolvedores das linguagens não ter que ficar mudando de linguagem o tempo todo. Por exemplo, escrever o compilador em C mas as bibliotecas em Rust. Lembrei disso quando conversei com os criadores e mantenedores de Ruby e eles comentando que escrevem mais C que Ruby.</p>
<p>Isso é mais uma curiosidade, não comecem a brigar em Reddit dizendo que sua linguagem é melhor porque é bootstrapped, ou seja, escrita nela mesma. Mais importante é o seguinte: linguagens interpretadas de script como Python, Ruby, Javascript ou PHP herdam muitas características da plataforma onde nasceram: do C. Diferente de Java ou Go que é na maior parte escrita nelas mesmas, linguagens de scripts costumam parecer mais &ldquo;cascas&rdquo; pras funcionalidades de C.</p>
<p>Veja a função de formatação de data e hora &ldquo;strftime&rdquo; de Ruby ou &ldquo;datetime.strftime&rdquo; de Python. Por que acha que tem esses nomes? Porque herdaram da função strftime da biblioteca time, cujo cabeçalho se chama &ldquo;time.h&rdquo;. Se num Linux, abrir um terminal e digitar &ldquo;man strftime&rdquo; vai dar os detalhes da função original de C. Essa função fica disponível numa biblioteca compartilhada chamada &ldquo;libc&rdquo; ou &ldquo;glibc&rdquo;, que praticamente todo programa que roda em Linux carrega e usa.</p>
<p>Vamos ver mais exemplos? Em Python, claro, muitos dos métodos do módulo &ldquo;os&rdquo; são derivados da libc. Temos &ldquo;os.mkdir()&rdquo; pra criar diretórios, não por acaso, é o mesmo nome do comando que você usa em qualquer terminal de linha de comando pra criar diretórios, porque ambos usam a mesma função da libC. Ou &ldquo;os.getenv()&rdquo; que é o mesmo nome da função da libC pra pegar variáveis de ambiente. Tem funções do módulo &ldquo;ctypes&rdquo; que, como o nome diz são &ldquo;tipos de C&rdquo;, como &ldquo;ctypes.memcmp()&rdquo; que compara dois blocos de memória e retorna um inteiro. Em particular vários métodos do módulo &ldquo;math&rdquo; herdam funções do cabeçalho &ldquo;math.h&rdquo; da libC como &ldquo;math.sin()&rdquo; pra calcular seno ou &ldquo;math.pow()&rdquo; pra calcular a potência de um número.</p>
<p>A mesma coisa vale pra Javascript, Ruby, PHP. E eu quero pegar esse gancho pra voltar pros projetos mais populares de Python. Vamos pegar o Pandas que é uma ferramenta muito poderosa, flexível e razoavelmente amigável de usar pra fazer análise e manipulação de dados. Povo de data science acho que começa aprendendo Pandas. Se abrirmos a página do projeto no GitHub, podemos ver que a maior parte é escrita em Python mesmo.</p>
<p>Mas essa não é a história toda. Como todo bom projeto moderno de Python, tem um arquivo de &ldquo;requirements&rdquo; que lista as dependências que o projeto precisa carregar pra funcionar, vamos dar uma fuçada. Tem uma quantidade enorme de bibliotecas, mas 2 das principais incluem o numpy e scipy. Vamos começar pelo numpy que é importantíssimo porque fornece coisas como array multidimensional, funções de álgebra linear, transformada de Fourier e mais.</p>
<p>Vamos abrir o projeto no GitHub e olha só: um terço é código C. Um dos pontos fortes do numpy é justamente facilitar integrar com bibliotecas em C e Fortran. É por isso que pra gerar o pacote pra Mac, por exemplo, o script que tá na wiki manda instalar o pacote de Fortran. Ou na página de wiki pra Windows, precisa do MSVC que é o pacote de compiladores de C e C++ do Visual Studio da Microsoft. Sim, pra gerar o pacote de um projeto de Python, precisa de compiladores de C, C++ e Fortran. Sabiam disso?</p>
<p>O numpy é requisito pra usar o scipy que é um conjunto de módulos científicos pra estatística, otimização, integração, álgebra linear, processamento de sinais e imagens e muito mais. Ele usa os arrays multidimensionais de numpy por exemplo. E no que SciPy é feito? Quase um quinto do código é Fortran e quase outro quinto é C e C++. Ou seja, pelo menos dois quintos do projeto todo também não é em Python e também precisa de compiladores de Fortran, C e C++.</p>
<p>Todo projeto de data science feito em Python precisa usar numpy e scipy, que são os grandes diferenciais, e eles são feitos em boa parte em Fortran, C e C++ e não em Python. E isso não é pra minimizar, denegrir, ou falar mal de Python, mas sim pra quem não sabia entender que só porque a cara da frente é numa linguagem, não significa que por baixo do capô também é.</p>
<p>E o Tensorflow que todo mundo fica hypado pra machine learning? Vamos ver a página no GitHub? Olha só, bem mais da metade do código é em C++, só um quinto dele é Python. O grosso, o motor mesmo, é todo em C++. E o PyTorch que é redes neurais acelerado por GPU? Metade desse código é em C++. E isso porque eu não estou olhando as dependências no arquivo de requirements.</p>
<p>Uma parte significativa que não tá contando aqui é a biblioteca de CUDA da NVIDIA e os shaders pra aceleração em GPU, que podem ser escritos em várias linguagens, incluindo Python mas compilam pra HLSL ou GLSL que é o OpenGL Shader Language. Eu falo sobre linguagens de shaders e GPU no video da longa história de CPUs e GPUs, depois dêem uma olhada.</p>
<p>Pra usar esses projetos, sabendo um pouco de Python é suficiente, mas se alguém queria realmente entender como redes neurais,  aceleração via GPU pra esse tipo de computação realmente funciona, obrigatoriamente não é Python e sim C++ e linguagens de shaders pra GPU. Eu estou sendo pedante nisso porque da forma como muita gente faz propaganda, parece que é tudo feito em Python e basta saber Python pra resolver tudo nessa área, o que não é verdade.</p>
<p>E de novo, meu objetivo não é pegar no pé do Python. Como contei no começo, ele estava no lugar certo na hora certa quanto Perl 5 foi desaparecendo. Linguagens de script como Perl ou Python, que foram feitos pra funcionar muito perto do sistema operacional, são ótimos pra funcionar como cascas pras bibliotecas de alta performance feitos em C ou C++.</p>
<p>Era esse tipo de coisa que se fazia com  Perl, que era menos ruim do que escrever scripts de SH ou Bash, e depois passamos a usar Python que, com sua sintaxe bem simples e espartana, caiu como uma luva pra administradores de sistemas que precisavam fazer coisas rápidas.</p>
<p>Com o tempo passou a ser adotado em distros com programas como o Conda que é escrito em Python e serve pra instalar e gerenciar pacotes. Não confundir com Anaconda que é outro projeto pra computação científica. Daí o pessoal de pesquisa do Google passa anos desenvolvendo um Tensorflow da vida em C++. Nesse estágio ele é difícil de usar, precisa saber C++. Em cima dele fizeram scripts em Python e um ferramental com mais usabilidade e mais simples de usar pra maioria das pessoas. Essa é vantagem do Python.</p>
<p>Diferente do Tensorflow de Python, o tfjs ou Tensorflow.js é na maior parte escrito realmente em TypeScript e Javacript. Assim como em Python as partes que exigem performance e falar diretamente com o hardware como GPUs, são feitos em C++, mas na versão de Javascript ele pode rodar no navegador e utiliza o suporte a WebGL e Web Assembly. A versão de Javascript não faz tudo que a de Python mas é suficiente. Tem o Tensorflow Lite pra rodar em smartphones também. Enfim, o principal é entender que quando precisamos de performance normalmente precisamos mais do que Python.</p>
<p>E pra não pegar só no pé do Python, Javascript é similar. Tudo que é impressionante e roda rápido no navegador não é mérito do Javascript e sim do navegador que oferece APIs como WebGL pra dar acesso à aceleração via GPU. E todo navegador é feito em C++. Entendam, toda linguagem interpretada depende muito do sistema operacional e outras bibliotecas instaladas.</p>
<p>Voltando ao ponto onde linguagens como Python, Javascript ou PHP dependem de bibliotecas como a LibC, - tanto que os nomes das funções são as mesmas das versões em C -, e se a LibC costuma vir em toda distro Linux mas não em Windows ou Mac, como que faz pra rodar Python em Windows? Vamos pro GitHub do CPython e vou te mostrar o que tem embaixo do tapete. Se procurarmos dentro desse repositório por &ldquo;ifdef MS_WINDOWS&rdquo; temos 8 páginas de resultados e olhem esses trechos.</p>
<p>Se nunca programou em C e não sabe o que é pré-processamento, deixa eu pegar um arquivo aleatório desse resultado. Vamos abrir o arquivo &ldquo;_ssl.c&rdquo;. Olha aqui na linha 628 tem um &ldquo;ifdef MS_WINDOWS&rdquo;. Se não existisse pré-processamento seria como se tivéssemos duas versões desse arquivo. Um chamado &ldquo;_ssl.c&rdquo; que teria esse trecho assim e outro arquivo chamado &ldquo;_ssl.c-ms_windows&rdquo; cujo trecho seria escrito dessa outra forma aqui. Conseguem ver a diferença?</p>
<p>O compilador, se estiver em Windows, vai compilar o código com esse &ldquo;if&rdquo; a mais chamando essa função &ldquo;SetFromWindowErr&rdquo; que só precisa no Windows. Em Linux ou Mac esse código não existe. E isso acontece em dezenas de arquivos, 8 páginas de resultados no GitHub. Ocódigo do CPython pra Windows é diferente da versão de Linux.</p>
<p>Por isso o CPython de Windows não é igual ao CPython de Linux que não é igual ao CPython de Mac. Isso vale pra tudo PHP de Windows, Javascript de Windows. Então vamos falar de Node.js. O Node é basicamente um projeto de C++. Um quinto desse projeto é só C++ mas precisamos falar de uma das dependências, o libuv. O coração da principal funcionalidade do Node, que é o famoso eventloop, o reactor, é implementado quase 100% em C. Vamos abrir a página dele no GitHub. Olha só aqui embaixo, 97% de C, mas o resto é só coisas administrativa, por isso é 100%.</p>
<p>E o que é a libuv. Novamente, sendo uma biblioteca de C, num terminal de Linux podemos usar o comando &ldquo;man&rdquo; que, se alguém tinha dúvida, significa &ldquo;manual&rdquo; e não &ldquo;homem&rdquo;. Normalmente bibliotecas de C instalam sua documentação nesse formato. E como diz aqui, esse projeto foi feito pelos desenvolvedores do Node.js pra ser uma biblioteca com capacidade pra rodar em diversas plataformas como Windows, Mac, BSD e é quem abstrai as funcionalidades de I/O assíncrono que o Node precisa pro seu reactor.</p>
<p>Cada sistema operacional implementa I/O assíncrono diferente. Linux costuma ter suporte a epoll, UNIX BSD tem Kqueue, Windows tem IOCP que é I/O Completion Ports, que muitos argumentam que é superior a epoll ou kqueue. Ou seja, a principal funcionalidade do Node só é possível porque quem de fato fornece o músculo é o sistema operacional. O libuv é mais uma casca em C++ pra abstrair essas diferentes formas de fazer I/O assíncrono e tornar possível o Node rodar em qualquer sistema operacional.</p>
<p>E o Deno, o competidor que muitos consideram melhor que o Node? Eu nem quero tentar entrar nessa discussão de quem é melhor. Mas se abrirmos a página GitHub do Deno, vemos que mais da metade, mais de 60% é escrito em Rust. Ahhh será que é por isso que ele é melhor que o Node então? Na verdade não. Se procurarmos por libuv nesse repositório, vemos que o Rust importa as mesmas funções do libuv do Node, então em termos do eventloop, ambos fazem a mesma coisa. Claro que podem parametrizar pra se comportar diferente, mas a fundação é a mesma e, de novo, é C++.</p>
<p>Eu poderia passar o dia todo dando exemplos em Ruby, em PHP, e até mesmo em Java. Mas ué, tudo que é de Java não era escrito todo em Java? Na realidade não. Como último exemplo, vamos abrir a página do OpenJDK e olha aqui embaixo, 13% do Java é escrito em C++. Mas o que? Vamos ver, olha só, muita coisa do HotSpot que é o motor do JIT ou Just in Time Compiler da JVM é escrito em C++.  Nem mesmo o Java, que nasceu nos anos 90 com a frase de &ldquo;Write Once, Run Anywhere&rdquo; é puramente feito em Java. E se isso não ficou claro, olha a página de resultados de arquivos C++: tem pelo menos 100 páginas de resultados, uma parte considerável da JVM não é feito em Java.</p>
<p>E isso não é perseguição. Ruby, PHP, DotNet, Go, Rust, Swift, e todo o resto tem partes consideráveis que depende de C e C++, coisas como a LibC, coisas de criptografia como OpenSSL ou alternativas como libsodium. Imagina se toda linguagem tivesse que fazer criptografia do zero? O mundo já tem bugs de segurança demais. É melhor todo mundo reusar o que já existe de melhor e mais maduro.</p>
<p>E isso só falando em Linguagens. Quem fez Ciências da Computação decentemente não perde muito tempo com discussões de melhor linguagem. Mas pessoal de desenvolvimento web, por alguma razão vira e mexe entra nesse tipo de argumento. Então só pra complementar, além de linguagens, no mundo web temos diversos outros serviços e componentes que precisamos usar no nosso dia a dia.</p>
<p>Por exemplo, no que é escrito o banco de dados Postgres? É mais de 85% de C. E o MySQL, mais especificamente o MariaDB? Metade é C++ e quase 40% é C. Bom, mas isso deve ser porque são velhos né, um banco de dados mais novo como MongoDB deve ser em outra coisa mais moderna? Não, 72% é C++.</p>
<p>E o Redis que usamos pra cache ou filas o tempo todo? É quase 80% de C. Mas não significa que tudo é em C ou C++. Vejamos o banco de dados distribuído Cassandra, esse é quase 100% feito em Java e não é o único. O Apache Kafka que é o sistema de filas que falei no episódio passado, é quase 75% em Java e 22% em Scala, que é outra linguagem que roda na JVM então 97% é Java. E falando em sistemas de fila, outro com excelente reputação e maturidade e que, se não me engano, era uma das estrelas por trás de serviços como Whatsapp, é o RabbitMQ que é escrito em Erlang, a plataforma por trás da moderna linguagem Elixir.</p>
<p>E aqui vem outra coisa que eu queria explicar. Tanto Java quanto Erlang são muito bons pra construir sistemas distribuídos, de forma mais produtiva do que fazer em C ou C++. Go também serviria, mas ele ainda é muito novo comparado com os outros, talvez o próximo grande banco de dados seja em Go, não sei. Ele ainda não se provou nesse espaço. Então deixa eu dar minha perspectiva sobre onde cada linguagem tem mais força.</p>
<p>Todo sistema operacional hoje é feito numa mistura de C e C++. E quando eu falo sistema operacional ou OS quero dizer coisas como a kernel e drivers de dispositivos, os componente que rodam no Ring Zero de maior privilégio. Todo o resto, nossos programas e scripts rodam no Ring 3, que tem menos privilégios, o que chamamos de userland. Todo programa em userland precisa usar o kernel do OS como intermediador pra falar com dispositivos como SSD, placa de rede, placa de video, teclado, mouse e tudo mais. O único que tem permissão pra falar com o hardware diretamente costuma ser o kernel e seus drivers, que rodam no mesmo espaço de memória.</p>
<p>Isso não vai mudar por muito tempo. A kernel XNU da plataforma Darwin do Mac, a kernel NT do Windows, a kernel do BSD UNIX, que eu não lembro se tinha codinome. Dispositivos Android usam a kernel do Linux. Dispositivos iOS como iPhone usam uma kernel derivada do MacOS. Existem outras de nicho, mas essas são as principais e vão continuar sendo por muito tempo. E todas elas tem uma coisa em comum: são escritos em C e C++. E porque isso importa já que no final, depois de compilar, vira tudo um binário?</p>
<p>Porque essas kernels expõe suas funcionalidades como funções de C, que podemos interfacear com os diversos arquivos de cabeçalho com extensão &ldquo;.h&rdquo;. Pra realmente entender isso, precisa entender um pouquinho de como compilação de C funciona. Eu expliquei parte disso em episódios como do Hello World, a diferença de arquivos texto e binário e o episódio de linguagem interpretada versus compilada. Depois assistam pra entender.</p>
<p>A explicação que vou fazer agora acho é um pouco errado, mas é pra simplificar o modelo na cabeça. Se alguém tiver uma explicação de forma mais simples, mandem nos comentários abaixo. Mas pense num driver, como driver da NVIDIA pra placa de video, ou driver pra placa de rede e coisas assim com uma DLL de Windows ou uma biblioteca .SO de Linux. A kernel depois que boota começa a carregar esses drivers. Pense um &ldquo;import&rdquo; em Python ou Javascript.</p>
<p>As estruturas de dados e funções que esses drivers implementam vão existir no mesmo espaço de memória que as outras estruturas da própria kernel. É como se a kernel já viesse embutida com esses drivers. Uma vez carregado, o driver enxerga tudo que a kernel enxerga e vice versa. Lembra ponteiros? A kernel pode manipular diretamente uma struct de uma placa de rede, por exemplo.</p>
<p>Isso é necessário porque no nível da kernel gostaríamos de ter a maior performance possível, o menor número de abstrações. A kernel ideal não desperdiça recursos fazendo indireções e abstrações. Por causa disso você nunca vai conseguir fazer um driver em Javascript, Python, Java ou Erlang.</p>
<p>O interpretador de Javascript, como o Google V8 ou o Spidermonkey da Mozilla, assim como a JVM de Java ou a BEAM de Erlang, poderíamos classificar mais ou menos como máquinas virtuais. A intenção delas é abstrair o sistema operacional por baixo, a kernel. Assim um programador de Python pode escrever praticamente o mesmo código pra rodar em Mac ou Windows ou Linux e o interpretador do Python vai se virar pra fazer funcionar embaixo. A JVM é a que mais esconde o OS por baixo, Python ou Javascript escondem menos e às vezes precisa fazer um &ldquo;if windows faça X else if Linux faça Y&rdquo; mas é raro precisar.</p>
<p>Pra ser capaz de criar um driver de dispositivo que consiga interagir direto com a kernel, em particular carregar no mesmo espaço de memória, precisa ser uma linguagem compilada que gera binários com a mesma ABI de C, ou seja cujo binário seja compatível com binários gerados pelo compilador de C em termos de tipos de dados básicos terem o mesmo tamanho, mesmo alinhamento, mesmo layout de structs e unions e chamadas de funções sigam as mesmas convenções, tipo uma compatibilidade de API no nível binário.</p>
<p>Outra coisa que talvez você não saiba de linguagens compiladas como C, C++, Rust, Nim, D ou Go e linguagens tecnicamente interpretadas como Python, Ruby, Javascript, PHP, e até Java, C# ou Erlang e Elixir. Eu digo &ldquo;tecnicamente interpretadas&rdquo; porque Java não gera binário nativo. Sim, Java ou Javascript tem coisas como JIT na JVM ou V8, que no final pode gerar binário de alguns trechos, mas originalmente não são binários nativos. Só em tempo de execução.</p>
<p>O problema de não ter binário nativo de antemão é que não sabemos o endereço de funções e estruturas de dados. Por isso não temos como fazer um código de C que aponta diretamente pra essas estruturas usando coisas como ponteiros. Mesmo assim é possível integrar binário feito em Rust ou C com programas em Java ou Javascript. Por exemplo, um JavaScript consegue importar bibliotecas como OpenSSL, só que um String de Javascript não é um tipo de dados igual a um String de C. Um String de Python não é igual a um String de C#, então como faz pra uma linguagem falar com outra?</p>
<p>A única forma do binário gerado por uma linguagem conseguir usar um tipo de dados diretamente via ponteiro de outra linguagem, só é possível se são binariamente iguais, tenham a mesma ABI. Quando não tem, precisa existir um processo de conversão. Você já deve ter ouvido falar dos termos serializar e desserializar ou marshalling e unmarshalling. A grosso modo, no mundo de web seria mais ou menos como converter uma estrutura XML num JSON ou vice versa.</p>
<p>O que acontece quando se chama uma API que só devolve XML mas precisa passar pra uma classe ou outra biblioteca que só entende JSON? Precisa converter, só que conversão tem o seguinte defeito. Veja este exemplo de XML que a API hipotética devolveria, ela tem  845 bytes.</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 class="nt">&lt;product&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;name&gt;</span>Playstation 5<span class="nt">&lt;/name&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;sku&gt;</span>PS5<span class="nt">&lt;/sku&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;manufacturer&gt;</span>Sony<span class="nt">&lt;/manufacturer&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;category&gt;</span>Video Games<span class="nt">&lt;/category&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;description&gt;</span>
</span></span><span class="line"><span class="cl">    The Playstation 5 is the next-generation gaming console from Sony, featuring lightning-fast loading, stunning graphics, and immersive gameplay.
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/description&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;specifications&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;CPU&#34;</span><span class="nt">&gt;</span>AMD Zen 2<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;GPU&#34;</span><span class="nt">&gt;</span>AMD RDNA 2<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;Memory&#34;</span><span class="nt">&gt;</span>16 GB GDDR6<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;Storage&#34;</span><span class="nt">&gt;</span>1 TB NVMe SSD<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;Dimensions&#34;</span><span class="nt">&gt;</span>39 x 10 x 26 cm<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;specification</span> <span class="na">name=</span><span class="s">&#34;Weight&#34;</span><span class="nt">&gt;</span>4.5 kg<span class="nt">&lt;/specification&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/specifications&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;pricing&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;price</span> <span class="na">currency=</span><span class="s">&#34;USD&#34;</span><span class="nt">&gt;</span>499.99<span class="nt">&lt;/price&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/pricing&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;inventory&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;quantity&gt;</span>50<span class="nt">&lt;/quantity&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/inventory&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/product&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>Digamos que fizemos um código pra converter de XML pra JSON ou seja, faz marshall entre os dois formatos, e eis o JSON equivalente:</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-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;product&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Playstation 5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;sku&#34;</span><span class="p">:</span> <span class="s2">&#34;PS5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;manufacturer&#34;</span><span class="p">:</span> <span class="s2">&#34;Sony&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;category&#34;</span><span class="p">:</span> <span class="s2">&#34;Video Games&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;The Playstation 5 is the next-generation gaming console from Sony, featuring lightning-fast loading, stunning graphics, and immersive gameplay.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;specifications&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;specification&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;CPU&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;AMD Zen 2&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;GPU&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;AMD RDNA 2&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Memory&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;16 GB GDDR6&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Storage&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;1 TB NVMe SSD&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Dimensions&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;39 x 10 x 26 cm&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Weight&#34;</span><span class="p">,</span> <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="s2">&#34;4.5 kg&#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><span class="line"><span class="cl">    <span class="nt">&#34;pricing&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;currency&#34;</span><span class="p">:</span> <span class="s2">&#34;USD&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;value&#34;</span><span class="p">:</span> <span class="mf">499.99</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="nt">&#34;inventoryQuantity&#34;</span><span class="p">:</span> <span class="mi">50</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>Esse JSON é um pouco menor, com 808 bytes. O importante é o seguinte: nesse instante a função que converte tem em memória as duas versões, ocupando o total de 845 + 808 que dá mais de 1600 bytes. Agora pense no nível de uma struct de C versus um objeto JSON de Javascript. Não tem como criar um ponteiro pra um objeto JSON a partir de um código C e tentar usar como se fosse uma struct. Se fossem compatíveis, não precisaria duplicar memória, mas como não são compatíveis, precisamos fazer marshalling, duplicando a memória usada.</p>
<p>Eu acho que muito iniciante não pensa assim, mas comece a enxergar onde memória é duplicada. Por exemplo, quando você abre seu navegador e pede uma página web pra amazon.com por exemplo, sabemos que o servidor web da Amazon vai processar o que precisar pra no final gerar um string gigante que é o conteúdo HTML pra te responder. Esse HTML está ocupando memória no servidor. Quando ele envia pra você, e chega no seu navegador, o navegador tem uma cópia dessa string. Tanto o servidor web quanto seu navegador tem uma cópia do mesmo HTML, duplicando memória nos dois lugares. Isso é inevitável.</p>
<p>O mesmo vale pra um script feito em Javascript ou Python. Lembra da tal função strftime pra formar data e hora que falei lá no começo do episódio? Por baixo é uma função em C. No Python criamos a String &ldquo;%d/%m/%Y&rdquo; pra formatar a data e chamamos &ldquo;datetime.strftime&rdquo;. Por baixo essa função vai chamar a função da LibC que também chama &ldquo;strftime&rdquo;, pra isso precisa criar uma String de C, mas a String de Python não é compatível com String de C, então precisa converter e isso vai duplicar esse String.</p>
<p>Esse é um dos custos escondidos que tem em várias linguagens quando integram com bibliotecas locais feitas em C por exemplo, ou quando fazemos chamadas remotas pra APIs de microserviços ou servidores de banco de dados. Várias estruturas de dados como Strings estão o tempo todo sendo duplicados pra converter num formato que o outro lado entende. O exemplo que dei de XML e JSON são pequenos, nem 1 kilobyte, mas no mundo real, quem já trabalhou em projetos de verdade sabe que estamos transitando documentos muito maiores que isso, de centenas de usuários o tempo todo. É uma das razões de tanta diferença no uso de recursos entre diferentes linguagens ou diferentes bibliotecas.</p>
<p>Quando Java tinha a noção de escrever tudo em Java, ou seja, tudo rodando dentro do mesmo processo da mesma JVM, onde todas as bibliotecas também seriam em Java, não era só pra ser bonito. Uma das vantagens é que se tudo fosse feito em Java, internamente não precisa ter marshalling e unmarshalling, poderia lidar diretamente com referências pras estruturas de dados. Sendo que se integrar como uma biblioteca feita em C usando o recurso de JNI ou Java Native Interface,  precisa existir uma tradução entre o layout do espaço de memória de Java pro espaço do C. O mesmo vale pra outras linguagens que não trabalham com os mesmos tipos de C.</p>
<p>Se ficou abstrato até aqui, deixa eu mostrar um exemplo super besta em Python. Considere o seguinte trecho de código em Python:</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-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Import the extension module</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">c_extension</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Call the py_c_function function with a Python string argument</span>
</span></span><span class="line"><span class="cl"><span class="n">c_extension</span><span class="o">.</span><span class="n">py_c_function</span><span class="p">(</span><span class="s2">&#34;Hello, C function!&#34;</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>Aqui eu estou importando um módulo qualquer feito em C e em seguida chamo uma função declarada nesse módulo passando um String qualquer. Vamos ver como teríamos que fazer essa função em C.</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="cp">#include</span> <span class="cpf">&lt;Python.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Declare a C function that expects a char* as a parameter
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">c_function</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Do something with the C string here
</span></span></span><span class="line"><span class="cl">  <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;C function received string: %s</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">s</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></span><span class="line"><span class="cl"><span class="c1">// Define a Python wrapper function for the C function
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">PyObject</span><span class="o">*</span> <span class="nf">py_c_function</span><span class="p">(</span><span class="n">PyObject</span><span class="o">*</span> <span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Extract the Python string argument from the Python tuple
</span></span></span><span class="line"><span class="cl">  <span class="n">PyObject</span><span class="o">*</span> <span class="n">py_s</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">PyArg_ParseTuple</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">&#34;O&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">py_s</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">NULL</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></span><span class="line"><span class="cl">  <span class="c1">// Convert the Python string to a C string
</span></span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">s</span> <span class="o">=</span> <span class="nf">PyBytes_AsString</span><span class="p">(</span><span class="n">py_s</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">s</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">NULL</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></span><span class="line"><span class="cl">  <span class="c1">// Call the C function with the C string
</span></span></span><span class="line"><span class="cl">  <span class="nf">c_function</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Return None to indicate that the function succeeded
</span></span></span><span class="line"><span class="cl">  <span class="n">Py_RETURN_NONE</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>Primeiro declaro a tal função <code>c_function</code> que recebe uma string e imprime na tela. Um string em C é só um array de caracteres que termina com caracter nulo. Já uma string em Python é um objeto, uma estrutura de dados bem diferente. Por isso embaixo declaramos uma segunda função chamada <code>py_c_function</code> que recebe um objeto Python cujo tipo é um ponteiro pra <code>PyObject</code> chamado <code>args</code>. O primeiro parâmetro é o <code>self</code> que todo método de Python declara explicitamente, o equivalente <code>this</code> em Javascript, por exemplo.</p>
<p>O importante nessa função é essa <code>PyBytes_AsString</code> que vai devolver o ponteiro pro array de chars que está nessa variável <code>py_s</code>. Agora, praquele código em Python conseguir chamar esse código em C, precisamos de outro código que vai intermediar isso, escrito em Cython com extensão &ldquo;.pyx&rdquo; mais ou menos 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-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># c_extension.pyx</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Declare a C function that expects a char* as a parameter</span>
</span></span><span class="line"><span class="cl"><span class="n">cdef</span> <span class="n">void</span> <span class="n">c_function</span><span class="p">(</span><span class="n">const</span> <span class="n">char</span><span class="o">*</span> <span class="n">s</span><span class="p">)</span> <span class="n">nogil</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Do something with the C string here</span>
</span></span><span class="line"><span class="cl">    <span class="n">printf</span><span class="p">(</span><span class="s2">&#34;C function received string: </span><span class="si">%s</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Define a Python wrapper function for the C function</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">py_c_function</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Convert the Python string to a C string</span>
</span></span><span class="line"><span class="cl">    <span class="n">cdef</span> <span class="n">const</span> <span class="n">char</span><span class="o">*</span> <span class="n">cs</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">encode</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Call the C function with the C string</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_function</span><span class="p">(</span><span class="n">cs</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>Esse é o código que faz o tal marshalling, ou conversão, do objeto de String do Python pra um array de char, é isso que faz o método &ldquo;encode&rdquo; da String, duplica a string. Isso é importante porque o objeto de String de Python está sob controle do garbage collector de Python. Digamos que ele decida que ninguém tá usando mais essa String e limpa da memória. Mas no lado do C, que o garbage collector não enxerga, a String ainda era necessária, daí iria crashear o programa. Mas passando uma cópia separada, agora cada um tem seu próprio string e um não pisa no pé do outro, mas o custo disso foi duplicar essa string.</p>
<p>É assim que linguagens como Python, Javascript, Ruby ou PHP fazem pra falar com funções de bibliotecas em C: via marshalling e unmarshalling de estruturas de dados, onde cada lado gerencia a memória do seu jeito. No lado do Python o garbage collector dele se vira com os objetos que ele criou. E do lado do C vai usar, por exemplo, malloc e free, pra lidar com seu espaço de memória. Por isso que precisa duplicar memória na comunicação entre eles e por isso é mais pesado.</p>
<p>E qual a diferença com uma linguagem que compila binário nativo compatível com C como Rust? Vamos fazer a mesma coisa usando Rust agora. Eis um exemplo de um código Rust que chama uma função de alguma biblioteca C qualquer que recebe uma String como parâmetro, e sendo em C, sabemos que String de C é sempre um ponteiro pra um array de chars terminado em 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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">ffi</span>::<span class="n">CString</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">os</span>::<span class="n">raw</span>::<span class="n">c_char</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Declare the C function using the extern keyword
</span></span></span><span class="line"><span class="cl"><span class="k">extern</span><span class="w"> </span><span class="s">&#34;C&#34;</span><span class="w"> </span><span class="k">fn</span> <span class="nf">c_function</span><span class="p">(</span><span class="n">s</span>: <span class="o">*</span><span class="k">const</span><span class="w"> </span><span class="n">c_char</span><span class="p">)</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="c1">// Do something with the C string here
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;C function received string: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="k">unsafe</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">CStr</span>::<span class="n">from_ptr</span><span class="p">(</span><span class="n">s</span><span class="p">).</span><span class="n">to_str</span><span class="p">().</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">main</span><span class="p">()</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="c1">// Create a Rust string
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;Hello, C function!&#34;</span><span class="p">.</span><span class="n">to_owned</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Convert the Rust string to a CString
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">c_string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CString</span>::<span class="n">new</span><span class="p">(</span><span class="n">s</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Call the C function with the CString
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">c_function</span><span class="p">(</span><span class="n">c_string</span><span class="p">.</span><span class="n">as_ptr</span><span class="p">())</span><span class="w"> </span><span class="p">};</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>Esse &ldquo;extern C&rdquo; é pra declarar a assinatura da função C externa que vamos chamar, cujo nome é <code>c_function</code> de novo e o parâmetro é o ponteiro pro array de char. Na função <code>main</code> começamos criando um String de Rust. Só que, assim como no caso do Python, um String de Rust é um objeto, uma estrutura de dados diferente de array de char, por isso criamos uma <code>CString</code> que existe no módulo &ldquo;std::ffi&rdquo; pra converter o String de Rust num String de C. Finalmente, dentro de um bloco inseguro, <code>unsafe</code> chamamos a função de C passando esse novo String de C como ponteiro.</p>
<p>Assim como em Python, tivemos que duplicar a memória pra converter os strings. Mas em Rust podemos pular a parte de criar a string de Rust e direto já trabalhar com String de C. Pra demonstrar isso deixa eu começar fazendo uma nova função em Rust besta que recebe um String e converte tudo pra maiúsculo:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">rust_function</span><span class="p">(</span><span class="n">s</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Do something with the Rust string here
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">s</span><span class="p">.</span><span class="n">to_uppercase</span><span class="p">()</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>Simples, recebe uma referência pra um String e retorna outro String. Agora vamos ver a mesma função mas usando direto String de C em vez do String de Rust:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">ffi</span>::<span class="n">CString</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">rust_function</span><span class="p">(</span><span class="n">s</span>: <span class="nc">CString</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">CString</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="c1">// Convert the CString to a slice of c_char
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">s_slice</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="n">c_char</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">as_ref</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Iterate over the slice and uppercase the characters
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">s_uppercase</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">c_char</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s_slice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|&amp;</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">to_ascii_uppercase</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Create a new CString from the uppercase characters
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">CString</span>::<span class="n">new</span><span class="p">(</span><span class="n">s_uppercase</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</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>Eu não sou super fluente em Rust então não sei se tem jeito melhor de fazer isso, mas veja a diferença. Agora o parâmetro é direto um string de C e devolve um string de C também. Um objeto de String de Rust tem métodos conveniente como &ldquo;to_uppercase&rdquo; pra transformar tudo em maiúsculo. Mas pra trabalhar em cima de um String de C, precisamos manualmente iterar caracter a caracter do array e transformar em maiúsculo com o método &ldquo;to_ascii_uppercase&rdquo;.</p>
<p>Pra fazer código Rust compatível com estrutura de C sem ficar convertendo em objetos nativos de Rust, que são mais convenientes mas duplica memória, precisamos praticamente escrever C usando sintaxe de Rust. Não vai ser o código mais bonito do mundo, mas em casos mais complexos que esse certamente vai ser melhor do que escrever C na mão, porque podemos contar com os recursos mais avançados de gerenciamento de memória e segurança que o compilador do Rust nos fornece. Mas se alguém tinha dúvidas de como trabalhar com Rust e C, por exemplo, na kernel do Linux, esse é um exemplo besta que ilustra como é mais ou menos.</p>
<p>Agora é minha opinião, mas dado o que expliquei até agora, o que eu penso de linguagens é o seguinte: se quiser fazer programação de sistemas, ou seja, coisas de baixo nível como drivers de dispositivo ou mesmo componentes de infraestrutura como a stack TCP, o certo é usar C, C++ e agora Rust ou até linguagens meio desconhecidas mas  de mais alto nível e compiladas como Nim ou a linguagem D, que como o nome diz, foi pensado pra ser sucessor de C mas nunca conseguiu  popularidade, talvez porque Rust e Go surgiram no meio do caminho e meio que roubaram os holofotes.</p>
<p>Numa camada acima vem construção de ferramentas de administração e infraestrutura. Antes muitos faziam bibliotecas em C como um oniguruma pra regex, e pra facilitar o uso, criavam clientes com linguagens dinâmicas de script como Python ou Perl da vida. Mas Rust e Go tornaram menos dolorido escrever esse tipo de software. A sintaxe é mais moderna e limpa do que C ou C++ então não é incomum que não precise mais fazer scripts de Python por cima pra facilitar o uso. Exemplo disso é um Docker, feito em Go. Antigamente alguém poderia criar um libdocker em C e um script chamado Docker em Python que integra com essa lib pra ser a ferramenta de linha de comando. Agora podemos usar só Go ou só Rust.</p>
<p>Go e Rust geram binários quase tão eficientes quanto C ou C++ e no caso de Rust até mais eficientes e mais seguros em muitos casos. Nos últimos anos o mundo Linux ganhou várias ferramentas como o terminal Alacritty feito em Rust que eu gosto bastante, e tem esta página com várias ferramentas alternativas escritas em Rust que eu uso no dia a dia como o &ldquo;exa&rdquo; que substitui o &ldquo;ls&rdquo;, o &ldquo;procs&rdquo; que substitui o &ldquo;ps&rdquo;, &ldquo;fd&rdquo; que substitui o &ldquo;find&rdquo; e muito mais, depois dêem uma olhada.</p>
<p>Depois entramos na categoria que não expliquei muito que são sistemas distribuídos.  São softwares como bancos de dados ou sistemas de armazemento de volumes grandes de dados. Aqui eu vejo muitas soluções que saíram do ecossistema Apache, como Apache Hadoop, Apache Solr, Apache Spark, Apache Kafka, Apache Cassandra e muito mais, todos feitos em Java. Precisava ser em Java? Não, por exemplo, existem alternativas como a DataStax que é Cassandra escrito em C++.</p>
<p>Mas o que surgiu desse esforço massivo da Apache foi um ecossistema de bibliotecas e ferramentas que facilitam o desenvolvimento de sistemas distribuídos, como o famoso ZooKeeper que é o componente responsável pelo consenso entre diversos nodes de um sistema distribuído. Se isso soou grego, pensa assim. A grande vantagem de um banco de dados Cassandra é conseguir aumentar a capacidade adicionando novos servidores no cluster. O sistema se coordena pra receber esse node e passa a distribuir o armazenamento e processamento pra ele. Se um node falha, o sistema de consenso lida com isso também, via eleições por exemplo. São soluções pra quem precisa lidar com petabytes de dados, não é pra tech startup pequena sair usando.</p>
<p>A grande vantagem do Java é ser mais produtivo e menos doloroso do que C++. Uma coisa é fazer algo relativamente &ldquo;pequeno&rdquo; em quantidade de linhas de código como uma ferramenta de linha de comando ou driver, outra coisa é criar um software massivo com milhões de linhas de código pra lidar com processos complexos como coordenação de dados massivos em nós de um sistema distribuído. Java foi a escolha certa na época, talvez a única escolha. Pouca gente conhecia Erlang e a sintaxe também nunca foi muito familiar pra quem se acostumou a herança de sintaxe do C. Hoje em dia um novo Cassandra poderia ser feito ou em Elixir ou em Go, mas sendo sistemas super complexos e já maduros, não existe motivo pra reescrever tudo nessas linguagens.</p>
<p>Erlang foi uma oportunidade perdida e Elixir apareceu uns 5 ou 10 anos mais tarde do que deveria. Se tivesse aparecido no começo dos anos 2000, certamente seria uma das linguagens mais usadas hoje. O ZooKeeper que eu falei, boa parte de suas funcionalidades existem por padrão no Erlang, que também usa uma máquina virtual e foi feito pra criar serviços distribuídos no mercado de telecomunicações nos anos 80. Ele estava muito à frente do seu tempo, muito antes de começarmos a nos preocupar com escalabilidade horizontal em tamanho de internet no fim dos anos 90.</p>
<p>Só que naquela época as opções era fazer tudo em C++, que é eficiente mas é masoquismo, ou C# mas aí  estava fechado no ecossistema de Windows da Microsoft, ou Java, que era multiplataforma e bem mais amigável do que C++. Por eliminação, escolhemos Java. Só que a JVM não foi criado pra sistemas distribuídos como Erlang, então empresas como Apache precisaram criar essa infraestrutura, componentes como o ZooKeeper, Storm, Flink.</p>
<p>Eu posso estar superestimando, mas acredito que se fosse fazer tudo que tem no portfolio da Apache em Elixir, seria mais produtivo e menos difícil hoje. Mas nunca vamos saber, porque a essa altura do campeonato, não vale a pena tentar reescrever um ecossistema inteiro. A Apache já resolveu esses problemas com Java e equiparou as funcionalidades como Erlang. Pra entender qual é essa grande vantagem do Erlang, assista essa minha palestra onde explico como isso funciona dentro da VM do Erlang.</p>
<p>Do ponto de vista de sistemas distribuídos, outra alternativa moderna é Go. Só que Go tem bem menos ferramentas do que Java ou Erlang, que tiveram muito mais tempo pra amadurecer. Assim como Java, primeiro precisaria criar a mesma infraestrutura. Algoritmos de consenso, sistema de arquivos distribuídos, sistema de processamento de batch, streams distribuídos, armazenamento em cluster e assim por diante, antes de conseguir fazer os equivalente Spark, Kafka ou Cassandra. De novo, é tudo especulação da minha parte, porque a realidade é que se eu precisar de sistemas distribuídos, vou olhar o ecossistema Java da Apache.</p>
<p>O que nos leva a criação de aplicações comerciais, como eu chamaria. Coisas que a maioria aqui assistindo provavelmente vai lidar mais. Aplicações web. Seja feito em Ruby on Rails, ou Django com Python, ou Laravel com PHP, ou Spring Boot com Kotlin, ou Express com TypeScript e assim por diante. Aqui vem uma divisor: os softwares que falei até aqui como uma kernel de OS, driver de dispositivo, ferramentas de linha de comando de Linux, ou até um Kafka ou Cassandra tem uma coisa em comum: o ideal é que eles mudem pouco, que a maior parte das melhorias sejam em pequenas otimizações de performance, segurança, estabilidade. Eu não quero meu banco de dados mudando tanto que a cada nova versão dá crash.</p>
<p>Já aplicações web, que lidam com consumidor final, seja um chat, ecommerce, rede social, ferramentas de produtividade como um Figma ou Canva, ele competem pela atenção e o dinheiro das pessoas. Pra competir, uma precisa adicionar rápido funcionalidades que superam do concorrente. Mesmo que apareça um bug novo que alguns vão reclamar, o importante é que dê pra subir correções rápido. Pra isso queremos linguagens que facilitem produtividade mais do que performance. Sim, daria pra fazer sistemas mais eficientes escrevendo 100% em Rust, ou Go, ou mesmo Java e Elixir, mas até certo ponto é mais fácil treinar pessoas em Python ou Javascript e elas se tornam mais rapidamente produtivas nessas plataformas.</p>
<p>Esse é o motivo de porque não jogamos fora Python ou Javascript: porque taxa homem é mais caro do que custo de CPU por hora. Essas linguagens são mais limitadas como mostrei antes: não servem pra desenvolvimento de coisas com drivers ou protocolos de consenso nem processamento de imagem ou video, mas são ótimos pra fazer código cola mais fácil de usar, importando binários feitos em linguagens mais eficientes como C ou Rust. Portanto podemos usar um Python da vida pra 80% das funcionalidades mais &ldquo;mundanas&rdquo; e usamos C, Go ou Rust pra fazer as partes mais difíceis e integramos com uma extensão usando FFI.</p>
<p>Daí temos o mundo mobile, Android e iOS. Pra aplicativos simples, pense num iFood, AirBnb, até um Facebook ou Instagram, podemos apelar pra frameworks mais pesados mas, de novo, mais produtivos como React Native ou Flutter, que evita que a maioria dos desenvolvedores precisem saber como o baixo nível funciona e produzam mais rápido. A maioria dos smartphones hoje já tem mais de 2 gigabytes de RAM, e dá desperdiçar um pouco.</p>
<p>Claro, se todo mundo usa React Native, todo mundo vai estar limitado nas mesmas coisas que ele suporta. Pra se diferenciar, fazemos a mesma coisa que falei no mundo Web: criamos componentes mais eficientes, de mais baixo nível em Swift pra iOS ou Kotlin pra Android e  integramos nesses frameworks. Quem souber o baixo nível sempre vai ser um profissional mais valioso. É obrigatório? Não. Mas você quer evoluir na carreira e ganhar mais no futuro? Então sim, precisa saber. Sabe tudo que vim mostrando de exemplo na primeira metade do episódio? Sim, aquilo tem que parecer arroz com feijão pra você se considerar um especialista no assunto.</p>
<p>Daí chegamos nas categorias mais hypadas e consideradas mais modernas como data science, machine learning e inteligência artificial. E aqui o mais importante é tirar o máximo proveito do hardware, em particular de conjuntos de instruções como AVX, SSE ou CUDA em GPUs NVIDIA ou OpenCL em GPUs AMD. Pra isso precisamos voltar pro nível de C++. Como expliquei antes, tudo de data science em cima de Python começa em bibliotecas como numpy e scipy, que são feitos em C e Fortran. Machine learning como um Tensorflow começa em coisas como CUDA e C++. Mesmo em javascript, depende de acesso à GPU via WebGL dos navegadores. Não é a linguagem a parte mais importante e sim acesso a esses componentes de computação paralela de vetores e matrizes de dados.</p>
<p>Eu disse no episódio anterior comentando sobre o ChatGPT que no mundo de deep learning, criar modelos como de geração de imagens do Dall-e 2 ou geração de textos como GPT3, custam astronomicamente caro e nenhum de nós vai ser capaz de criar nada perto disso. Só quem tiver acesso a conseguir trabalhar nos centros de pesquisa de uma OpenAI ou Microsoft da vida que vai ter essa oportunidade. Mas pra coisas úteis do dia a dia não precisamos de um GPT. Machine learning com tensorflow e modelos ordens de grandeza mais simples já são úteis pra escopos específicos. Detecção de movimento é um exemplo, pra aplicações de realidade aumentada.</p>
<p>Muitos não pensam nisso, mas algoritmos mais &ldquo;simples&rdquo; como de relevância que é basicamente classificação estatística já é extremamente útil pra coisas como ecommerce conseguirem mostrar &ldquo;produtos recomendados pra você&rdquo;. Ou sistemas como Elastic terem pesquisa fuzzy com relevância, como um mini Google, onde o usuário não precisa escrever os termos exatos, mas mesmo assim ele consegue devolver resultados relevantes. Tudo isso já existe e já usamos em diversos sistemas, de ecommerces a redes sociais. A maioria dos desenvolvedores nem sabe o que é um Elastic ou Solr e acha que vai entender como funciona um ChatGPT. Tá longe, bem longe.</p>
<p>Enfim, eu sei que o episódio de hoje talvez tenha sido denso, mas eram alguns pensamentos aleatórios que tinha na minha cabeça fazia tempo que queria dar dump. Se você é iniciante, não espere compreender tudo que eu disse de uma só vez, pesquise os termos que não conhecia, volte de novo. Quem já tem experiência já sabia tudo que eu falei, talvez dê alguns insights pra complementar. O objetivo não era dizer que essa ou aquela linguagem é a melhor ou a pior.</p>
<p>Cada uma estava no lugar certo na hora certa, não necessariamente foram escolhidas por serem as melhores. E mesmo surgindo coisas novas consideradas melhores, não saímos reescrevendo. Maturidade é sempre mais importante que novidade. 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>linguagens-de-programacao</category><category>programacao</category><category>akitando</category></item><item><title>[Akitando] #135 - ChatGPT Consegue te Substituir? | Entendendo Jobs Assíncronos</title><link>https://akitaonrails.github.io/2023/01/05/akitando-135-chatgpt-consegue-te-substituir-entendendo-jobs-assincronos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/01/05/akitando-135-chatgpt-consegue-te-substituir-entendendo-jobs-assincronos/</guid><pubDate>Thu, 05 Jan 2023 11:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/Yl-hlwhj2B0"
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;Vamos entender mais sobre Jobs Assíncronos, a diferença de usar Promises ou Bull. E um pouco mais sobre o comportamento do famigerado ChatGPT. Vamos ver se ele realmente substitui programadores durante os códigos deste video.&lt;/p&gt;
&lt;h2&gt;Capítulos&lt;span class="hx:absolute hx:-mt-20" id="capítulos"&gt;&lt;/span&gt;
&lt;a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;01:42 - Cap 1 - Form de Upload com ChatGPT | Coisas que você não pensa&lt;/li&gt;
&lt;li&gt;04:55 - Cap 2 - Upload Direto pra Cloud | Porque não gravar local&lt;/li&gt;
&lt;li&gt;09:23 - Cap 3 - Ajustando o Front | ChatGPT é júnior?&lt;/li&gt;
&lt;li&gt;11:46 - Cap 4 - Jogando o Back todo dentro do Controller | Como avaliar Back pesado&lt;/li&gt;
&lt;li&gt;18:26 - Cap 5 - Promises, não: Jobs Async | Controlando seu back&lt;/li&gt;
&lt;li&gt;23:52 - Cap 6 - Exemplos Reais de Jobs Async | Google, Anchor&lt;/li&gt;
&lt;li&gt;26:00 - Cap 7 - Precisa de Jobs Async se Java tem Threads? | Apache Kafka&lt;/li&gt;
&lt;li&gt;29:51 - Cap 8 - Qual o nível do ChatGPT | Errando com Convicção&lt;/li&gt;
&lt;li&gt;33:27 - Cap 9 - Quanto custa um ChatGPT? | IA é inacessível&lt;/li&gt;
&lt;li&gt;36:40 - Cap 10 - ChatGPT vai te substituir? | Talvez?&lt;/li&gt;
&lt;li&gt;38:50 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.bullmq.io/guide/connections"target="_blank" rel="noopener"&gt;https://docs.bullmq.io/guide/connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/bull-ui"target="_blank" rel="noopener"&gt;https://www.npmjs.com/package/bull-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ShaneK/Matador"target="_blank" rel="noopener"&gt;https://github.com/ShaneK/Matador&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/spring-kafka"target="_blank" rel="noopener"&gt;https://www.baeldung.com/spring-kafka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/yE-XLLa2AME?t=642"target="_blank" rel="noopener"&gt;https://youtu.be/yE-XLLa2AME?t=642&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/0uQqMxXoNVs?t=551"target="_blank" rel="noopener"&gt;https://youtu.be/0uQqMxXoNVs?t=551&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jdhao.github.io/2022/08/10/http_benchmark_wrk/"target="_blank" rel="noopener"&gt;https://jdhao.github.io/2022/08/10/http_benchmark_wrk/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Yl-hlwhj2B0"
    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>Vamos entender mais sobre Jobs Assíncronos, a diferença de usar Promises ou Bull. E um pouco mais sobre o comportamento do famigerado ChatGPT. Vamos ver se ele realmente substitui programadores durante os códigos deste video.</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:42 - Cap 1 - Form de Upload com ChatGPT | Coisas que você não pensa</li>
<li>04:55 - Cap 2 - Upload Direto pra Cloud | Porque não gravar local</li>
<li>09:23 - Cap 3 - Ajustando o Front | ChatGPT é júnior?</li>
<li>11:46 - Cap 4 - Jogando o Back todo dentro do Controller | Como avaliar Back pesado</li>
<li>18:26 - Cap 5 - Promises, não: Jobs Async | Controlando seu back</li>
<li>23:52 - Cap 6 - Exemplos Reais de Jobs Async | Google, Anchor</li>
<li>26:00 - Cap 7 - Precisa de Jobs Async se Java tem Threads? | Apache Kafka</li>
<li>29:51 - Cap 8 - Qual o nível do ChatGPT | Errando com Convicção</li>
<li>33:27 - Cap 9 - Quanto custa um ChatGPT? | IA é inacessível</li>
<li>36:40 - Cap 10 - ChatGPT vai te substituir? | Talvez?</li>
<li>38:50 - 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://docs.bullmq.io/guide/connections"target="_blank" rel="noopener">https://docs.bullmq.io/guide/connections</a></li>
<li><a href="https://www.npmjs.com/package/bull-ui"target="_blank" rel="noopener">https://www.npmjs.com/package/bull-ui</a></li>
<li><a href="https://github.com/ShaneK/Matador"target="_blank" rel="noopener">https://github.com/ShaneK/Matador</a></li>
<li><a href="https://www.baeldung.com/spring-kafka"target="_blank" rel="noopener">https://www.baeldung.com/spring-kafka</a></li>
<li><a href="https://youtu.be/yE-XLLa2AME?t=642"target="_blank" rel="noopener">https://youtu.be/yE-XLLa2AME?t=642</a></li>
<li><a href="https://youtu.be/0uQqMxXoNVs?t=551"target="_blank" rel="noopener">https://youtu.be/0uQqMxXoNVs?t=551</a></li>
<li><a href="https://jdhao.github.io/2022/08/10/http_benchmark_wrk/"target="_blank" rel="noopener">https://jdhao.github.io/2022/08/10/http_benchmark_wrk/</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>Todo mundo parece que gostou do episódio de técnicas de otimização web, onde explico porque o aplicativo que você acabou de conseguir terminar seguindo um tutorial ou curso online não se parece nada com um projeto profissional de verdade. Não foi surpreendente como realmente, a maioria dos iniciantes nem imaginava que aquelas técnicas existiam. E quem já tem experiência também confirmou que tudo que falei é o arroz com feijão.</p>
<p>Na mesma semana, em dezembro de 2022, tava todo mundo super hypado com o ChatGPT. Acho que todos assistindo pelo menos ouviram falar dessa inteligência artificial, da OpenAI do Elon Musk, que consegue fazer sua lição de casa, até escrever uma matéria jornalística ou script pra videos de YouTube sem nenhum esforço e, pra nós programadores, o relevante é ele conseguir escrever código direitinho. Muitos YouTubers até prevêem o fim dos programadores. Será? Se você pensou assim também, hoje vou explicar porque está errado!</p>
<p>(&hellip; )</p>
<p>Recapitulando, no episódio de otimização expliquei rapidamente o conceito básico de balanceador de carga, tanto no nível de requisições HTTP com um NGINX ou entre sua aplicação e o banco de dados com pool de conexões. No final falei sobre como deferimos a execução de operações demoradas pra jobs assíncronos. Alguns iniciantes corretamente se questionaram: por que usar um sistema como o Bull, no caso de Node.js, se existem Promises, Async/Await, Child Processes e Worker Threads? Por que não simplesmente colocar a operação mais demorada numa Promise? Vai rodar em paralelo e devolver o HTML pro usuário rápido. Não é a mesma coisa?</p>
<p>Pra responder isso, eu queria mostrar um outro exemplo. E como escrevi este script tarde da noite, realmente tava morrendo de preguiça de escrever código Javascript então resolvi pedir pro meu copiloto, o ChatGPT, pra escrever pra mim.  Eu peço as coisas pra ele sempre tudo em inglês, porque o modelo em inglês é o mais completo e mais bem treinado e é quem dá as melhores respostas. E minha primeira requisição foi: &ldquo;escreve um form multipart pra upload de uma imagem&rdquo; (&ldquo;write a multipart form to upload an image&rdquo;).</p>
<p>Como era de se esperar, a resposta está correta, eis o código. E ele até dá uma explicação sobre o atributo <code>enctype</code> e tudo mais. Isso é bacana mas não exatamente impressionante. Pra uma lição de casa de curso ou faculdade é suficiente. Mas numa aplicação de verdade não fazemos mais 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-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;/upload&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>Select an image to upload:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;image&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload Image<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&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>É o seguinte: esse HTML vai gerar um formulário onde o usuário pode escolher uma imagem no seu computador ou smartphone. Quando clicar em &ldquo;enviar&rdquo; o navegador vai fazer aquele caminho que já expliquei nos episódios de rede: criar o pacote de HTTP request, conectar no servidor que responde com o endpoint &ldquo;/upload&rdquo;. E no servidor o processo que recebe essa requisição fica bloqueado esperando receber esse arquivo.</p>
<p>Qual o objetivo de um endpoint de uma aplicação web? É responder de volta pro usuário o mais rápido possível, de preferência na faixa abaixo de 100 milissegundos. Lembram? Quanto mais tempo sua aplicação leva pra responder deixando o usuário pendurado, menos requisições por segundo seu servidor consegue responder, daí somos obrigados a gastar mais, colocando mais servidores pra suportar todo mundo  acessando  simultaneamente. Portanto, sempre precisamos pensar se o que estamos fazendo contribui pra aumentar ou diminuir esse tempo.</p>
<p>O problema do form desse exemplo é que ele se conecta e vai deixar sua aplicação e o usuário parados esperando o upload da imagem completar. Num pior caso, digamos que o usuário é um fotógrafo e resolveu dar upload numa foto em 8K RAW, ou seja, descomprimido, que pode ocupar entre 60 a 100 megabytes! Digamos que seja 60 megabytes, e ele está fazendo upload do celular, numa conexão 4G ruinzinha que dá upload só a 10 megabits por segundo.</p>
<p>Isso vai custar 480 segundos, ou aproximadamente 8 minutos. Isso é terrível. Esse 1 upload fez um processo do seu servidor ficar simplesmente parado, esperando, e sem poder fazer mais nada por 8 minutos inteiros!</p>
<p>O Node tem capacidade de jogar essa espera pra uma thread separada que não bloqueia a habilidade de conseguir responder outras requisições enquanto isso. Porém, imagine que sua aplicação é feita pra fotógrafos que vão ficar o dia todo subindo imagens gigantes desse tipo, o tempo todo. Tudo tem um limite, e em breve seu servidor vai ficar bem pesado, porque não dá pra ficar criando threads infinitamente. E threads não são de graça.</p>
<p>Muitos não entendem isso. Cada nova thread ocupa espaço em RAM e tempo de processamento pra mudar o estado de uma thread pra outra. Um paliativo é justamente usar o NGINX na frente da aplicação Node, ou Spring Boot, ou Laravel ou seja lá qual framework esteja usando, porque o NGINX foi feito pra lidar com coisas como uploads demorados de forma mais eficiente do que o Node. O usuário primeiro se pendura no NGINX, faz o upload, e só quando termina o NGINX passa o arquivo direto pra sua aplicação.</p>
<p>Só que NGINX, apesar de ordens de grandeza mais rápido que seu Node, também não é infinito, e uma hora vai ficar pesado. Tem diversas formas de lidar com esse problema, mas hoje em dia o melhor custo-benefício é simplesmente não lidar com ele. Especialmente porque tem outro problema ruim que não tem a ver com a espera e sim com onde fica o arquivo: o storage, ou HD local do processo que recebeu o arquivo.</p>
<p>Lembra que numa infraestrutura de verdade subimos vários pequenos servidores embaixo de um balanceador de carga? Significa que se a aplicação receber um arquivo e gravar localmente, só os processos no mesmo servidor vão conseguir ler, os outros não vão ter acesso. Mas aí o usuário navega na sua aplicação e vai querer ver a imagem que acabou de subir, e não vai achar, porque o NGINX elegeu outro servidor pra receber sua requisição e nesse servidor não tem a imagem. Como faz?</p>
<p>Podemos usar um volume compartilhado na rede via NFS, que nem quando você se conecta num servidor de arquivos da sua empresa. É uma solução válida. Mas ao mesmo tempo gostaríamos de ter um backup, caso o servidor de arquivos dê algum pau, não queremos perder arquivos dos usuários. E no fim, vamos acabar mandando esses arquivos pra algum lugar como o serviço AWS S3. Por isso falei que o menos trabalhoso é simplesmente não lidar com arquivos localmente e sim já mandar direto pro AWS S3.</p>
<p>Eu já sabia disso então fiz outra requisição pro ChatGPT. Pedi pra ele: &ldquo;agora escreva outra versão desse formulário mas com suporte a upload direto pra um bucket com url pré-assinada do S3&rdquo;. E como esperado, devolveu um exemplo correto. (&ldquo;now write another version of this form but with support for amazon aws s3&rsquo;s pre-signed direct upload to a bucket&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-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;{{ presignedUrl }}&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;key&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ objectKey }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;acl&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;private&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Credential&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzCredential }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Algorithm&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;AWS4-HMAC-SHA256&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Date&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzDate }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Policy&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ policy }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;X-Amz-Signature&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{ xAmzSignature }}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;image&#34;</span><span class="p">&gt;</span>Select an image to upload:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;image&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;file&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload Image<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&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>Se você é iniciante e pelo menos já tentou programar um upload via web, provavelmente fez igual o ChatGPT na primeira vez: um form multipart dando POST pra um endpoint na sua aplicação. Só que nunca parou pra pensar em coisas como a demora do upload num cenário de verdade e nunca pensou que daria pra dar upload direto pro S3 sem passar pela sua aplicação. O grande truque é o atributo &ldquo;action&rdquo;. No exemplo anterior era um POST pra &ldquo;/upload&rdquo; na sua aplicação. Mas agora é pra URL que estiver nessa variável &ldquo;presignedUrl&rdquo;.</p>
<p>Em seguida tem vários campos escondidos que não chequei na documentação mais atualizada da AWS se é assim mesmo, mas se não for, é parecido. São coisas como o nome da chave onde vai ser gravado esse arquivo no bucket do S3, credenciais e outras configurações. E o resto do formulário é igual. Como falei antes, o  lance é que quando o usuário clicar em &ldquo;submeter&rdquo;, o arquivo não passa por nenhum dos servidores da sua aplicação. Vai dar upload direto pro bucket que você configurou na sua conta antes.</p>
<p>Mas como que gera essa tal de URL pré-assinada? Essa foi minha próxima requisição pro ChatGPT: &ldquo;agora escreva um examplo em Node.js Express de como conseguir a url pré-assinada antes de renderizar o HTML anterior&rdquo;. E ele fez o código de back-end que executa antes de mandar renderizar o HTML que acabei de mostrar. (&ldquo;now write a node.js express example of how to acquire the presignedUrl before rendering the previous HTML&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="kr">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;aws-sdk&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;express&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">S3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/upload&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</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">objectKey</span> <span class="o">=</span> <span class="sb">`uploads/</span><span class="si">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span><span class="si">}</span><span class="sb">/</span><span class="si">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">fileName</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Bucket</span><span class="o">:</span> <span class="s1">&#39;my-bucket&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Key</span><span class="o">:</span> <span class="nx">objectKey</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Expires</span><span class="o">:</span> <span class="mi">60</span><span class="p">,</span> <span class="c1">// URL expires in 60 seconds
</span></span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">s3</span><span class="p">.</span><span class="nx">getSignedUrl</span><span class="p">(</span><span class="s1">&#39;putObject&#39;</span><span class="p">,</span> <span class="nx">params</span><span class="p">,</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">presignedUrl</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">500</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></span><span class="line"><span class="cl">    <span class="c1">// Render form template with presigned URL and other required parameters
</span></span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="s1">&#39;upload_form&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">presignedUrl</span><span class="p">,</span> <span class="nx">objectKey</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>Nada mau. Ele importa bibliotecas como &ldquo;aws-sdk&rdquo;, o &ldquo;express&rdquo; e tudo mais. Note que só pedi esse trecho, mas pra funcionar, ou precisa saber fazer ou pedir pro ChatGPT configurar seu <code>package.json</code> o arquivo <code>app.js</code> e outras configurações. Só esse código sozinho não funciona. Se nunca usou Express, quando abrir o navegador apontando pra &ldquo;localhost:3000/upload&rdquo;, vai cair nesse <code>app.get('/upload')</code>.</p>
<p>Gerar a chave do objeto pode ser feito de diversas formas. Se for antes do usuário escolher qual arquivo vai mandar, vamos precisar criar um nome aleatório usando coisas como uma função de hash, tipo um SHA512 de um número aleatório. Se for via Javascript a partir do formulário, depois que o usuário escolher o arquivo, podemos usar o próprio nome do arquivo. O principal é escolher uma fórmula que gere um string que vai ser único dentro do bucket, pra não sobrescrever o arquivo de outra pessoa.</p>
<p>Então construímos um JSON simples indicando o bucket, a chave que geramos e por quanto tempo a URL de upload vai ficar disponível. Coisa como 60 segundos ou menos, porque se essa URL não tiver expiração, e vazar, qualquer um poderia ficar fazendo uploads arbitrários no seu bucket até acabar o espaço ou te fazer pagar uma fortuna à toa.</p>
<p>Finalmente, usamos o objeto de s3 que carregamos e chamamos essa função <code>getSignedUrl</code> passando os parâmetros que configuramos. A biblioteca do s3 vai chamar a API da AWS e se não der erro, vai devolver uma URL especial, a tal URL pré-assinada. Que passamos pra esse template &ldquo;upload_form&rdquo; que é o HTML que mostrei antes.</p>
<p>Aliás, faltou uma coisa né? Não tem nada mais ruim pro usuário do que dar upload de um arquivo gigante e não saber se tá fazendo upload devagar ou se travou tudo. Então pedi pro ChatGPT refazer o formulário anterior adicionando uma barra de progresso. &quot;<br>
rewrite the html form with pre-signed url for aws s3 but with a visual progress bar as well&quot;</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-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;upload-form&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;{{presignedUrl}}&#34;</span> <span class="na">enctype</span><span class="o">=</span><span class="s">&#34;multipart/form-data&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;key&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{objectKey}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-algorithm&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;AWS4-HMAC-SHA256&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-credential&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{credential}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-date&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{date}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;hidden&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;x-amz-signature&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;{{signature}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;file&#34;</span><span class="p">&gt;</span>Choose file<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;file&#34;</span> <span class="na">required</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;progress-bar-container&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;progress-bar&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Upload<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">progress-bar-container</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">20</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">200</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#ccc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">relative</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></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">progress-bar</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">0</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#4caf50</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">position</span><span class="p">:</span> <span class="kc">absolute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">top</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">left</span><span class="p">:</span> <span class="mi">0</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">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;upload-form&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">progressBar</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;progress-bar&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">form</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;submit&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</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">event</span><span class="p">.</span><span class="nx">preventDefault</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">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">form</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">upload</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;progress&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">lengthComputable</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">percentComplete</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">loaded</span> <span class="o">/</span> <span class="nx">event</span><span class="p">.</span><span class="nx">total</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">progressBar</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">percentComplete</span> <span class="o">*</span> <span class="mi">100</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><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s1">&#39;POST&#39;</span><span class="p">,</span> <span class="nx">form</span><span class="p">.</span><span class="nx">action</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">formData</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">&lt;/</span><span class="nt">script</span><span class="p">&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>E ele fez de novo uma solução simples, mas correta. Primeiro adicionou um <code>div</code> chamado &ldquo;progress-bar-container&rdquo; com outro <code>div</code> dentro chamado &ldquo;progress-bar&rdquo;.  Em seguida criou um CSS simples pra cada um desses elementos. E no fim fez um Javascript que captura o evento de &ldquo;submit&rdquo; do form pra mandar o upload usando a classe XMLHttpRequest, que tem evento pra indicar o progresso do upload. Ele usa isso pra conseguir o percentual que já foi e atualizar o comprimento do <code>div</code>  da barra via CSS.</p>
<p>Honestamente não testei pra ver se funciona. Só de bater o olho parece que sim, mas de novo, é um código super simplificado. Como  não especifiquei que era pra usar Vue.js ou React ou outro framework, ele fez javascript puro. Se seu projeto usa React, não dê copy e paste desse código. Faça um componente seguindo o padrão da sua equipe. É o tipo de código que se encontraria numa resposta de stackoverflow e que não se deve copiar e colar sem ajustar. Note que pesquisei rapidamente no stackoverflow e achei uma resposta com código similar ao que o ChatGPT me deu. No geral não tá errado.</p>
<p>Estão entendendo? Digamos que você é um programador júnior iniciante num projeto e o gerente pedisse, &ldquo;fulano, faz um formulário de upload de imagens no nosso site?&rdquo;. Você faria a mesma coisa que o ChatGPT: aquele primeiro formulário HTML multipart mandando o arquivo direto pra aplicação e gravando no disco local do servidor e pronto. Se a mesma requisição viesse pra mim ou outro programador mais experiente, faríamos perguntas: &ldquo;é pra coisas pesadas? quem vai usar, só pessoal interno ou é pra todo usuário? onde quer gravar essas imagens? podemos mandar direto pro s3? quer mostrar uma barra de progresso pro usuário?&rdquo;</p>
<p>O ChatGPT sabe como fazer tudo isso, mas ele não questiona, faz o mais simples primeiro, e não tá errado. Precisamos saber de antemão que queremos mandar pro S3 via URL pré-assinada, daí ele faz exatamente o que foi pedido. Deixa eu dar outro exemplo pra ficar mais claro e aproveitar pra explicar mais conceitos que iniciantes não conhecem. Agora que temos a imagem no bucket do S3, digamos que nossa aplicação web é tipo um clone de Instagram. Eu gostaria que a imagem fosse cropada em formato quadrado e sempre fosse gravado em formato PNG pra padronizar tudo.</p>
<p>Então pedi exatamente isso pro ChatGPT: &ldquo;agora escreva um exemplo de ação de controller de Node.js Express pra puxar a imagem que deu upload na url pré-assinada do S3, daí use ImageMagick pra cropar num quadrado perfeito, salvar como um PNG e dar upload pra outro bucket do S3&rdquo;. (&ldquo;now write an example of a node.js express controller action to fetch the image that was uploaded to the presigned url in aws s3, then use imagemagick to crop it in a perfect square, save as a PNG and upload to another s3 bucket&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="kr">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;aws-sdk&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;express&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">s3</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">S3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">im</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;imagemagick&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/process-image&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Download image from S3 bucket
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">objectKey</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">objectKey</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">Bucket</span><span class="o">:</span> <span class="s1">&#39;my-bucket&#39;</span><span class="p">,</span> <span class="nx">Key</span><span class="o">:</span> <span class="nx">objectKey</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">imageData</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">s3</span><span class="p">.</span><span class="nx">getObject</span><span class="p">(</span><span class="nx">params</span><span class="p">).</span><span class="nx">promise</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">imageBuffer</span> <span class="o">=</span> <span class="nx">imageData</span><span class="p">.</span><span class="nx">Body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Use ImageMagick to crop the image into a square
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">croppedImageBuffer</span> <span class="o">=</span> <span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</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">im</span><span class="p">.</span><span class="nx">crop</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">srcData</span><span class="o">:</span> <span class="nx">imageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">width</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">height</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">gravity</span><span class="o">:</span> <span class="s2">&#34;Center&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">stdout</span><span class="p">,</span> <span class="nx">stderr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</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="nx">resolve</span><span class="p">(</span><span class="nx">stdout</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Save the cropped image as a PNG
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pngImageBuffer</span> <span class="o">=</span> <span class="kr">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</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">im</span><span class="p">.</span><span class="nx">convert</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="s1">&#39;-strip&#39;</span><span class="p">,</span> <span class="s1">&#39;-interlace&#39;</span><span class="p">,</span> <span class="s1">&#39;Plane&#39;</span><span class="p">,</span> <span class="s1">&#39;-quality&#39;</span><span class="p">,</span> <span class="s1">&#39;100&#39;</span><span class="p">,</span> <span class="s1">&#39;-colorspace&#39;</span><span class="p">,</span> <span class="s1">&#39;sRGB&#39;</span><span class="p">,</span> <span class="s1">&#39;-define&#39;</span><span class="p">,</span> <span class="s1">&#39;png:compression-level=9&#39;</span><span class="p">,</span> <span class="s1">&#39;png:-&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nx">croppedImageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">stdout</span><span class="p">,</span> <span class="nx">stderr</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nx">reject</span><span class="p">(</span><span class="nx">err</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="nx">resolve</span><span class="p">(</span><span class="nx">stdout</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><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Upload the cropped image to another S3 bucket
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">targetBucket</span> <span class="o">=</span> <span class="s1">&#39;cropped-images&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">targetObjectKey</span> <span class="o">=</span> <span class="nx">objectKey</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s1">&#39;uploads/&#39;</span><span class="p">,</span> <span class="s1">&#39;cropped/&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">await</span> <span class="nx">s3</span><span class="p">.</span><span class="nx">putObject</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Bucket</span><span class="o">:</span> <span class="nx">targetBucket</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Key</span><span class="o">:</span> <span class="nx">targetObjectKey</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Body</span><span class="o">:</span> <span class="nx">pngImageBuffer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">ContentType</span><span class="o">:</span> <span class="s1">&#39;image/png&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}).</span><span class="nx">promise</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">sendStatus</span><span class="p">(</span><span class="mi">500</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>De novo, tá certinho. No começo ele carrega bibliotecas que precisa saber carregar no <code>package.json</code>. E cria um endpoint de POST chamado &ldquo;/process-image&rdquo; que poderia ser chamado pelo Javascript do formulário, pelo objeto de XMLHTTPRequest quando detectar que o upload terminou, por exemplo. Esse código faz o seguinte, primeiro tem que saber a URL completa do arquivo que acabou de subir no bucket do S3, e configurar os parâmetros pra esse bucket.</p>
<p>Ele começa chamando o método <code>getObject</code> do objeto do s3. Não estou familiarizado com a biblioteca <code>aws-sdk</code> pra Javascript, mas parece que  devolve uma Promise e corretamente dá <code>await</code>. Pra quem é de outras linguagens que não tem Promises, não é a mesma coisa, mas é como se estivesse colocando esse <code>s3.getObject</code> dentro de uma nova Thread e esse <code>await</code> é como se fosse <code>join</code>, ou seja, pra esperar a thread terminar antes de continuar.</p>
<p>Finalmente, puxamos o conteúdo binário do arquivo nessa variável <code>imageBuffer</code>. Relembrando, se a imagem tinha 60 megabytes, agora tem 60 megabytes na memória do interpretador V8 desse processo. Na sequência, ele faz o código pra fazer crop, ou seja, pra recortar a imagem num quadrado usando a biblioteca ImageMagick nesse &ldquo;im&rdquo; chamando o método &ldquo;crop&rdquo; direitinho.</p>
<p>Ele assumiu que eu queria uma imagem de 200 por 200 pixels. Tá ok, não tinha dito nenhum outro número, mas de novo, por isso que não pode copiar e colar às cegas. Se o amador só copiasse sem ver e deixasse isso passar, agora aquela imagem de altíssima qualidade em 8K RAW do fotógrafo, se transformou numa imagem porcaria minúscula. Se for pra ser fixo eu não cortaria nada menor que uns 1000 por 1000 pixels.</p>
<p>Esse pattern que ele faz de englobar tudo numa nova Promise e imeditamente chamar await, do ponto de vista só desse código, seria a mesma coisa que não ter isso. Desse jeito (muda imagem). Esse código vai esperar, await, até o crop terminar. Pela forma como o event loop do Node.js funciona, suponho que a vantagem é que fazendo assim, a única thread do event loop ficaria bloqueada e o Node não iria conseguir fazer mais nada até o crop do ImageMagick terminar.</p>
<p>Mas colocando numa Promise e dando await, ele delega pro sistema operacional a operação de esperar o I/O (ler arquivo, gravar arquivo), e libera o event loop pra fazer outra coisa, como responder outras requisições de outros usuário. Quando a operação cara de I/O retorna, a thread continua depois do await como se nada tivesse acontecido. Essa é a grande vantagem de se usar Node em primeiro lugar, o event loop e o suporte a Promises.</p>
<p>Vou voltar nisso mais pra frente. Em seguida temos um bloco de código que chama direto o programa &ldquo;convert&rdquo;. Mesma coisa, Promise com await. E no final, o último bloco é chamando a biblioteca do S3 pra fazer o upload do binário cropado e reformatado como PNG pro bucket do S3.</p>
<p>Agora vamos entender o problema. Quer dizer, não é nenhum problema se só você for usar essa aplicação, ou mesmo se for uma parte administrativa de um site onde só meia dúzia de pessoas for usar. Mas se for pra todo usuário do site e tiver centenas ou milhares de usuários, isso vai pesar rapidamente e exigir que se faça deploy de muitos servidores, tornando a infraestrutura super cara.</p>
<p>Primeiro de tudo. Quando fazemos download da imagem do S3 e guardamos nessa variável <code>imageData</code> e a imagem tem 60MB, de cara estamos consumindo 60MB de RAM do servidor. Digamos que 100 usuários estão fazendo upload de imagens mais ou menos ao mesmo tempo. Seria 6 GB de RAM sendo ocupado no servidor web. Pra processar imagens, realmente vai usar muita RAM, o problema é compartilhar isso no mesmo servidor que precisa responder requisições web o mais rápido possível.</p>
<p>Além disso temos duas operações de rede pra s3, de download e upload, baixando uns 60MB e subindo uma imagem menor, mas é um tráfego que não podemos ignorar. De novo, se tiver 100 usuários simultaneamente subindo imagens de alta qualidade, naquele minuto seu servidor tá precisando gastar uns 6 gigabytes de tráfego só pra baixar do S3. &ldquo;Ah, mas é porque você mandou fazer direct upload pro s3, se estivesse num storage local estaria ocupando menos banda&rdquo;</p>
<p>Sim, é verdade e eis um exemplo de que não tem resposta certa. Se você é um serviço grande onde uma das partes principais do que oferece aos usuários é armazenamento de arquivos grandes, talvez  queira ter essa infraestrutura sob seu controle. Vai ter gente dedicada, com servidores no data center dedicados só pra gerenciar armazenamento, daí não precisa usar tanto S3.</p>
<p>Por outro lado, pode ser uma empresa no começo, que ainda não tem  tantos servidores, rede e gente de infra dedicada só pra armazenamento. Talvez o S3 seja um custo-benefício melhor. Nenhuma das duas respostas é errada, depende de quem você é, qual seu nível de serviço, seu orçamento, quanto cobra e qual sua receita.</p>
<p>Digamos que somos uma empresa menor, e pra gente S3 faz sentido, então o código seria mais ou menos como nesse exemplo. Mas além do armazenamento temos esses dois blocos pesados de processamento, que é calcular o cropping e converter a image pra outro formato. Lembra que falei que na variável <code>imageData</code> tem 60 megabytes do binário da imagem original? Nessa parte aqui a variável <code>croppedImageBuffer</code> vai ter o binário da imagem recortada, digamos que seja uns 50 Megabytes.</p>
<p>E nessa outra parte aqui onde convertemos pra PNG faz de conta que o PNG ficou com uns 5 Megabytes de tamanho, porque é comprimido. Então, antes dessa função acabar, essas 3 variáveis estão ocupando 60 + 50 + 5, ou seja, 115 megabytes de memória. Se tiver 10 usuários pendurados fazendo upload de imagens semelhantes, estamos ocupando uns 11 giga e meio de RAM dos servidores só nesse processo.</p>
<p>Estão entendendo? O problema não é só conseguir rodar essas tarefas em paralelo pra liberar processamento pro Node conseguir atender mais requisições. O problema é que quanto mais gente usar o sistema, mais rápido vai acabar os recursos das máquinas web e em breve não vai ter RAM pra atender mais gente chegando. O objetivo é responder rápido, mas fazendo tudo isso, é impossível devolver a resposta HTML pro usuário na faixa de menos de 100 milissegundos.</p>
<p>Por isso que pra casos assim, o correto é jogar esse processamento pra fora da aplicação web. Isso não vai diminuir nem a carga de processamento e nem o uso de RAM, mas vamos conseguir segmentar, separar os recursos dedicados só pra web e os recursos dedicados pra processar as imagens. No caso do Node.js podemos usar a biblioteca Bull, como mencionei no último episódio.</p>
<p>Pedi pro ChatGPT: &ldquo;escreva o mesmo código mas sem usar Promises e na realidade deferir as tarefas de download da imagem, cropping, salvar em PNG e fazer upload de volta pra outro bucket como um job assíncrono implementado como um worker de Bull. (&ldquo;write the same code but without using Promises and actually deferring the tasks for downloading the image, cropping the image, saving as PNG and uploading back to a bucket to an asynchronous job implemented as a Bull worker&rdquo;)</p>
<p>Sabendo perguntar em detalhes exatamente o que queremos, o ChatGPT responde direitinho. Ele não tem como adivinhar qual é a melhor solução pra você, por isso sempre vai dar a resposta mais simples. Se eu não soubesse que Bull existe, o ChatGPT não ia me dizer. Mas agora o código começa com ele carregando a biblioteca do Bull e instanciando uma nova fila chamada &ldquo;image-processing&rdquo;. Em seguida já adiciona uma nova tarefa na fila com a chave do objeto no bucket, que é a imagem original que o usuário fez upload. Esse é o trecho que iria no controller do Express e vamos tirar fora.</p>
<p>Eu não pedi pro ChatGPT reescrever aquele trecho <code>app.post(&quot;/upload&quot;)</code> do Express, mas seria arrancar fora todo aquele código do processamento de imagens e simplesmente colocar essa linha de registrar uma nova tarefa na Fila e rapidamente devolver OK pro usuário parar de esperar na fila. Caso a página web que iria devolver tivesse um preview da imagem que subiu, não teríamos como mostrar porque ainda não processamos, mas aí é só colocar uma imagem genérica com uma mensagem de &ldquo;aguarde, estamos processando&rdquo;, e colocar um WebSocket ou algo assim pra puxar a imagem processada quando ficar disponível depois.</p>
<p>Voltando pro Worker de Bull, é a mesma coisa de antes, o código que acabamos de apagar do endpoint de <code>app.post</code>. Esse worker eu instalaria num servidor separado só pra workers. A vantagem é que  posso controlar quantos tarefas quero processar simultaneamente. Digamos que meu servidor não tenha 11 gigabytes de RAM que seria necessário pra rodar 100 tarefas simultâneas de imagens grandes. Só tenho 4 gigabytes, então só dá pra rodar umas 30 tarefas ou menos. Mas tudo bem. Vai demorar mais, mas como o usuário não tá pendurado esperando e nem estou congestionando os servidores web, não tem problema se demorar o triplo de tempo.</p>
<p>Além disso tem outra razão pra jogar essas coisas pesadas num job assíncrono em vez de tentar rodar com Promises ou Threads. Digamos que tenha um bug na biblioteca do ImageMagick ou do S3 e dê um crash na hora de processar por alguma razão. Como faço pra rodar de novo essa tarefa? Existem bibliotecas como o &ldquo;async-await-retry&rdquo;, mas imagina que seja um erro sistêmico, e tinha 100 usuários subindo imagens e por um erro qualquer, as 100 tarefas comecem a dar pau, e 100 vezes o sistema tente dar retry e fique retentando? Meu servidor vai morrer retentando à toa assim. Eu preciso ter controle sobre regras de retry.</p>
<p>Numa fila como Bull, se os workers começam a dar pau, posso configurar pra não dar retry e só marcar erro. Se descobrimos que foi algo sistêmico, podemos corrigir o bug e depois que estiver tudo corrigido, mandar as tarefas pendentes na fila rodar, tudo sob controle. Na documentação do Bull ele explica como podemos configurar retries automáticos, por exemplo, aqui ele diz pra tentar 3 vezes caso dê problema e com um tempo de backoff exponencial que começa em 1 segundo na primeira tentativa, 2 segundos pra segunda, 4 segundos pra terceira. Nunca é bom retentar imediatamente porque muito provavelmente vai dar problema igual.</p>
<p>E o Bull tem um monte de funcionalidades importantes como Gracefull shutdown. Digamos que modificamos o código dos workers e agora precisamos atualizar nos servidores. Não pode simplesmente desligar tudo e atualizar. Não queremos crashear jobs que estão rodando. Normalmente queremos que o worker saiba que tem uma atualização e reinicie só depois que terminar de processar o job que estava em andamento.</p>
<p>Se temos vários servidores de Bull, cada um com vários processos, ele vai desligando, atualizando e reiniciando um a um só depois que terminar os jobs. Assim nenhum job vai corromper. É que nem dar shutdown normal num Windows em vez de apertar o botão de força e desligar forçado, que nunca é bom.</p>
<p>E sendo um sistema com fila e workers separados, existem projetos como o Matador, que é uma interface web pra monitorar os trabalhos na fila do Bull. Assim um administrador ou devops pode checar visualmente se os workers estão se comportando como deveriam e facilitar conseguir identificar se tudo está correndo bem.</p>
<p>Entendam, colocar um Bull e jogar aquele processamento pesado de imagens em outros servidores não diminui a quantidade de recursos sendo gastos. Ele serve pra tornar esse processamento mais gerenciável e previsível. Tudo que roda no lado dos servidores web é pra ser leve e rápido. Tudo que é pesado, como nesse exemplo de imagens, jogamos em jobs em outros servidores que só rodam jobs.</p>
<p>E a comunicação entre os dois é feita via uma Fila. No caso do Bull ele usa um módulo chamado <code>ioredis</code> que usa Redis como servidor de fila. O correto na hora de instanciar a fila é indicar o servidor do Redis na conexão e quando subimos os workers, apontamos pro mesmo Redis pra escutarem quando chegar novos jobs, como estou mostrando neste exemplo que tem na própria documentação.</p>
<p>Se não ficou claro vou repetir: quando precisamos executar alguma tarefa mais pesada, faz de conta, mandar uma linha nova de log pra um servidor de log, enviar uma notificação, coisas que não duram muitos segundos ou até minutos, é possível que Promises no caso de Javascript, ou async/await num DotNet ou mesmo Threads como num Java, façam sentido. O problema é quando a duração desse processamento é longo e, pior, varia bastante. Nesse caso o tempo vai variar dependendo do tamanho das imagens. Fica imprevisível demais pra controlar misturado com a requisição web.</p>
<p>O certo é mandar pra jobs assíncronos. Isso me lembrou um caso. Esses dias eu estava configurando minha conta de Google Workspace, que são os planos pagos do Google pra serviços como Gmail. Eu queria habilitar a opção de conseguir mudar o tema do meu Gmail, isso tava desligado sei lá porque. Fui no admin, nessa tela e liguei. Mas mesmo recarregando meu Gmail, essa opção não aparecia. Olha só, em outra conta de Google Workspace que é da minha empresa, já tá ligado e em configurações aparece essa opção aqui tão vendo? Por que na outra conta não apareceu?</p>
<p>Porque quando habilitei no admin ele mandou isso pra uma fila que tem um tempo super longo pros workers pegarem. Pode ser porque tem tanta operação em fila dos milhões de usuários que usam a plataforma, que precisa de horas pra executar meu pedido. Você imaginaria que deveria ser imediato, mas eles escolheram separar assim porque deve ser pesado por N motivos que não temos acesso de saber.</p>
<p>Veja o YouTube. Toda vez que termino um episódio, faço upload do arquivo de video. É um arquivo de dezenas de gigabytes, leva vários minutos pra subir. E depois que sobe precisa fazer checagens de copyright, gerar versões com resoluções menores, sincronizar cópias em CDN e assim por diante. Enquanto isso não acontece, eu fico vendo essa mensagem indicando que ainda tá processando. Só depois que termina que consigo fazer coisas como adicionar cards.</p>
<p>Veja no anchor.fm, que é onde eu subo o arquivo mp3 pra publicar no Spotify ou Deezer. Eu faço upload e depois ele manda pra alguma fila, enquanto isso no meu front-end só indica que tá &ldquo;processando&rdquo;. Meu navegador não tá pendurado no servidor deles. Provavelmente tem um javascript que de tempos em tempos checa se já terminou, ou tem um web socket aberto.</p>
<p>Qual &ldquo;a&rdquo; melhor forma de fazer tudo isso? Não tem. Depende de quantos servidores, de quais tamanhos você pode pagar. O software tem que ser feito pra organizar os recursos disponíveis de forma que fique tudo menos difícil de gerenciar. Não quer dizer necessariamente mais performance ou rodar tudo em paralelo. Controle significa que quando alguma coisa dá erro eu tenha a oportunidade de pausar, checar, consertar e mandar rodar de novo, e pra isso soluções usando fila e workers como o Bull de Node.js faz sentido.</p>
<p>Muitos pensam que porque Node.js tem eventloop não precisa de jobs assíncronos, só Promises. Espero que tenham entendido que são funcionalidades diferentes pra casos diferentes. Mesma coisa pessoal de Java, que tem threads nativas e poderiam achar que não precisam de jobs assíncronos. Mas um dos sistemas de fila mais reconhecidos é justamente o Apache Kafka, que é feito em Java. Aproveitei pra pedir pro ChatGPT reescrever o worker de Bull em Spring Boot (&ldquo;To process an image that was uploaded to an Amazon S3 bucket using a pre-signed URL in a Spring Boot app, using a Kafka queue to defer the tasks to a worker, you can use the following code&rdquo;)</p>
<p>Tecnicamente, o boilerplate, a parte burocrática de configuração de Kafka exige adicionar o pacote org.springframework.kafka no arquivo <code>pom.xml</code> do projeto que é mais ou menos o equivalente ao <code>package.json</code> de Node. Daí teria que usar o script <code>kafka-topics.sh</code> pra criar os tópicos da fila. Finalmente, pra aplicação Spring Boot conseguir criar mensagens pra mandar pra fila, precisa configurar um <code>ProducerFactory</code> que é o responsável pela estratégia de como criar instâncias de Producers de Kafka.</p>
<p>O código de exemplo que o ChatGPT gerou instancia um KafkaTemplate mas ele deveria injetar um ProducerFactory dentro, que foi omitido. Como o video não é pra ser um tutorial de Spring Boot, depois leiam a documentação, mas só pra explicar que, de novo, o ChatGPT não me deu tudo mastigado. Eu sei que falta coisa porque já li a documentação. Se você nunca usou Kafka na vida e achou que o ChatGPT ia te dar tudo mastigado, pensou errado.</p>
<p>Em seguida ele cria um endpoint web chamado &ldquo;/process-image&rdquo; como tínhamos feito com o Express. No Spring Boot isso é configurado com uma annotation que é essa linha que começa com arroba pra decorar a função logo abaixo. E essa função responde super rápido porque a única coisa que faz é pegar a instância de KafkaTemplate e enviar a mensagem do job pra fila.</p>
<p>A classe de Worker abaixo também é configurado com outra annotation pra dizer que é o responsável por mensagens enviadas pro tópico &ldquo;image-processing&rdquo;. Nessa classe tem a função <code>processImage</code> que vai fazer a mesma coisa que o worker de Bull, só que em Java. Instanciar bibliotecas de S3 e ImageMagick nas versões de Java, download da imagem num ByteArray em memória, cropar e converter em PNG e fazer upload. Em termos de tempo de processamente, como o grosso depende da conexão remota com o S3 e de chamar o mesmo ImageMagick, tanto faz ser em Javascript ou em Java, vai levar mais ou menos o mesmo tempo pra rodar.</p>
<p>Pra quem gosta de perder tempo discutindo benchmarks de linguagens em subreddit, entenda esse cenário. Veja esses dois códigos lado a lado. A linguagem meio que não importa, porque o grosso do trabalho não é feito nem em Javascript e nem em Java. O grosso é o ImageMagick e o S3. Nas dus versões, vão fazer fork e subir um processo separado da ferramenta <code>convert</code> que vem no pacote do ImageMagick, que é feito em C.</p>
<p>Ele é quem vai fazer o trabalho sujo. E sim, eu não precisava ter usado ImageMagick, poderia ter escolhido uma biblioteca feita em puro Javascript e outra feita em puro Java. Mas não vejo porque, ImageMagick é a ferramenta mais madura e mais reconhecida pra trabalhar imagens. É literalmente um Photoshop em linha de comando que faz praticamente tudo. Pra que vou reinventar a roda? E mesmo se eu reinventasse a roda, não seria uma diferença muito grande de qualquer jeito.</p>
<p>Novamente, o objetivo de hoje foi demonstrar como um código feito como exercício simples, que só você vai usar sozinho na sua própria máquina é de um jeito. Mas como as mesmas funcionalidades num projeto de verdade ficam completamente diferentes. Na sua própria máquina, não precisa de Bull nem de Kafka. Basta subir uma Promise, ou child process que é fork de processos, ou worker thread que sobe uma thread no mesmo processo pra rodar em paralelo. Mesmo se a imagem for pesada, só você tá usando, mesmo se demorar alguns minutos e usar muita RAM e cair pro swap que é mais lento, não tem problema. O problema só aparece se estiver exposto na internet, aí fodeu.</p>
<p>E agora sobre coisas como o ChatGPT ou GitHub Copilot que não mostrei nesse video mas já usei. Acho excelentes. Eu sei exatamente o que quero, eu sei que não quero uma solução simples com upload direto na aplicação web. Eu sei que existe a técnica de URL pré-assinada, então o ChatGPT me obedece. Eu sei que o processamento de imagens, download e upload pro S3 é demorado, por isso eu soube pedir pro ChatGPT fazer o código levando isso em consideração e usar Bull e Kafka.</p>
<p>Além disso eu sei que nos exemplos de código que o ChatGPT me devolveu não tem a configuração de Redis do Bull nem do ProducerFactory de Kafka. Eu sei onde configurar e os detalhes que não lembro. Eu sei achar a documentação e completar. Também poderia pedir. &ldquo;Ow, ChatGPT, faltou a configuração de ProducerFactory no KafkaTemplate, pode completar pra mim?&rdquo;. Entenderam? Vocês assistindo que são iniciantes e ainda não trabalharam em projetos de verdade: já tinham ouvido falar dessas coisas?</p>
<p>Não estou dizendo que ninguém ensina, mas entenda: os cursos mais básicos querem que vocês tenham a falsa ilusão que programar é simples e qualquer um pode fazer. E te dão um passo a passo pra conseguir fazer um front-end de instagram, com upload de imagem, e quando conseguir ver a imagem postada na sua aplicação local, fica feliz. E realmente, a primeira vez que consegue é bacana mesmo. Mas acho que falta dizer que em cima disso tem esse tanto de coisas que mostrei nesse video e no anterior.</p>
<p>Uma inteligência artificial consegue facilmente substituir um programador júnior inexperiente como puderam ver aqui. Porém, ele ainda não consegue substituir um programador experiente. Eu vou ficar impressionado quando pedir pra ele fazer alguma coisa e em vez de me dar um código simples, começar a me devolver perguntas pertinentes, como quantos usuários eu espero que o sistema tenha que aguentar, ou o volume dos uploads. Um programador experiente não sai codando à toa de qualquer jeito.</p>
<p>O ChatGPT é um serviçal bem burrinho que dá respostas erradas ou incompletas com muita convicção. Por exemplo, num video que eu tava assistindo, do Joseph Carlson, ele pediu pro irmão dele, que é médico de verdade, fazer uma pergunta pro ChatGPT, no caso foi &ldquo;qual a condição mais comum que provavelmente se origina na coróida e que afeta homens jovens?&rdquo; E o ChatGPT faz um textão explicando que é neovascularização coroidal e segue explicando o que é isso. E o médico responde: &ldquo;nada mau, mas está incorreto. Seria correto se estivéssemos falando de pessoas idosas. A resposta correta seria retinopatia serosa, e é por isso que fazer auto diagnóstico via Google  não funciona&rdquo;.</p>
<p>Claro, esse caso em particular, com um pouco mais de treino o ChatGPT deve conseguir responder correto da próxima vez, mas o ponto é que as respostas deles não são garantidamente corretas o tempo todo e quando são erradas, ele não tem noção que tá errado. A resposta é sempre verbalizada de uma forma bem assertiva. Se você não for também especialista no assunto, vai acreditar e nesse caso poderia se auto-diagnosticar errado e acabar tendo problemas graves. Mas isso já acontece hoje com Google também.</p>
<p>Pra amadores, o ChatGPT pode ser usado como uma ferramenta de estudos, e nada do que ele soltar deve ser usado no mundo real sem consultar um especialista, porque você não tem o conhecimento e nem a experiência pra saber se veio errado ou incompleto. Pra especialistas de verdade, o ChatGPT é excelente pra fazer as tarefas mundanas que nós sabemos fazer, mas não vale nossa taxa hora, como os códigos que mostrei aqui. Eu sei avaliar se o código serve ou não, por isso pra mim o ChatGPT funciona bem. Quando ele não sabe responder, ou me responde errado, eu sei como consertar.</p>
<p>O banco de dados, o modelo dele, é realmente impressionante. O que mais me impressiona não é funcionar. Eu sei que funciona. As teorias pra isso existem e vem sendo evoluídas e desenvolvidas faz décadas. Mas pra fazer modelos desse tipo custa extremamente caro. Eu estava assistindo o canal Coldfusion que gosto muito e recomendo, e no video ele menciona o que o Sam Altman, presidente da OpenAI  falou.</p>
<p>Hoje temos milhões de pessoas usando o ChatGPT pela novidade. Mas ele não escala pra ordem de bilhões de pessoas, que seria a escala de redes sociais como Facebook ou a escala que Google, Apple ou Microsoft conseguem atingir com seus produtos. O custo seria inviável. Segundo o Altman, uma resposta do ChatGPT custa de 10 a 100 vezes o custo de pesquisa no Google. Ainda precisa otimizar 100 vezes essa tecnologia pra chegar num ponto onde vai ser economicamente viável todo mundo usar no dia-a-dia, como smartphones ou um Google.</p>
<p>E claro, em alguns anos isso vai acontecer. Hoje ainda não é esse dia. Pode levar 10 anos, 20 anos. E isso pra otimizar custos. Pra chegar num ponto onde posso confiar quase 100% a ponto de não precisar de especialistas humanos, isso realmente não vejo acontecendo tão cedo. Eu vejo sim, sendo um excelente auxiliar pra nós especialistas, e num espectro menor, ajudando não especialistas com perguntas simples do dia a dia, algo um pouco melhor que as porcarias de Alexa, Siri ou Cortana.</p>
<p>Pra nós programadores tem outro aspecto. Muitos iniciantes acham que deveriam estudar tópicos de inteligência artificial porque num futuro próximo todo mundo vai estar programando inteligências artificiais, mas sinto te informar que isso não vai acontecer como você pensa. Sim, se você tem nível e talento pra ser contratado pra times de pesquisa da OpenAI, da Microsoft, da Apple, do Google, aí você teria chances de trabalhar na construção dessas inteligências. Mas fora dessas empresas gigantes, não vai rolar.</p>
<p>O maior problema não são os algoritmos. O problema é o treinamento. Pra conseguir fazer um Dall-e 2, Midjourney ou Stable Diffusion gerar fotos e desenhos artísticos realistas, precisou treinar cada um desses modelos com milhões de imagens diferentes por muito e muito tempo. Estamos falando em escala de petabytes de dados, milhares ou milhões de horas em dezenas ou centenas de servidores super caros. Data centers dedicados com o supra sumo das placas de video NVIDIA. Ou seja, você, no seu PCzinho ou notebook, jamais vai conseguir produzir um modelo de verdade que vá além de um  brinquedo.</p>
<p>Modelos sérios do tamanho de um ChatGPT custam milhões de dólares pra produzir. Você não vai ter acesso a esse tipo de investimento e infraestrutura. Se alguém de uma tech startup desconhecida vier te convidar pra construir um modelo desses, já sabemos que ele mora em Nárnia, enxerga unicórnios e tem amigos imaginários. Não vai acontecer.</p>
<p>Na prática, quase todos nós, programadores, no máximo vamos poder usar modelos prontos dos outros, como da OpenAI. Se já tentou usar o ChatGPT todo dia, já deve ter visto como do nada uma resposta engasga e dá erro de conexão, ou como o site todo às vezes fica fora do ar por alguns segundos ou minutos. Exatamente porque nesta versão de agora, ainda não escala. Precisa sair uma versão 2.0 com mais garantias pra gente poder investir em produtos que tirem proveito desse modelo.</p>
<p>Mas a pergunta que todo mundo tem na cabeça é &ldquo;o ChatGPT vai substituir programadores?&rdquo; E a resposta é simples: sim, vai substituir os ruins, os preguiçosos, os falsos sêniors e infelizmente alguns dos iniciantes. Porque muitas tarefas mundanas de código que um júnior pegaria, eu como sênior posso usar o ChatGPT pra fazer mais rápido e, mais importante, com zero reclamação, mais rápido e com menos erros. Agora, substituir um bom sênior de verdade? Eu diria que precisa pelo menos de uns bons 50 anos antes de cogitar algo assim. Tá longe, bem longe.</p>
<p>Eu estou satisfeito o suficiente com ferramentas como o ChatGPT ou Copilot do GitHub. Eles são mais eficientes e mais rápidos do que dar alt-tab e procurar o que preciso no Google ou Stackoverflow. Eu sei quando ele me devolve bobagem e consigo ignorar. E sei quando tá incompleto. Ao mesmo tempo, é bom pra fazer coisas mudanas e repetitivas, como testes unitários por exemplo. Ou me devolver exemplos de códigos que estou com preguiça de procurar no Github. Realmente, é como se eu tivesse um serviçal que procura no Google pra mim.</p>
<p>Muitos YouTubers tem feito video dizendo &ldquo;olha só como o ChatGPT escreveu o script desse video&rdquo;. Eu tentei, mas tava dando tanto trabalho que foi mais r;apido eu mesmo escrever. Sinto muito, na atual versão, o ChatGPT é incapaz de escrever um script pra este canal sem muita intervenção. Eu concluo que os canais dos outros são bem superficiais e simplórios se um script gerado pelo ChatGPT é capaz de enganar a audiência. No meu caso não rolou. O ChatGPT é excelente pra encher linguiça de forma convincente, mas falar direto ao ponto, ele tem muita dificuldade.</p>
<p>No final a conclusão é simples: seu grau de empolgação com o ChatGPT é inversamente proporcional ao seu nível de conhecimento. Se for baixo, vai ficar exageradamente empolgado, se for alto, vai achar útil, com ressalvas. É a diferença de quem vê um truque de mágica pela primeira vez, e de quem já sabe como o truque funciona. Pra quem não quer ser substituído por uma IA mande dúvidas 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>sistemas-distribuidos</category><category>backend</category><category>llms</category><category>akitando</category></item><item><title>[Akitando] #134 - Top 3 Distros Linux | Qual Recomendo?</title><link>https://akitaonrails.github.io/2022/12/23/akitando-134-top-3-distros-linux-qual-recomendo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/12/23/akitando-134-top-3-distros-linux-qual-recomendo/</guid><pubDate>Fri, 23 Dec 2022 09:18:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/unpJgmjTLEg"
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;Como video de fim de 2022, vou participar de uma corrente chamada &amp;ldquo;Top 3 Distros Linux&amp;rdquo;. Vou eleger 3 distros Linux que gosto e depois falar sobre distros Linux em geral.&lt;/p&gt;
&lt;p&gt;E aqui vão os &amp;ldquo;summon&amp;rdquo; pra participar da corrente:&lt;/p&gt;
&lt;p&gt;@HackerVilela
@pikuma
@ManualdoCodigo&lt;/p&gt;
&lt;h2&gt;Conteúdo&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:49 - As Regras da Corrente dos Top 3&lt;/li&gt;
&lt;li&gt;02:04 - Número 3&lt;/li&gt;
&lt;li&gt;03:28 - Número 2&lt;/li&gt;
&lt;li&gt;05:38 - Número 1!&lt;/li&gt;
&lt;li&gt;08:34 - Como eu enxergo Distros Linux&lt;/li&gt;
&lt;li&gt;12:56 - Recomendação pra Iniciantes?&lt;/li&gt;
&lt;li&gt;15:11 - Summon 1: Vitor&lt;/li&gt;
&lt;li&gt;15:55 - Summon 2: Pikuma&lt;/li&gt;
&lt;li&gt;16:59 - Summon 3: Douglas&lt;/li&gt;
&lt;li&gt;17:56 - Bloopers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://diolinux.com.br/video/top-3-distro-linux.html"target="_blank" rel="noopener"&gt;https://diolinux.com.br/video/top-3-distro-linux.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://toastytech.com/guis/mkde.html"target="_blank" rel="noopener"&gt;http://toastytech.com/guis/mkde.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://batocera.org/"target="_blank" rel="noopener"&gt;https://batocera.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Mandriva_Linux"target="_blank" rel="noopener"&gt;https://en.wikipedia.org/wiki/Mandriva_Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.redhat.com/en/about/press-releases/press-redhat42"target="_blank" rel="noopener"&gt;https://www.redhat.com/en/about/press-releases/press-redhat42&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/unpJgmjTLEg"
    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>Como video de fim de 2022, vou participar de uma corrente chamada &ldquo;Top 3 Distros Linux&rdquo;. Vou eleger 3 distros Linux que gosto e depois falar sobre distros Linux em geral.</p>
<p>E aqui vão os &ldquo;summon&rdquo; pra participar da corrente:</p>
<p>@HackerVilela
@pikuma
@ManualdoCodigo</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 - Intro</li>
<li>00:49 - As Regras da Corrente dos Top 3</li>
<li>02:04 - Número 3</li>
<li>03:28 - Número 2</li>
<li>05:38 - Número 1!</li>
<li>08:34 - Como eu enxergo Distros Linux</li>
<li>12:56 - Recomendação pra Iniciantes?</li>
<li>15:11 - Summon 1: Vitor</li>
<li>15:55 - Summon 2: Pikuma</li>
<li>16:59 - Summon 3: Douglas</li>
<li>17:56 - 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://diolinux.com.br/video/top-3-distro-linux.html"target="_blank" rel="noopener">https://diolinux.com.br/video/top-3-distro-linux.html</a></li>
<li><a href="http://toastytech.com/guis/mkde.html"target="_blank" rel="noopener">http://toastytech.com/guis/mkde.html</a></li>
<li><a href="https://batocera.org/"target="_blank" rel="noopener">https://batocera.org/</a></li>
<li><a href="https://en.wikipedia.org/wiki/Mandriva_Linux"target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Mandriva_Linux</a></li>
<li><a href="https://www.redhat.com/en/about/press-releases/press-redhat42"target="_blank" rel="noopener">https://www.redhat.com/en/about/press-releases/press-redhat42</a></li>
<li></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>Normalmente eu não participaria de correntes, mas por acaso o assunto bateu com alguns dos meus videos mais recentes então decidi participar. Ainda mais porque fui &ldquo;sumonado&rdquo;, praticamente intimado pelo Diolinux no video dele de &ldquo;Top 4 Distros de Todos os Tempos&rdquo;, que por sua vez foi &ldquo;sumonado&rdquo; pelo Slackjeff e assim por diante. A idéia é falar sobre as 3 distribuições Linux que mais me impactaram mas não vale falar da distro que estou usando neste momento. E no final tem que provocar mais pessoas pra continuar a corrente. Este video vai ser curtinho, só pra fechar o ano, então vamos lá!</p>
<p>(&hellip;)</p>
<p>Ironicamente, não pode falar da distro que estou usando neste momento. O problema é que estou usando 4 distros ao mesmo tempo. Deixa explicar, o principal é o Manjaro Gnome, que uso no PC de casa, e é minha máquina de trabalho. Mas tenho vários ambientes virtuais. Quem me acompanha no Instagram sabe disso, e vai ser tema de um próximo video que já estou escrevendo. Mas como ambiente pra retrogames eu tava usando o Garuda Linux numa máquina virtual. Assim como o Manjaro é derivado de ArchLinux também, mas vem mais estilizado e pré-configurado pra gamers. É uma distro bem pesada, feita pra máquinas mais parrudas.</p>
<p>Também tenho um Ubuntu 22 virtualizado pra alguns testes então não vou falar dele e tenho o Windows Subsystem for Linux, ou WSL rodando ArchLinux em cima, que uso no meu notebook porque lá o OS principal é Windows mesmo. Alguns modelos de notebook aceitam Linux de boa, alguns nem tanto, e esse Zephyrus G14 dá probleminhas com coisas como Bluetooth então decidi ficar no Windows mesmo já que uso mais pra viajar.</p>
<p>Então nada de Manjaro, nem Garuda, nem Ubuntu e nem Arch. Pois bem, vou falar primeiro o tema da corrente, as Top 3 distros que acho mais interessantes e depois o que acho que todo mundo realmente queria saber que são as distros que eu usaria como minha principal mesmo. Vamos lá, começando pelo número 3.</p>
<p>A número 3 é a distro que mais usei nos anos 90, e foi a francesa Mandrake Linux que depois se fundiu com a brasileira Conectiva e virou Mandriva, que começou em 1998 e morreu em 2011. Mandrake pra mim sempre pareceu um RedHat mais amigável, mais ou menos como um Ubuntu nasceu como um Debian mais amigável, só que quase uma década antes.</p>
<p>Mandrake vinha com KDE por padrão, e o principal é que tinha um instalador gráfico muito mais interessante que a versão em ncurses padrão dos RedHats. E pra quem não sabe o que é a distro RedHat, depois ele virou CentOS que é mais usado em servidores de empresas e o Fedora que é a versão mais usada em desktops. Além disso tinha o Mandriva Control Center, meio parecido com a proposta do Yast do OpenSuse de ter um painel de controle completo pra configurar o sistema em vez de ter que fazer muita coisa pelo terminal.</p>
<p>O gerenciador de pacote não lembrava o nome, mas pesquisando achei que era o &ldquo;urpmi&rdquo; que funciona como um wrapper pra RPM. Eu jurava que ele usava direto RPMs, que é o formato de pacotes RedHat Package Manager, que ainda é usado no CentOS e Fedora e gerenciado pela ferramenta &ldquo;DNF&rdquo;. O Mandrake, Conectiva e depois Mandriva foi onde realmente comecei a aprender Linux, aprender a programar com Perl, PHP, aprender sobre servidores web, configurar Apache e coisas assim. Eu usei outras distros daquela época, mas sempre voltava pro Mandrake.</p>
<p>O Top 2 acho bacana pra quem só conhece Ubuntus da vida porque é largamente usado e ninguém sabe disso, o Alpine Linux. Se você lida com infraestrutura, principalmente se é devops de Kubernetes e outras plataformas de containers, deve usar Alpine ou Debian. A idéia do Alpine é o oposto de um Fedora ou Ubuntu. Enquanto distribuições pra desktop tentam colocar um monte de software pra tornar a máquina mais fácil de usar, um Alpine tem como objetivo ser o mais leve e o mais seguro possível, vindo só com realmente o mínimo necessário pra um container funcionar.</p>
<p>Acho que tem louco que tenta usar Alpine como desktop, mas ele não foi feito pra isso. Dá? Dá, mas não tem vantagem em desktop, só em containers. Ele deriva de uma linhagem anterior de distros como o antigo LRP ou Linux Router Project ou &ldquo;Linux on a Floppy&rdquo;, cuja idéia era ter uma distro tão pequena que caberia num disquete antigo. Esse tipo de distro é útil pra bootar coisas como roteadores ou firewalls.</p>
<p>Sabe o modem de internet que você tem aí na sua casa? É um Linux dessa família, não necessariamente o Alpine, mas parecido. Ele vem com o kernel, os drivers mínimos pra coisas como placa de rede, nenhum tipo de interface gráfica pesada como KDE, e só um servidor web como Apache pra fornecer uma aplicação web que você acessa pelo navegador do seu PC ou via um app no celular.</p>
<p>Pra quem sobe Docker ou Podman, normalmente puxa imagens cuja base original era um Alpine. Por exemplo, este é repositório onde tem o Dockerfile que gera a imagem de Postgres que você usa nas suas aplicações. Ele tem duas versões, uma gerada a partir de Debian e outra a partir de Alpine. Se olharmos o repositório da imagem de python, o Dockerfile dele também começa com a opção de Alpine e depois Debian.</p>
<p>Debian ainda é bem usado pra essas coisas por causa da sua reputação lendária de estabilidade e maturidade. Mas o Alpine tá lá pau a pau. Diferente de Ubuntu ou Debian que usa a ferramenta &ldquo;apt&rdquo; pra gerenciar pacotes, Alpine usa &ldquo;apk&rdquo; e hoje em dia tem apk pra tudo que precisamos no mundo de servidores ou sistemas embarcados. Como programador, é importante aprender como o Alpine funciona e quais as diferenças com outras distros mais antigas como Debian.</p>
<p>E agora, meu Top 1 é pra dar uma troladinha, mas só pra sair do comum. Acho que é válido falar do Batocera. Quando falamos em distros pra gamers, muitos pensam no PopOS, ou Garuda ou mesmo o meu Manjaro, mas a maioria esquece do Batocera.</p>
<p>Eu uso Batocera num Raspberry Pi ligado neste setup que tenho em casa. Eu sempre quis um arcadezão de madeira que nem tinha antigamente, mas na prática, no meu apartamento pequeno, seria um trambolhão ocupando muito espaço. Daí resolvi repensar o conceito e ter um arcade minimalista. Eu já tinha um raspberry pi sobrando, tinha comprado este joystick de arcade da 8bitdo, que não é nem de longe bom pra competir numa EVO da vida, mas pra jogar casual é excelente. Daí comprei esse monitor portátil, super fino e leve. Tão leve que coloquei fita dupla face da 3M atrás e grudei na parede.</p>
<p>No Raspberry Pi antigamente instalava o RetroPie, que é outra distro dedicada a retrogaming, mas o Batocera acho evoluiu mais rápido e tem o melhor acabamento. Você baixa a imagem dele, instala num SD Card, coloca no Raspberry e já era, tudo funciona. Olha só o boot dele. Já vem com emuladores como RetroArch configurado, já vem com o front-end EmulationStation configurado pra ficar bonito que é esse que estou mostrando, já tem todos os perfis de controles de arcade e gamepads, então é tudo plug and play, não precisa abrir terminal pra ficar gambiarrando nada. Só copiar os arquivos de ROMs dos jogos e jogar. É excelente pra máquinas fracas ou raspberry pra virar central de games antigos.</p>
<p>Eu particularmente gosto muito desse novo mercado de consoles portáteis pra retrogames, como os feitos pela Ambernic ou Retroid. Este é meu Ambernic RG552, e dá pra instalar Batocera nele também. Este é meu Miyoo Mini, mas nesse o recomendado é instalar o Onion OS que é mais configurado pra este hardware em particular. E este é meu Aya Neo Next Pro que tá com Windows mesmo, mas esse é potente demais e seria meio desperdício usar Batocera nele. É basicamente a performance de um notebook. Se eu ligar um teclado e mouse, posso substituir meu notebook em viagens.</p>
<p>Se quiserem reviews de consoles portáteis, como configurar Batocera, Onion e muito mais, assinem os canais Retro Game Corp, Taki Udon, ETA Prime e Retro Dodo, que são os que eu assisto. Eles tem falado de Steam Deck, Aya Neo, Ayn Odin, e muito mais.</p>
<p>Pronto, terminei minha missão: os Top 1 e Top 2 foram pra abrir a curiosidade de vocês em ir conhecer distros Linux especiais pra usos específicos que não só desktop. Acho esse assunto bem mais fascinante do que ficar discutindo tema mais bonitinho de KDE ou GNOME. E agora que terminei meus Top 3 da corrente, deixa eu fazer alguns comentários sobre o que acho de outras distros.</p>
<p>Pra mim o mundo de distros se divide assim. Primeiro tem as distros OG, as originais ou derivadas das originais. Coisas como Slackware, que foi minha primeiro distro, ou Gentoo, que poderíamos dizer que é o menos velho dessa categoria. Mas são as distros que exigem que você tenha mais intimidade com a máquina e com baixo nível da kernel e configurações mais cabeludas. Não foram feitas pra serem amigáveis, foram feitas pra quem sabe o que está fazendo. Debian pra mim se encaixa nesse grupo. Se quiserem ver como é Slackware e Gentoo eu fiz um video pra cada recentemente, dêem uma olhada depois.</p>
<p>Daí existe a RedHat, que nos anos 90 despontou como a melhor solução de Linux comercial, oferecendo garantias, suporte técnico e tudo mais. Em paralelo apareceu a alemã Suse que hoje é OpenSuse e foi competidor de outros comerciais como Caldera Linux da Novell ou VA Linux que foi o primeiro IPO de Linux no meio da bolha da Internet do fim dos anos 90. RedHat junto com a Suse foram os únicos que sobreviveram. Dela derivam Fedora e CentOS que se usa até hoje.</p>
<p>Na virada do século nasce o Ubuntu, que é derivado de Debian e usa o mesmo formato de pacotes DEB e o mesmo gerenciador de pacotes apt. Ubuntu é o Debian menos estável, porque trás pacotes mais novos. Normalmente em servidor ou embarcado você não quer os pacotes mais novos, você quer os mais testados, estáveis, maduros e livres de bug.</p>
<p>Parte da popularidade do Ubuntu se deve a ele ter sido mais pragmático. Debian segue a filosofia de software livre mais à risca. Nada de binários sem código-fonte ou código que não tenha licença realmente livre. Já Ubuntu preferiu ser mais amigável e incluiu coisas como codecs de mp3, de video, drivers binários de hardware como NVIDIA e tudo mais. Isso facilita a vida do usuário médio, mas já não segue mais a idéia de software livre. Muitas distros hoje fazem a mesma coisa.</p>
<p>Em desktop não tem tanto problema instalar um pacote com bug e esperar alguns dias pra aparecer a correção. Mas em embarcado ou servidor, isso é um grande problema. Imagina um bug que é automaticamente instalado em centenas de servidores e causa crash pra todos os usuários. Seria uma catástrofe. Por isso um Debian da vida oferece pacotes mais &ldquo;velhos&rdquo; mas que sabemos que funcionam sólidos.</p>
<p>Tem gente que não curte o Ubuntu da Canonical. Seja porque não gosta de laranja, seja porque não gosta de Snap ou seja lá o que for, seja porque não curtem Unity ou GNOME. Daí surgiram dezenas de distribuições derivadas de Ubuntu. Nessa categoria temos o PopOS, Linux Mint, Elementary, Zorin OS, Budgie, Feren, Lubuntu, KDE Neon e vários outros. O que muda é a interface gráfica, quais pacotes já vem pré-instalados, configurações e tuning do sistema. Mas pra mim é tudo Ubuntu.</p>
<p>Tem uma coisa que eu não curto em APT, que é o gerenciador de pacotes que todos usam, e ter que instalar e configurar PPAs pra instalar pacotes de terceiros. Acho um saco, acho manual demais, acho bugado. Por isso passei a preferir derivados de ArchLinux pra desktop. Arch é o oposto de Debian: o mais instável possível porque ele instala tudo que sai de novo. E eu sou o tipo de cara que gosta de novidades o quanto antes.</p>
<p>ArchLinux funciona no sistema de rolling updates. Não tem isso de upgrade de Ubuntu 20 pra 21, de 21 pra 22, ele vai só se atualizando constantemente. O problema disso é que quase todo dia tem atualização. Quem se incomoda com isso vai odiar. E também por causa disso, vira e mexe pode vir uma novidade pouco testada que causa bugs.</p>
<p>E pra piorar ainda temos o AUR que é o Arch User Repository, onde qualquer um pode subir qualquer pacote de qualquer coisa. A vantagem disso é que todo software que imaginar provavelmente alguém subiu lá e tá fácil pra instalar. A desvantagem é que todo mundo pode subir qualquer coisa e vira e mexe sobe mal feito e mal testado: você instala e dá pau.</p>
<p>Essa não é uma distro pra amadores. Vira e mexe vai ter que consertar alguma coisa na não. Por isso existem derivados de Arch que tentam estabilizar um pouco mais as coisas e aí chegamos no meu Manjaro. Em vez de liberar pacote novo toda hora, o Manjaro dá uma segurada pra dar tempo da comunidade testar um pouco mais, subir correções e só depois eles liberam tudo de uma vez só.</p>
<p>Ele atualiza com menos frequência e quando vem atualização não quebra catastroficamente. E Manjaro passou a oferecer o sistema de arquivos BTRFS já na instalação, em vez de ext4, então temos suporte a snapshots, que oferece outra camada de proteção caso a gente instale um pacote bugado. Vou falar disso em outro video.</p>
<p>Isso tudo dito, pra um iniciante que não sabe bem que distro usar, posso recomendar ou Manjaro, seja KDE ou GNOME, ou PopOS que acho que é um dos derivados de Ubuntu mais bem feitinhos. Ou direto o próprio Ubuntu. A vantagem é que a maioria dos posts de blog e tutoriais documentam como fazer as coisas em Ubuntu. Raramente você vai achar um post que ensina como fazer as coisas em Gentoo ou Slackware, mas pra Ubuntu é mais fácil. Por isso que falamos que é mais indicado pra iniciante: porque tem mais chances de achar documentado como resolver um problema específico.</p>
<p>ArchLinux e Gentoo tem outra coisa legal: o Wiki deles são a melhor documentação de Linux ever. Sério, tudo que imaginar tem documentado lá. Se não tiver na Wiki do Arch, vai ter na Wiki do Gentoo. Se não tiver mesmo lá, parabéns, você achou alguma coisa que ninguém no mundo viu ainda. Mas esse Wiki pode intimidar os iniciantes, porque é bem detalhado.</p>
<p>Mas na prática o que todo mundo quer mesmo saber é qual interface gráfica usar, independente da distro por baixo. KDE que você gasta algum tempo configurando temas e plugins, costuma ser o mais bonito, mas também meio pesadão. Recomendo brincar com gerenciadores de tiling window, onde as janelas ficam automaticamente uma do lado da outra em vez de uma em cima da outra. Quem popularizou isso foi o i3, o próprio PopOS trás essa funcionalidade de forma opcional que é legal pra começar, mas pra ser mais hardcore tentem usar o Sway ou XMonad.</p>
<p>Sway é mais ou menos a mesma coisa que o i3 mas feito pra rodar em Wayland. Se tiver GPU que não seja NVIDIA, provavelmente é a que vai rodar melhor. E o mais avançado acho que é XMonad. São interfaces gráficas feitas pra operar sem mouse. É pra programador hardcore de Vim ou Emacs que não tira os dedos das teclas pra nada. Eu já usei, e se minha rotina ainda fosse só programar, certamente usaria um dos dois. Mas como não é mais, fico no GNOME, que é o mais simples, não precisa customizar muito, e se comporta mais parecido com os antigos MacOS.</p>
<p>No geral é isso, por hoje é só. Diferente fazer um video curto assim. Pra continuar a corrente, preciso dar summon em outros canais pra passar o bastão. Eu realmente não assisto muitos canais de programação nem Linux no YouTube, então vou chamar dois que já interagi pelo Twitter e um terceiro que me indicaram. Eles fazem coisas bacanas que vocês vão gostar.</p>
<p>O primeiro é o Vitor Vilella. O moleque é hardcore, ele pega binários de ROMs de jogos de Super NES que tem problemas de lentidão, como Gradius III, por exemplo, e ativa chips de aceleração do Super NES como o SA-1. Ele faz assembly de chip de Super NES, e a ROM resultante dá até pra gravar num chip de cartucho de verdade e jogar no hardware original.</p>
<p>Encontrei ele por causa do projeto de Super Mario World em widescreen. Sério, ele ajustou o jogo todo pra ter mais espaço na direita e na esquerda e dar pra ver mais da fase ao mesmo tempo, sem quebrar nada. Só baixar um emulador como o BSNES que tem suporte a esse tipo de hack e jogar. Eu assino o Patreon dele. Dêem uma olhada. O canal de YouTube ainda é pequeno mas vale a pena.</p>
<p>O segundo que me veio à cabeça é o Pikuma &hellip; paikuma? pikuma? Eu acho que ele é professor brasileiro que trabalha na Inglaterra. Ele vende cursos online. E eu sei, logo eu anti-curso recomendando curso é foda, e já deixando claro que ele não me pagou pra dizer isso, mas os temas dos cursos dele são interessantes.</p>
<p>Segundo o site dele, o objetivo é ensinar as fundações da programação de verdade, que eu sempre falo que ninguém ensina, como álgebra linear, trigonometria, matemática discreta e análise numérica, cálculo. Só que em vez de serem aulas teóricas chatas, ele faz isso aplicando na construção de um motor de jogos 2D em C++, ou programando em assembly do antigo Atari VCS, ou replicando o sistema de raycasting usado nos primeiros jogos de tiro como Wolfenstein.</p>
<p>Eu não cheguei a assistir nenhum dos cursos, estou chutando que sejam bons pelo tema e pelos capítulos que li. Claro que não dá pra explicar a ciência da computação inteira só nesses cursos, mas com aplicações reais e divertidas, deve incentivar o pessoal a procurar se aprofundar depois. Vocês podem me dizer nos comentários se gostaram.</p>
<p>O terceiro canal me foi recomendado pelo Twitter  e esbarrei no Manual do Código do Douglas. Esse cara tem centenas de videos de temas interessantes de programação. Ele deveria ter muito mais seguidores. Vão lá dar uma moral pra ele. Assim como o Pikuma, também fala de programação de videogames antigos, tem javascript, tem python, tem megadrive, tem muita coisa pra quem curte programação.</p>
<p>E é isso aí, agora é com vocês, claro que é opcional participar da corrente, mas acho que foi uma boa oportunidade pra falar de outros canais aqui do Brasil que tão fazendo mais do que repetir as mesmas coisas cansadas de Python ou Javascript. Quem conhecer outros canais parecidos e quiser recomendar, não deixem de postar nos comentários abaixo. Se curtiram o video mandem um joinha, assinem o canal e compartilhem o video com seus amigos. E você assistindo, mesmo não tendo sido intimado aqui, faça um video de Top 4 distros também e compartilhem. Boas Festas, Feliz Natal e a gente se vê em 2023, Feliz Ano Novo!</p>
]]></content:encoded><category>linux</category><category>reviews</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] #132 - Rant: A Bolha de Startups Estourou?</title><link>https://akitaonrails.github.io/2022/11/22/akitando-132-rant-a-bolha-de-startups-estourou/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/11/22/akitando-132-rant-a-bolha-de-startups-estourou/</guid><pubDate>Tue, 22 Nov 2022 10:15:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/xxoVJQvEyEU"
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;2o Semestre de 2022: todo mundo está vendo toda grande tech startup e emrpesa de tecnologia fazendo demissões em massa depois de apresentar resultados ruins. Isso ainda pode continuar? Por que isso está acontecendo agora? O fim dos lockdowns não deveria ter melhorado tudo?&lt;/p&gt;
&lt;p&gt;Hoje vamos entender o que eu tentei avisar em Junho de 2019, sem saber que uma pandemia iria acontecer. E o que você, se preparando pra entrar no mercado, precisa entender sobre sua carreira.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/xxoVJQvEyEU"
    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>2o Semestre de 2022: todo mundo está vendo toda grande tech startup e emrpesa de tecnologia fazendo demissões em massa depois de apresentar resultados ruins. Isso ainda pode continuar? Por que isso está acontecendo agora? O fim dos lockdowns não deveria ter melhorado tudo?</p>
<p>Hoje vamos entender o que eu tentei avisar em Junho de 2019, sem saber que uma pandemia iria acontecer. E o que você, se preparando pra entrar no mercado, precisa entender sobre sua carreira.</p>
<p>== Capítulos</p>
<ul>
<li>00:00 - Intro - Demissões em Massa em Tech</li>
<li>02:21 - Cap 1 - De Onde Vinha o Dinheiro? Venture Capital</li>
<li>06:15 - Cap 2 - Os Efeitos da Pandemia - Casa de Cartas</li>
<li>16:51 - Cap 3 - Pós-Pandemia e a Falta de Programadores - Excessos</li>
<li>21:14 - Ferramentas de Avaliação Financeira</li>
<li>27:15 - Cap 4 - O Escândalo da FTX - Criptomoedas</li>
<li>35:22 - Cap 5 - NÃO ACREDITEM EM INFLUENCERS - Cancelem Cursos</li>
<li>40:12 - Cap 6 - Estamos no Fim ou só no Começo? Crash de 2001?</li>
<li>43:48 - Bloopers</li>
</ul>
<p>== Links</p>
<ul>
<li>Tech Layoff Tracker (<a href="https://www.trueup.io/layoffs"target="_blank" rel="noopener">https://www.trueup.io/layoffs</a>)</li>
<li>TradingView (<a href="https://www.tradingview.com/"target="_blank" rel="noopener">https://www.tradingview.com/</a>)</li>
<li>Qualtrim Insights (<a href="https://qualtrim.com/insight"target="_blank" rel="noopener">https://qualtrim.com/insight</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>Estamos no fim de 2022 e todo mundo deve ter visto o tanto de demissões em massa em empresas de tecnologia. No fim de novembro a Amazon anunciou a demissão de 10 mil funcionários, boa parte  engenheiros. Pouco antes, tio Zuckerberg anunciou o resultado horrível da Meta e seguiu cortando 11 mil pessoas, boa parte engenheiros. Na mesma época tio Elon Musk comprou o Twitter e na sequência já cortou metade dos funcionários da empresa, mais de 3 mil pessoas. A Salesforce cortou 2 mil pessoas, a Netflix até que cortou pouco, só umas 450. A corretora Robinhood cortou 20% dos funcionários, a Snap cortou 20%, até a Intel que costuma ser mais estável cortou 20%. Só nas Big Tech dos Estados Unidos, foram pra lá de 120 mil demissões.</p>
<p>Vocês se lembram deste meu video de Junho de 2019 onde eu dizia que a bolha ia estourar? E depois este outro de Junho de 2021 onde  explico o que a pandemia causou na economia e o que ia acontecer em breve? Alguns aqui assistindo devem ter ouvido influencers, coaches, vendedores de curso uniesquina, todos falando que o Akita é negativo, que tá só espalhando medo, que é teoria da conspiração, terra planista. Pois bem, senhoras e senhores, não vou dizer que eu estava certo, mas pelo menos até agora tá próximo disso. E agora vou explicar como te enganaram até hoje.</p>
<p>(&hellip;)</p>
<p>Que o mercado de big tech estava inchado não era novidade pra ninguém que é sério. Vamos ver um exemplo que ilustra bem isso. Esta é a evolução do preço das ações do Facebook, agora Meta. De 2013 até meados de 2018, meros 5 anos, essa empresa valorizou mais de 500%. O preço da ação foi de 34 dólares pra mais de 200 dólares. Se fosse só ela poderíamos achar que foi uma exceção. Mas não, todo mundo tava assim. Vamos ver mais uma.</p>
<p>Se pegarmos o mesmo período, 2013 até 2018, as ações da Amazon subiram mais de 600%, pulando de 14 pra mais de 100 dólares. Imagina que sensacional, você investe 1400 dólares em 2013 e sai com 10 mil em 2018 sem fazer nada. E tava todo mundo assim, Microsoft, Apple, Google, Salesforce, Uber, Lyft, Netflix. Não é à toa que de repente o mundo de tecnologia se tornou as meninas dos olhos de Wall Street. O crescimento foi absurdo.</p>
<p>O que acontece é que todo mundo ao redor quer ver esse crescimento aumentando ainda mais, a qualquer custo. Então os fundos de investimento, os venture capitalists ou VCs, entraram no jogo pra alavancar todo mundo. É onde se vê nomes como Sequoia, Redpoint, Tiger, Softbank e outros. Toda hora a gente vê nas notícias, SoftBank colocou X milhões em startup fulano. SoftBank colocou Y milhões em em startup ciclano. Mas a gente olha pra essas startups e pensa &ldquo;cara, não é possível, esse business não vale tudo isso nem aqui, nem na China&rdquo;.</p>
<p>Ironia já que o dono da SoftBank, o Masayoshi Son se tornou famoso por ter apostado em Jack Ma do Alibaba antes dele ter virado gigante. Assim como o fundo de investimento Sequoia ficou famoso por ter acreditado no Mark Zuckerberg no começo do Facebook. É sempre assim que funciona: eles conseguem tirar a sorte grande, saem discursando como são mais inteligentes que todo mundo por terem sido visionários, e o resto do mundo, idiotas, acreditam que eles tem o toque de Midas e tudo que tocam vira ouro, sendo que a explicação mais simples e mais racional é sempre a mesma: só tiveram um tiro de sorte. Que inteligência você acredita que um ganhador da Loteria tem? Nenhuma. É a mesma coisa.</p>
<p>Só que nesse discurso eles atraem outros investidores pra junto deles. Assim como o Masa Son resolveu criar um segundo grande fundo, o Vision Fund, com objetivo de levantar nada menos que 100 bilhões de dólares pra investir em startups inovadoras. Onde ele conseguiu esse dinheiro? Com milionários do petróleo e sheiks do Oriente Médio. Todo mundo tava acreditando nesse papo de guru financeiro.</p>
<p>Os últimos 12 anos foram anormais. Tudo começou na última crise financeira de 2008, quando estourou a bolha imobiliária e muitos anunciavam que poderíamos estar pra reviver os anos negros da Grande Depressão de 1929. Justamente daquela época o que muito se critica o governo americano de Herbert Hoover foi ter demorado muito pra intervir e não ter intervido o suficiente. E em 2008 o presidente do Federal Reserve, o FED, Ben Bernanke, estava determinado a não cometer o mesmo erro e copiou a mesma estratégia do Banco do Japão de 2001, o que ficou conhecido como Quantitative Easing, ou QE.</p>
<p>QE é a compra e remoção dos ativos podres do mercado, pra agilizar a recuperação. Foi a compra massiva dos Mortage Backed Securities ou MBS que tinham contaminado todo o sistema financeiro em 2008. Na prática é imprimir muito, mas muito dinheiro, bilhões e bilhões, brrrrrrr. Não só isso mas também baixar as taxas de juro. Taxa de juros é o que você vê na TV quando falam em taxa Selic no Brasil. É o que se usa pra calcular, por exemplo, os juros que você paga quando financia uma casa ou pede empréstimo no banco. Quanto maior essa taxa, mais caro se paga em cada parcela que deve ao banco.</p>
<p>A queda na taxa de juros estimula a economia porque torna o crédito barato. Ao mesmo tempo diminui a rentabilidade de investimentos considerados seguros como papéis do Tesouro, CDB, Renda Fixa no caso de Brasil. Some essas duas coisas e acontece o seguinte: você é um fundo de investimento, se deixar a grana parado em papéis do tesouro, não vai render nada. As taxas de juro em determinado momento, chegaram muito perto de zero e todo mundo tava até cogitando o que ia acontecer se chegasse no elusivo caso de juros negativos. Literalmente o governo ia te pagar pra pegar dinheiro com eles, que é o que acontece no Japão.</p>
<p>Se não rende nada deixar em renda fixa, sua única alternativa é o mercado de risco, de ações, startups e coisas assim. É por isso que nos últimos 12 a 10 anos o mercado de tech explodiu: tinha muito, mas muito dinheiro sobrando. Todo fundo de investimento, hedge fund, family office saiu caçando todo mundo com montanhas de dinheiro parado, prometendo valuations absurdas no nível de Facebook ou Amazon pra todo mundo. E todo mundo acreditou.</p>
<p>Por 10 anos ficaram jogando dinheiro em tech startups esperando o momento de terem retorno desse investimento, quando abrissem IPO, que é quando a empresa se torna pública e seus papéis são negociados na bolsa de valores, atraindo ainda mais investidores. Mas chegou 2019 e veja o caso da Lyft por exemplo, concorrente do Uber e bem usado nos Estados Unidos. Todo mundo tinha expectativas que valorizaria muito. A ação abriu custando uns 88 dólares no dia 25 de março de 2019 e 1 ano depois, em março de 2020 tinha caído pra 20 dólares. O que aconteceu?</p>
<p>Na época que postei o video se a Bolha das Startups ia estourar, que foi Junho de 2019, eu tinha na cabeça que se fosse vir uma correção, era provável que seria por volta de 2020 ou 2021. Por isso postei o video achando que estava avisando com 6 meses a 1 ano de antecedência. Mas o que nem eu e nem ninguém fora da China podia imaginar é que em Março de 2020 estaríamos fazendo um lockdown mundial por causa de uma pandemia global, coisa que não se via faz mais de um século.</p>
<p>A pandemia veio nos 45 do segundo tempo, na prorrogação, e salvou o mundo da tecnologia do crash iminente. Foi um efeito colateral inesperado. E os sinais ficavam mais claros no fim de dezembro de 2019, quando a NASDAQ teve sua primeira grande queda em 10 anos. Mas a pandemia chegou bem a tempo de impedir essa correção. O que aconteceu foi simples: tranca todo mundo em casa. Esse movimento teve como efeito aumentar exponencialmente a demanda por todo tipo de serviço online possível.</p>
<p>Todo mundo que vendia infraestrutura, como VPNs, software de cooperação e comunicação como Zoom, Slack, com trabalho tendo que virar remoto, a demanda por soluções pra isso explodiu. Todo mundo precisa comer, serviços como iFood aumentaram absurdamente. Ninguém mais podia sair pra se entreter em cinema, bar ou balada, então serviços de streaming como Netflix, Disney+ e outros aumentaram absurdamente. Não dá pra ir nas lojas, mas continuamos precisando comprar coisas, então lojas online como Mercado Livre, Amazon e outros aumentaram absurdamente. E pra conseguir atender toda essa nova demanda massiva, precisava de mais infraestrutura, mais engenheiros, e a demanda por programadores, devops, infosec, data scientists e tudo mais ao redor, também aumentou absurdamente.</p>
<p>Tudo na economia caiu, comerciantes de rua, restaurantes, hotéis, viagens, menos tecnologia, que cresceu a níveis extraordinários. Se a casa de cartas já tava alta e prestes a colapsar no fim de 2019, a gente aumentou mais andares ainda em cima dessa casa. O caso clássico foi a Netflix, que atingiu seu All Time High, ou seja o preço mais alto já negociado nas suas ações em Junho de 2018,  negociado na faixa de 420 dólares. Desde então caiu um pouco, tentou subir, caiu de novo e em setembro de 2019 tava baixo na faixa de 240 dólares. Uma queda de quase 40% em 1 ano. Mas aí chegou a pandemia.</p>
<p>Em novembro de 2021, mais de 1 ano depois do começo oficial dos lockdowns, as ações dela foram pra quase 700 dólares, um aumento de mais de 160%. Mas ela foi conservadora. Veja o Zoom, que todo mundo passou a usar pra trabalhar remoto. Saiu da faixa de 80 dólares no começo de 2020 pra quase 600 dólares no fim de 2020, um aumento absurdo de quase 800%.  Amazon subiu 120%, Google subiu quase 200%, a Meta foi uns 180%, Apple foi uns 200%. Todo mundo tava subindo 200% ou mais nesse período.</p>
<p>Por causa da pandemia todos os governos do mundo literalmente seguiram a cartilha do &ldquo;A Economia a gente vê depois&rdquo;. Os níveis de Quantitative Easing que foram feitos pra salvar o mercado financeiro em 2008, na crise que muitos consideravam que poderia ser o fim do capitalismo, foi fichinha comparado com a quantidade de dinheiro impresso entre 2020 e 2021, foram trilhões e trilhões, o equivalente ao PIB desses países. É como se você se endividasse num valor muito superior ao que ganha de salário durante o ano todo, de uma só vez, só que no nível de países. Se alguém era fã de Universal Basic Income, essa foi a maior distribuição de renda diretamente pra pessoas que já se viu na história. Máquina de imprimir dinheiro nunca fez tanto brrrrrrrrrrr</p>
<p>Isso deveria ter feito a inflação explodir, mas como tava todo mundo preso em casa, boa parte dessa inflação ficou contida. Mas não no mercado financeiro. Esses aumentos de preço de ações que eu falei, 200% ou mais, foi justamente isso: inflação. Mas o que todo mundo viu nas notícias foi que era possível imprimir dinheiro e não ter inflação, parecia um milagre, no meio de uma pandemia. 2020 foi um ano bizarro. Só porque você não vê não significa que não existe, só que você não sabe onde olhar. Alguns de nós sabíamos.</p>
<p>Aí chegamos no fim de 2021, começo de 2022. A pandemia chegou ao fim, a maior parte dos lockdows impostos pelo mundo foram derrubados. Todo mundo voltou à vida normal. E os efeitos da gastança desenfreada começaram a aparecer. É impossível imprimir dinheiro e não ter inflação e foi o que aconteceu nos Estados Unidos. Pra quem não acompanha, pela primeira vez em não sei quantas décadas a inflação lá está maior do que aqui no Brasil. Já pararam pra imaginar isso? 7% lá contra 4% aqui. Americano não sabe lidar com isso não. Eles chegaram perigosamente próximos de ter inflação de 2 dígitos, uma catástrofe.</p>
<p>E só pra piorar o tio Putin resolveu lidar o foda-se e começar uma Guerra. Bora lá invadir a Ucrânia, por que não? Todos os líderes mundiais do Ocidente são os maiores bundões que já se viu na história tudo de uma só vez. Trudeau, Macron, o senil do Biden. De imediato isso causou sérios problemas no preço da energia, petróleo e principalmente gás natural. A Europa, em particular a Alemanha, que tomou a decisão estúpida de fechar usinas nucleares, ficou na mão da Rússia. Tivemos todo o imbróglio do Nordstream. Com tudo isso, o FED foi obrigado a subir as taxas de juros. Lembram? As taxas que tava perigando ficar negativo, começou a subir, 25 pontos, 50 pontos, em toda reunião do FOMC e agora já está em na faixa de 3% e pode subir mais.</p>
<p>Some a isso que todas as apresentações de resultados de empresas de tecnologia em 2022 foram ruins. Lockdown acabou, demanda diminuiu. E lógico, resultados ruins se comparados ao pico da pandemia um ano antes. A demanda por serviços caiu à medida que as pessoas puderam parar de ficar presas em casa. Serviços de delivery, vendas online, streaming, tudo caiu. Quanto eu falei que o Netflix subiu no pico da pandemia? 160%? Até agora, em novembro de 2022, ela perdeu 55% do seu valor.</p>
<p>Alguém pode pensar, &ldquo;ah, se cresceu 160% e perdeu só 55%, não é tão ruim assim&rdquo;. Isso porque você não sabe pensar em percentual. Quando seu preço é faixa de 280 dólares e crescer 160%, vai pra quase 700 dólares, é crescimento de quase 2.5x. Cada 100% a mais é dobrar. Mas quando cai, cair 50% significa perder metade do valor. Então essa queda de 55% devolveu o Netflix pra níveis semelhantes de antes da pandemia, pra faixa de 300 dólares. Pra recuperar não é subir os mesmos 55% mas sim 100%. Se você perdeu metade do valor, pra recuperar agora precisa dobrar. Todo a valorização que ela ganhou durante a pandemia, se perdeu em 2022. Só tem um problema: eles contratarm bastante gente e investiram mais. Então você tá valendo a mesma coisa de antes só que gastando muito mais.</p>
<p>E isso aconteceu com todo mundo. Veja a Amazon, cresceu 120% mas em 2022 caiu quase 50% e mesma coisa, voltou pro mesmo patamar de antes da pandemia. Veja o Zoom, que cresceu quase 800%, em 2022 perdeu 85% do valor, novamente, voltando pros patamares de antes da pandemia. Literalmente todo mundo de tech teve comportamento semelhante, salvo alguns poucos que tem excelência em gestão como a Apple, que ainda retém boa parte do crescimento que teve.</p>
<p>Portanto temos a tempestade perfeita pra retomar o crash que deveria ter acontecido no começo de 2020, a que eu estava avisando em 2019: uma alta drástica na demanda que fez todo mundo investir mais e contratar mais durante 2020 e 2021, seguida de uma baixa na demanda, que derrubou a receita de todo mundo, seguida de taxas de juros elevadas, o que faz muitos dos investidores que antes eram obrigados a investir em mercado de risco a voltar pra investimentos mais seguros como papéis do tesouro. Seguido dos resultados ruins de todo mundo durante o ano e o que temos é o que vocês estão vendo agora: demissões em massa.</p>
<p>Como listei no começo do video, Amazon teve que demitir 11 mil funcionários. Meta com seu fracassado plano de Metaverso, que já consumiu 8 bilhões de investimento sem ter nenhum resultado, teve que demitir outros 11 mil funcionários. E assim segue todas as Big Tech, deixando na rua mais de centenas de milhares de pessoas, que já é mais demissões do que tivemos no pico do Crash da Bolha da Internet de 2001. E essa ainda parece ser só a primeira perna desse crash. Muitos acreditam que ainda vamos ter um segundo crash nessa faixa de 50% antes de podermos declarar que atingimos o fundo.</p>
<p>A tal SoftBank, que era a menina dos olhos do mundo de startups, que teve sorte uma vez com o Alibaba, fez investimento ruim atrás de investimento ruim. O mais notável foi a queda da WeWork, nas mãos do hipster louco do Adam Newmann. Da abertura do IPO até agora ela perdeu 70% do valor. Nunca gerou nenhum valor pros investidores e manchou pra sempre a reputação da SoftBank. E o antes guru genial Masayoshi Son agora deve 4 bilhões de dólares pro Vision Fund que ele ajudou a criar e deixa o cargo de CEO depois tanta aposta ruim.</p>
<p>Caso semelhante foi o fundo de investimento Arkk Innovation que só investia em startups de risco que eles categorizam como altamente inovadores. A guru do investimento Cathie Wood atraiu muito investidor e durante a pandemia também teve um crescimento meteórico de quase 400%. Só que como todo mundo, em 2022, perdeu 75% desse valor. Todos os grandes gurus foram massacrados este ano. Todos foram expostos como tendo tido sorte durante um período econômico favorável e não porque eram gênios. Um verdadero gênio estaria ganhando dinheiro em 2022 onde todo mundo perdeu. E nenhum deles conseguiu isso.</p>
<p>Durante os últimos 10 anos, em particular nos 2 anos de pandemia, a demanda por programadores atingiu All Time High, a maior demanda da história ever. Toda tech startup minimamente competente conseguia atrair investidores a rodo. Tinha dinheiro sobrando, alta liquidez. E todo mundo tinha ânsia de crescer rápido, conseguir chegar no tão sonhado IPO e virar bilionário. Então estavam contratando como se não houvesse amanhã. Logo começou a faltar programadores no mercado.</p>
<p>Eu senti isso na pele. Entre 2020 e 2021, a demanda pelos programadores da minha empresa aumentou de forma absurda. Sério, tinha fila de gente querendo contratar. Todos os dias alguém queria mais programadores. Mesmo eu aumentando preços, nem assim eles desistiam. Se eu tivesse sido ganancioso e aproveitado a ocasião pra, por exemplo, consolidar e comprar concorrentes menores, dobrar o número de colabores, eu teria alocado todos com facilidade pelo dobro do preço. Ainda bem que não fiz isso, porque senão agora eu estaria na mesma lista de todo mundo que tá precisando fazer demissão em massa.</p>
<p>Por isso cursos foram um excelente negócio durante esse período, porque realmente estava razoavelmente fácil qualquer empresa contratar qualquer programador, independente do nível técnico dele, pela simples escassez do mercado. Bootcamps, cursos com marketing de &ldquo;estude 6 meses e tenha emprego garantido&rdquo; estava muito fácil. Se soubesse inglês então, todo mundo conseguia ir pro Canadá, pra Alemanha, pra todo lugar. Só que num mercado em crise como agora, quem você acha que vai ser cortado primeiro? Pois é.</p>
<p>Muita gente pensa, &ldquo;porra, olha os números da Meta. Os caras faturaram quase 30 bilhões de dólares&rdquo;. E é assim todo trimestre. Olha só como eles ganham bem pra caramba. Pra que precisava demitir 11 mil funcionários? Opa, calma lá, eles investiram muito e tiveram muitas despesas nesse período. O que realmente sobra de dinheiro, é o que chamamos de Free Cash Flow, fluxo de caixa livre. É o dinheiro que realmente se tem em caixa. E como investidor é o que você precisa prestar atenção. Deixa eu explicar como esses números realmente funcionam.</p>
<p>No caso da Meta, olha que ridículo. Até o fim de 2021 só crescia, chegou a sobrar 12 bilhões de dólares em caixa. Mas olha 2022. Caiu pra 8 bilhões, depois 4 bilhões e o último relatório diz que agora sobrou só 320 milhões. Pra dar dimensão pra vocês, pensa em dezembro de 2021 sobrando 12 mil reais de grana no saldo da sua conta. Agora você olha em setembro de 2022 e sobrou só 320 reais. Sim, não 3 mil e 200, 320 reais. Você ficaria preocupado?</p>
<p>Mas isso não é tudo. Principalmente nos Estados Unidos, engenheiros de software não costumam ser pagos só em dinheiro. Boa parte do que atraía eles pra trabalhar numa tech startup era ganhar as tais stock options, que quando era liberado, dava a oportunidade deles poderem comprar ações da empresa a preço mais baixo do que o mercado. É a estratégia pra manter você motivado já que faz você sentir que sua performance vai te dar mais lucro no futuro e também pra te reter já que options tem período de vesting, onde só se recebe essa opções de compra depois de alguns meses ou anos.</p>
<p>Só que ela tem outra vantagem: compensação com stock options é considerado non-cash expenditure ou gastos não em dinheiro, portanto não subtrai da conta de fluxo de caixa livre. Vamos voltar pro gráfico de cash flow da Meta. Como disse antes, vem caindo, mas pelo menos tá sobrando dinheiro, certo? 320 milhões dá pra pagar bastante gente. Nope, nessa ferramenta que estou usando posso mostrar o que foi gasto com stock options e olha só, na realidade eles tão no negativo! 320 milhões de fluxo de caixa nesse trimestre contra nada menos que mais de 3 bilhões em compensação de stock options!</p>
<p>A Meta tá efetivamente no negativo. Eles dobraram a quantidade de funcionários, boa parte sendo engenheiros, e pra atrair tanto engenheiro numa época onde programador tava em falta e super valorizado, ofereceram muita stock option como compensação. Isso significa que os investidores estavam sendo diluídos, o que diminui o valor de cada ação. Eles precisam rebalancear isso logo. E justamente por isso se justifica a demissão em massa que tiveram que fazer e os novos planos de tirar o foco do metaverso e tentar monetizar outros produtos como o Whatsapp. É assim que se lê os números de uma empresa.</p>
<p>Falando em ferramenta, antes que me perguntem, estou usando duas neste video, a primeira é a famosa TradingView que todo mundo que acompanha bolsa de valores usa todo dia. A versão gratuita é suficiente e ela facilita acompanhar e visualizar a evolução de preços de ações do mundo todo, fazer price discovery e análise técnica com as ferramentas gráficas. Todo dia eu bato o olho em diversas ações que gosto de acompanhar pra saber como anda o mundo.</p>
<p>A segunda ferramenta eu descobri num canal de YouTube, do Joseph Carlton. Eu passei a acompanhar ele porque gosto das análises. É um seguidor do livro de Warren Buffett, e é o oposto dos grafistas que acompanham só gráficos. Ele prefere o estilo mais fundamentalista de investir em empresas que tem fundamentos bons, que parecem bem administrados e tem bom histórico de retorno. É um estilo bem mais conservador, que eu gosto.</p>
<p>Eu não tenho vocação pra ser super arriscado fazendo day trading. Prefiro colocar meu dinheiro em empresas confiáveis e deixar lá por anos. Enfim, ele criou essa ferramenta pra facilitar avaliar os dados publicados por essas empresas. A ferramenta se chama Qualtrim Insight e pra ter acesso precisa assinar o Patreon dele. Achei super barato e a ferramenta é eficiente, então assinei.</p>
<p>Pra análise fundamentalista, precisa saber minimamente ler os números da empresa. As empresas públicas com ações negociadas na bolsa de valores precisam publicar esses relatórios todo trimestre, como manda a regulamentação, é por isso que temos esses dados. Deixa eu mostrar como é uma empresa pública de administração exemplar. A melhor que eu conheço é a Apple. E não porque eu sou fanboy, nem uso produtos Apple mais, mas pelos números mesmo. Primeiro no Trading View. Como todo mundo, ela também caiu esse ano, mas enquanto muitas caíram 50%, 70% ou mais, a Apple desvalorizou só pouco mais de 15%, o que é excelente.</p>
<p>Vamos ver faturamento, receitas e olha só isso. Ano após ano, a receita cresce muito. Em 2022 são quase 400 bilhões de dólares. Mas só faturar muito não significa nada se a empresa tá excessivamente endividada, então vamos ver fluxo de caixa livre e olha só, sobra dinheiro. Este ano ela tem em caixa mais de 100 bilhões. Dá pra comprar 2 Twitters e ainda sobra troco. Mas e compensação com stock options? Ela não fez a mesma coisa que a Meta e contratou a rodo?</p>
<p>Não, olha isso que coisa linda, 9 bilhões de compensação em options contra 111 bilhões de free cash flow. Tem 10x mais dinheiro líquido do que diluição das ações. Ela sabe contratar muito bem. Ela gera muito dinheiro, por isso mesmo distribuindo stock options, proporcionalmente, é muito pouco. Além disso veja como ela distribui dividendos e sempre vai aumentando ao longo do tempo, que todo investidor gosta. Ao mesmo tempo a quantidade de shares outstanding está diminuindo, o que significa que ela recompra as próprias ações. A Apple faz o oposto de diluir, ela valoriza as ações.</p>
<p>Empresas que alavancam às custas de investidor aumentam shares outstanding, ou seja, vendem mais ações e diluem os investidores atuais, diminuindo o valor de cada um. Quando a empresa recompra as ações, ela tira as ações de circulação, recupera mais controle da empresa ao mesmo tempo que valoriza quem investiu nela. Como investidor eu sempre quero ver empresas que distribuem dividendos, recompram ações, mas ao mesmo tempo não deixam de investir em melhorar o negócio.</p>
<p>Que é exatamente o que a Apple faz. Veja com as despesas dela aumentaram. Isso pode ser compra de outras empresas menores, pode ser pesquisa e desenvolvimento. É desse dinheiro que ela faz fábricas e desenvolve os novos chips M1 e M2. Mas ela continua aumentando esses gastos, ou seja, continua investindo no futuro da empresa. E faz isso sem ter que diluir investidor, aumentando fluxo de caixa e veja o lucro líquido, sempre crescendo. A Apple é o modelo que toda empresa gostaria de ser. Pouquíssimas empresas no mercado tem esse perfil, em qualquer área, não só tech.</p>
<p>Agora vamos ver uma que eu não ficaria confortável em investir, o oposto de uma Apple. Vamos ver a Shopify, plataforma de e-commerce que abriu IPO não faz muito tempo, por volta de 2016. Como todas as outras, teve crescimento meteórico, o preço das ações foi de uns 3 dólares pra mais de 160, crescimento absurdo de mais de 5 mil porcento! Mas nem preciso dizer o que aconteceu em 2022 né? Caiu mais de 80%. Continua com preço acima do IPO, o que não é ruim considerando todos os fatores, mas a história não acaba aqui.</p>
<p>Ela trabalha direitinho, veja as receitas, crescendo trimestre após trimestre, o que é sempre bom de ver. Ela sempre teve um Ebitda baixo, mas pelo menos era positivo. Em 2020 e 2021 foi quando ela explodiu pelos efeitos da pandemia que expliquei antes. Ebitda excelente, mas chegou 2022 e ela despencou espetacularmente no negativo. Números horrorosos.</p>
<p>E por que? Olhe os gastos e investimentos, vieram crescendo substancialmente e constantemente. E o fluxo de caixa livre? Uma grande bosta. Negativo em 2022, mais de 200 milhões negativo no último trimestre. Essa é uma empresa que sempre se orgulhou dos engenheiros que tem, portanto eles são muito bem compensados com stock options e, de fato, nesse mesmo último trimestre pagou quase 150 milhões de dólares em stock options. É a tendência oposta que um investidor gostaria de ver: olhe a curva de stock options subindo e a de fluxo de caixa decaindo, inversamente proporcionais, horrível.</p>
<p>E não é só isso, sendo uma empresa nova, ela ainda não distribui nenhum dividendo e vem pagando todas essas despesas em boa parte com dinheiro de investidor, diluindo as ações. Veja como shares outstanding só cresce também. Em julho ela demitiu 10% dos funcionários. Essas pessoas tiveram um pouco de sorte porque isso foi antes das demissões em massa das outras big techs, então a maioria conseguiu se recolocar com menos concorrência.</p>
<p>Mas olhando esses gráficos, eles não demitiram o suficiente ainda. Pelo jeito acreditam que o mercado pode virar no curto prazo, mas eu não contaria com isso. Essas contas precisam ser desesperadamente rebalanceadas logo. Eles incharam pra uns 5 mil funcionários. É gente demais. Em setembro demitiram só um pouquinho, meros 70. Mas pra rebalancear precisam cortar muito mais.</p>
<p>Se sairmos olhando todas as tech startups do mercado, todas se parecem com a Shopify, ou muito pior, e passam longe da excelência da Apple. Por isso estão já demitindo em massa ou muito em breve vão ter que demitir. Os fundos de investimentos estão secos. Não tem mais SoftBank pra ficar salvando startup ineficiente. Com os juros altos e inflação alta, vai ser bem mais caro conseguir dinheiro no mercado. E sem fluxo de caixa livre, é questão de tempo pra não conseguirem mais pagar funcionários.</p>
<p>Pra piorar mais ainda, agora em novembro de 2022 também tivemos o crash da corretora FTX de criptomoedas, que expôs diversos defeitos e corrupção em partidos políticos e no governo americano. Em resumo, a FTX de Sam Bankman-Fried cometeu fraude no nível do lendário Bernie Madoff, desfalcou milhares de investidores em bilhões de dólares e causou um efeito dominó que deve estourar em diversas outras tech startups e fintechs, não só de criptomoedas.</p>
<p>Basicamente, quem queria comprar criptomoedas e tokens nessa corretora fazia depósito em dólares pro banco que é parceira deles, a Alameda Research, cujo dono também é o próprio Sam. Ele deixou a ex namorada retardada dele no comando, uma figura que ficava tweetando como é legal tomar anfetaminas. E esse animal era responsável por gerenciar 10 bilhões de dólares. A FTX tinha um token, a FTT, que era negociado na FTX e servia como incentivo pro pessoal continuar transacionando lá e mantendo seus ativos dentro da corretora. Provavelmente aquele esquema de quanto mais tempo você deixa seu dinheiro lá, mais tokens vai ganhando, daí os trouxas não tiram.</p>
<p>O Sam pegava 10 milhões de dólares equivalentes em tokens FTT e usava como colateral, como garantia, pra pedir empréstimo pro banco Alameda. Por exemplo, me empresta 100 milhões de dólares reais e fica com esses 10 milhões em token que eu tirei do Koo, como garantia. E com esse dinheiro ele comprava políticos e celebridades. Vejam, ele chegou a ter um estádio em Miami com seu nome, a FTX Arena. Eles patrocinavam a Mercedes na F1, todo mundo que acompanha viu nos bonés deles. O Sam era parça do Bill Clinton, Leonardo DiCaprio, os típicos manés que todo fraudador adora andar junto e foi um dos maiores patrocinadores da campanha do senil do Joe Biden. Pensem nisso por 2 segundos. E o comercial de TV da FTX tinha ninguém menos que Tom Brady e Gisele Bundchen. Um papelão.</p>
<p>Pois bem, o Sam resolveu criar esse personagem idiota de bilionário nerd do bem. Inventou esse bullshit chamado &ldquo;altruísmo efetivo&rdquo;. Eu nem tô brincando &hellip; Que todo mundo da mídia amava, porque o discurso era que ele queria maximizar quanto dinheiro ganhar pra conseguir maximizar quanto podia doar. A mídia amava ele patrocinando campanhas pró-vacinação de Covid. Era o queridinho da América, capa da Forbes, Fortune e tudo mais. E se envolveu em querer ditar pro Congresso Americano como regulamentação do mercado de criptomoedas deveria ser.</p>
<p>Isso é muito inteligente. Se não dá pra evitar ter regulamentação, a melhor coisa é pagar pra políticos pra desenhar uma que fode ele pouco mas fode bastante toda a concorrência. E nisso, a Binance, a número um desse mercado, não ficou feliz. No começo da FTX, a fundador da Binance, o lendário CZ, fez um bom investimento pra ajudar eles. Mas depois que o Sam começou a apunhalar pelas costas, falando mau deles pra comissão de regulamentação, o CZ resolveu devolver à altura: tweetou que ia liquidar todos os tokens que ganhou pelo investimento na FTX. Um dump colossal.</p>
<p>Quando uma baleia faz um dump desse tamanho, um ativo que era pra ser escasso passa ter em excesso, ou seja, o preço instantaneamente implode e cai como se não houvesse amanhã, e isso gera uma corrida aos bancos, porque ninguém quer ser o último segurando esse token depois que todo mundo vender antes. O valor da FTX, que era todo em cima desse token, despencou de uns 15 bilhões pra menos de 1 bilhão no espaço de poucas horas.</p>
<p>Daí travaram os saques da corretora, vazamentos começaram a acontecer, e descobriram que a corretora não tinha o dinheiro dos investidores na conta. Pior, eles sequem tinham os bitcoins que vendiam, nenhum. O Sam gastou o dinheiro. No desespero, o Sam descobriu que pagar celebridade não serve pra nada, porque na hora do aperto todo mundo some. E foi obrigado a ligar pra ninguém menos que o próprio CZ pedindo pra comprar ele. O CZ tweetou que estava considerando mas no dia seguinte tweetou de novo dizendo que não tinha mais interesse depois de avaliar os números que teve acesso. Foi o prego no caixão.</p>
<p>No mesmo dia a FTX abriu falência. E não eram só duas empresas, a FTX e a Alameda, mas um conglomerado de umas 100 entidades jurídicas que ele criou em torno dessa fraude, um monte de empresas fantasmas pra lavar dinheiro. Uma fraude em proporções bíblicas. E essa novela ainda tá em andamento, o Sam continua foragido, todo mundo tá coçando a cabeça se questionando como confiaram num banco de moleques drogados de 25 anos que praticavam orgias no Bahamas com dinheiro dos outros.</p>
<p>Até o Kevin O&rsquo;Leiry, investidor do programa Shark Tank se ferrrou porque ele investiu e fazia propaganda a favor da FTX. Agora tá se lamentando no TikTok. Puta papelão. Por isso eu falo: nunca acreditem nessa história do fundador shark tank gênio guru abraçador de árvores que quer salvar o planeta, esse é o discurso padrão de todo charlatão ladrão vendendo óleo de cobra. Foi a mesma coisa com o Adam Newmann que fundou a WeWork e fodeu com a SoftBank. Foi a mesma coisa com a Elizabeth Holmes e a Theranos anos antes.</p>
<p>Aliás, Holmes que finalmente foi sentenciada agora no fim de novembro a ter que cumprir 11 anos de prisão. Se não conhecem a história da Theranos tem vários documentários a respeito já, podem procurar. E comparado com a FTX, a Theranos foi brincadeira de criança. O Sam precisa ser condenado a no mínimo o dobro disso sem direito a condicional. Mas sabemos que ele não vai, porque se meteu demais com política. Ou ele termina foragido e some pra sempre ou aparece o corpo com nota de suicídio em breve em algum lugar no sudeste asiático.</p>
<p>Ele fez o papel dele tão direitinho que olha só como a impressa tem tratado esse ladrão. Reuters: Sam Bankman Fried fez um favor ao sistema financeiro. Brincadeira. Na Forbes: Rainha Caroline, a jovem de 29 anos amante de riscos enroscada no colapso da FTX. Forbes virou uma piada. The Washington Post: O colapso da FTX prejudica os esforços do fundador pra prevenir outra pandemia. Caraca, esse povo virou mesmo terra planista. Notem, nenhum chama eles de ladrões ou fraudadores. Estão com pena deles! Agora veja o que o próprio Sam conversou com repórter da Vox quando foi questionado sobre seu discurso sobre ética: ele disse literalmente, é assim que se constrói reputação, nesse jogo idiota ocidental de woke, nós dizemos todas as merdas que eles querem pra que eles gostem da gente.</p>
<p>Todo discurso de sinalização virtuosa é cuidadosamente produzido pra atrair os idiotas online, políticos e celebridades pra ficarem a seu favor. Até um drogado ladrão de 25 anos sabe disso. E todo mundo continua caindo nesse discursinho de salvador da pátria, abraçador de árvore. Vocês são muito burros.</p>
<p>O problema é que nos últimos 10 anos surgiram dezenas de Sams, Adams, Elizabeths e todo mundo investiu bilhões e bilhões, porque tinha dinheiro fácil. De onde vinha esse dinheiro? Nos Estados Unidos toda aposentadoria é privada, tem fundos de pensão que administram o dinheiro de trabalhadores pra eles poderem se aposentar no futuro. No caso da FTX, por exemplo, o fundo de pensão dos professores de Ontario, do Canadá, se ferraram, perderam todo o dinheiro.</p>
<p>E não foi porque eram excessivamente gananciosos. O problema como eu disse é que a taxa de juros americana tava no zero, quase negativo, ou seja, não tinha nenhum investimento seguro como papéis do tesouro pra deixar o dinheiro rendendo. Então todos se viram obrigados a colocar esse dinheiro junto com hedge funds, como uma SoftBank, como uma Sequoia, Tiger, e dezenas de outras. Era só ver os gráficos: toda tech startup só subia, independente se tinha fluxo de caixa livre ou não, era um milagre. Por isso que tinha tanto hipster idiota falando que ninguém mais precisava trabalhar e o governo só deveria imprimir e distribuir mais dinheiro de graça pra todo mundo.</p>
<p>Mas agora a conta chegou. A conta sempre chega. Não existe isso de distribuir dinheiro infinito a custo zero. Fazer discurso bonitinho lacrador não trás fluxo de caixa livre. E no fim é só isso que interessa: fluxo de caixa livre: faturar mais do que gasta. Essa é a conta mais fundamental de todas. Não precisa ser analista cocainado de Wall Street nem engenheiro da Nasa pra entender essa conta: nunca gaste mais do que ganha. Só isso. Fiquem espertos, toda empresa que precisa gastar mais do que ganha, com o discurso que agora é a hora pra conseguir crescer e atingir o bilhão, é um charlatão, e vai implodir em breve.</p>
<p>Veja alguns sinais meio óbvios. O dinheiro tá secando. Investidor tá vazando. Como conseguir mais dinheiro? Começa a vender criptomoeda. Vende bitcoin mas diz que não pode sacar, tem que manter na plataforma. Foi o que a FTX fez. Quem comprou Bitcoin lá nunca realmente teve Bitcoin, a FTX sequer tinha os bitcoins pra eles. Era tudo mentira. Depois invente um NFT, Token, stablecoin ou qualquer coisa assim, mais papel que não vale nada, e bote umas celebridades reconhecidas pra fazer propaganda pra você. Todo mundo que segue esse caminho termina na merda. Escrevam o que estou dizendo, vocês foram avisados. Not your keys, not your coins. Se  não está na carteira que você, e só você controla a chave privada, então essas moedas não existem.</p>
<p>E o que acontece agora? Pra nossa realidade, de programadores, o cenário é o pior possível. Faça as contas. Se todo mundo tá demitindo ou deixando de contratar, vai ficar muito, mas muito mais difícil e concorrido conseguir boas vagas. Quem entrou no mercado faz poucos meses precisa botar a mão na consciência. Todo influencer que vende curso veio com esse discurso idiota de &ldquo;é fácil, faça um bootcamp de 6 meses e tenha emprego garantido ganhando 10 mil reais&rdquo;. Já era balela antes, mas dava pra enganar porque todo mundo via que toda empresa contratava qualquer um sem parar. Era muito fácil cair nesse conto do vigário.</p>
<p>Mas agora não tem mais essa mamata. Eu venho dizendo isso desde que comecei o canal: não acreditem em influencers, e era especificamente influencers que vendem cursos e sonho de emprego garantido que ganha muito fazendo pouco esforço. Teve milhares de programadores que foram demitidos agora, muitos deles formados em faculdades renomadas como Stanford, MIT, Harvard. Esses vão se recolocar fácil.</p>
<p>Todo mundo que só estudou alguns cursos por aí, vai pro fim da fila. Pro fim da fila que vai ter centenas de milhares de pessoas. É óbvio. Se agora a empresa tem opção de contratar alguém que fez 4 anos de faculdade e alguém que fez 4 meses de curso, quem acha que vão escolher? Pior, lembra a idéia de mudar de país? A empresa da Europa, que agora se vê obrigado a cortar custos e demitir. Entre manter um funcionário nascido no mesmo país e um imigrante, quem você acha que eles vão cortar primeiro? Precisa desenhar?</p>
<p>Pra todo jovem que tava em dúvida, ansioso porque via todo influencer botando pressão, falando que fazer faculdade era inútil, que bastava pagar o curso dele que você tava garantido. Acabou a ansiedade. Bloqueie esse influencer, cancele todos os cursos, e vá pra uma faculdade de verdade com boa reputação. Abandone as uniesquinas. Aprenda inglês direitinho. Esqueça os atalhos e caminhos mais curtos. Vai levar uns 2 anos ou mais pro mercado começar a se recuperar, uns 5 anos ou mais pra voltar a crescer como antes. Se você achava que não tinha tempo, agora tem bastante tempo.</p>
<p>Lembra o que eu falei no video de Dimensão do Tempo? Sim, você estava mesmo atrasado pra surfar esse último ciclo. Ele já acabou, mas talvez esteja adiantado pra conseguir surfar o próximo ciclo. E pra isso precisa estar muito melhor preparado que todo mundo, porque a concorrência aumentou exponencialmente. Por um lado isso é ótimo, porque todo freeloader, aproveitador, que só dizia que sabia alguma coisa mas realmente nunca precisou trabalhar de verdade, agora vai ser massacrado pela concorrência massiva. Vai ser o maior tapa na cara da história, e vai doer. E isso vai filtrar o mercado. É uma lei da natureza: seleção natural.</p>
<p>Eu não nasci pra ser cantor, nem artista, nem médico, nem chef, nem nada disso. Eu jamais desrespeitaria essas áreas dizendo que são fáceis e qualquer um pode virar médico ou artista. Porque isso não é verdade. Poucos tem aptidão e principalmente suportam a dor do esforço pra isso. Mesma coisa pra programação, nem todo mundo nasceu pra ser programador. E isso não é um defeito, é a vida. Programação não é fácil. Eu fiz um video inteiro explicando isso. Esqueça o que um influencer vendedor de curso diz: não é fácil. Ele vai sempre te dizer que é, porque obviamente, quer enfiar um curso goela abaixo. Não engula, não consuma mais esses cursos.</p>
<p>Sabe aquela história de programador que diz que é só front-end e não quer aprender nada de back-end? Vai acabar essa mamata também. O full-stack vai voltar a ser a preferência mais valorizada. Programador é programador e não meio-programador. Sabe backend que fala que não quer saber nada de devops? Acabou. Lembra o que eu sempre falo? Numa crise, quem sabe aprender rápido, não ficar só reclamando e sabe se virar, é quem não vai ter problemas. Todo mimizento, limitado, e crocodilo, da boca grande e braço curto, se fodeu. Já não era sem tempo.</p>
<p>Na pior das hipóteses, o crash de 50% que tivemos este ano é só a primeira perna. No crash da Internet de 2001 tivemos uma primeira correção como agora, daí o mercado rapidamente voltou a crescer, o otimismo começou a voltar, e na sequência tivemos uma segunda perna de crash muito pior. Veja nesse gráfico como foi. Essa foi a primeira queda, seguida de otimismo, e uma segunda queda. As coisas só começaram a voltar de novo depois de 2004. Por isso eu falo que pode levar de 2 a 4 anos pro crescimento de verdade voltar. Ou seja, o pior ainda pode estar por vir.</p>
<p>Na melhor das hipóteses, quem sabe o FED realmente consiga controlar a inflação, consiga evitar que o mercado entre em recessão, e com isso comece a baixar as taxas de juros, o que facilitaria acesso a investimentos de novo. Mas eu não vejo isso acontecendo tão cedo. No mínimo, 2023 vai ser um ano perdido pra empresas de tecnologia. Com sorte 2024 pode ser melhor, mas eu não contaria com isso.</p>
<p>Veja o índice NASDAQ que concentra as principais empresa públicas de tecnologia. Em maio atingiu o ponto mais baixo do ano, acumulando queda de 40%. Mas durante o ano ficou subindo e descendo mas já recuperou quase 40%, totalizando queda no ano de só 20%. Se você for otimista, pode argumentar que o mercado vai se recuperar logo. Mas eu acho que estamos em tempo emprestado. O volume de consumo ainda não caiu porque muita gente tá terminando de gastar o que tinha economizado durante a pandemia, somado ao diversos planos de ajuda dos governos. Essas economias tão acabando. Lembra da 2a perda que expliquei sobre a Bolha de 2001? Isso se parece com o otimismo curto antes da 2a perna do crash. Que eu acho que é o mais provável.</p>
<p>Em todos os videos que fiz falando do crash iminente, a idéia não era tentar prever nada, muito menos com exatidão, mas sim explicar o que os dados me diziam, que dados são esses, e mais ou menos como eu estava interpretando, como eu fiz neste também. Dei só alguns poucos exemplos, mas acho que dá pra entender como extrapolar pra avaliar todas as outras empresas mais importantes do mercado e ver se as conclusões são as mesmas. Novamente, não odeiem o mensageiro, eu só entrego as mensagens. É muito possível que eu esteja completamente enganado e daqui 1 ano tudo volta ao normal. Era impossível adivinhar que teríamos uma pandemia em 2020, vai saber o que pode acontecer em 2023.</p>
<p>Eu não tenho prazer nenhum em dar más notícias. Simplesmente descrevo as evidências à medida que elas se apresentam. Eu também trabalho nesse mercado, não é interessante ver ele regredindo. Por outro lado, parte de mim gosta de ver o filtro acontecendo e quem nem é programador mas ficava se achando começar a tomar no Koo. Não sentiremos sua falta. E quem estava sério de verdade na área, vai voltar a ser valorizado como deveria, sem agendas políticas, só resultados.</p>
<p>A dica é simples: a empresa que você trabalha tem fluxo de caixa livre positivo? Então pode ficar menos preocupado. Senão, o que está esperando pra estudar mais? A fila da concorrência tá só crescendo. Se ficaram com dúvidas deixem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal, compartilhem com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>mercado-de-tecnologia</category><category>economia</category><category>startups</category><category>akitando</category></item><item><title>[Akitando] #131 - Sua Segurança é uma DROGA | Gerenciadores de Senhas, 2FA, Encriptação</title><link>https://akitaonrails.github.io/2022/11/08/akitando-131-sua-seguranca-e-uma-droga-gerenciadores-de-senhas-2fa-encriptacao/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/11/08/akitando-131-sua-seguranca-e-uma-droga-gerenciadores-de-senhas-2fa-encriptacao/</guid><pubDate>Tue, 08 Nov 2022 10:20:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/s7ldn31OEFc"
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;Você já deve ter consciência que não deveria estar usando a mesma senha pra tudo, nem senhas curtas, nem senhas fáceis, e não deveria estar recebendo código 2FA via SMS, e deveria ter habilitado encriptação nos seus HDs.&lt;/p&gt;
&lt;p&gt;Mas por quê? Hoje quero demonstrar porque todas essas &amp;ldquo;boas práticas&amp;rdquo; que todo mundo tá careca de saber, e até hoje tem preguiça de colocar em prática, realmente significa. Vamos entender.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/s7ldn31OEFc"
    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>Você já deve ter consciência que não deveria estar usando a mesma senha pra tudo, nem senhas curtas, nem senhas fáceis, e não deveria estar recebendo código 2FA via SMS, e deveria ter habilitado encriptação nos seus HDs.</p>
<p>Mas por quê? Hoje quero demonstrar porque todas essas &ldquo;boas práticas&rdquo; que todo mundo tá careca de saber, e até hoje tem preguiça de colocar em prática, realmente significa. Vamos entender.</p>
<p>== Errata</p>
<p>3:57 - eu falo que o Mitnick não mostra o programa mas eu que não prestei atenção, é o bom e velho HASHCAT. Procurem no Google pra saber a respeito.</p>
<p>== Capítulos</p>
<ul>
<li>00:00 - Intro</li>
<li>01:14 - Cap 01 - Pra que Senhas Fortes? Quebrando senhas</li>
<li>06:11 - Cap 02 - Como se Guarda Senhas no Banco de Dados? bcrypt vs sha512</li>
<li>09:41 - Cap 03 - Pra que Servem Gerenciadores de Senhas? Bitwarden</li>
<li>17:17 - Cap 04 - Como funciona Two-Factor Authentication? TOTP</li>
<li>22:32 - Cap 05 - Como se pega um Ransomware? Pirataria</li>
<li>31:49 - Cap 06 - Encripte seus Drives</li>
<li>33:59 - Cap 07 - Veracrypt: Segredo do Segredo</li>
<li>40:44 - Bloopers</li>
</ul>
<p>== Links</p>
<ul>
<li>How Easy It Is To Crack Your Password, With Kevin Mitnick (<a href="https://www.youtube.com/watch?v=K-96JmC2AkE"target="_blank" rel="noopener">https://www.youtube.com/watch?v=K-96JmC2AkE</a>)</li>
<li>Hashcat (<a href="https://hashcat.net/hashcat/"target="_blank" rel="noopener">https://hashcat.net/hashcat/</a>)</li>
<li>Bitwarden (<a href="https://bitwarden.com/"target="_blank" rel="noopener">https://bitwarden.com/</a>)</li>
<li>Host Your Own Open Source Password Manager (<a href="https://bitwarden.com/blog/host-your-own-open-source-password-manager/"target="_blank" rel="noopener">https://bitwarden.com/blog/host-your-own-open-source-password-manager/</a>)</li>
<li>Authy (<a href="https://authy.com/"target="_blank" rel="noopener">https://authy.com/</a>)</li>
<li>Veracrypt (<a href="https://www.veracrypt.fr/en/Home.html"target="_blank" rel="noopener">https://www.veracrypt.fr/en/Home.html</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>Você é iniciante em programação ou já trabalha com tecnologia? Usa a mesma senha pra tudo? Anota senha num arquivo texto no seu dropbox? Baixa instaladores piratas de coisas como Photoshop ou cheats pros seus jogos? Recebe o código de autenticação two factor via SMS? Pois bem, hoje é o dia de saber porque você tá fazendo tudo errado e também porque.</p>
<p>Mais do que isso, qual a diferença de usar um SHA256 ou bcrypt pra gerar hash de senha pra gravar no banco? Como funciona o algoritmo de two factor? Que aplicativos devo usar de fato pra estar minimamente protegido? Finalmente vamos explorar um pouco mais sobre segurança pessoal e um pouco de como essas coisas funcionam por baixo dos panos. Então o episódio deve servir tanto pra programadores como pra quem só está preocupado em se proteger. E não importa o que faça, você nunca vai estar 100% protegido.</p>
<p>(&hellip;)</p>
<p>Meses atrás eu já tinha feito um video falando um pouco sobre hacking, consultorias de segurança e como esse mercado idiota de selo de segurança é uma grande porcaria. Depois assistam aquele video também. Mas hoje é sobre segurança profissional e não infosec.</p>
<p>A primeira coisa mais básica que todo mundo erra é a famosa recomendação de usar uma senha forte diferente pra cada site que tem conta. Senhas fortes é usar o máximo de caracteres diferentes possíveis, incluindo letras minúsculas e maiúsculas, números, caracteres especiais como cerquilha, asterisco, e ter um comprimento de no mínimo uns 12 caracteres. Ideal é preencher o campo de senha até o máximo que permite. Sites que até hoje limitam a senha a coisas ridículas como 8 ou 10 caracteres foram feitos por imbecis morônicos amadores que não entendem absolutamente nada de segurança.</p>
<p>Vamos entender de verdade porque isso é importante. Tem um video de 2018 de ninguém menos que Kevin Mitnick, um dos hackers mais famosos do mundo. Não porque é o melhor, só que virou celebridade na época em que foi preso. Nesse video ele começa demonstrando como uma pessoa normal poderia pensar em fazer uma senha que acha forte. Pra começar, vai num gerador de palavras aleatórias e escolhe uma que acha comprido, como &ldquo;quadrilateral&rdquo;, que tem nada menos que 13 letras. Olha só, deve ser forte né?</p>
<p>Mas só isso não parece seguro. Vamos adicionar uns números e caracteres especiais no fim como &ldquo;1&rdquo;, &ldquo;2&rdquo;, asterisco e cifrão. Mas mesmo assim ainda não parece seguro. Uma coisa que todo mundo faz é converter palavras em leet codes. Leet code é trocar &ldquo;A&rdquo; pelo número 4 ou arroba, o &ldquo;i&rdquo; pelo número &ldquo;1&rdquo;, &ldquo;e&rdquo; por &ldquo;3&rdquo; e assim por diante. E olha só, agora assim, parece uma senha forte, não parece? Uma senha de 17 caracteres misturando letras, números, caracteres especiais.</p>
<p>A história hipotécia que ele conta é que conseguiu copiar o banco de dados de senhas do Windows de alguém. O Windows antigo não grava sua senha aberta, passa por um algoritmo de hash. Lembra o que é hashing? Eu explico isso no 1o video de criptografia. Assiste depois. É como uma impressão digital, não é encriptação. Ele passa essa senha por uma função que cospe uma string de tamanho fixo. Se fosse uma senha de 2 caracteres ou 100 caracteres, no final essa função cospe uma string com o mesmo tamanho fixo de 512 bits. Ou seja, pra saber se adivinhamos a senha, precisamos passar por essa mesma função e comparar o hash gerado com o hash que estava no banco de dados do Windows.</p>
<p>Todo desenvolvedor web já fez isso alguma vez na vida. Quando o usuário se cadastra no seu site e digita a senha no formulário, você passa por uma função de hashing como SHA512 da vida e grava o hash no banco, na esperança que se alguém um dia conseguir roubar seu banco de dados, pelo menos as senhas não vão estar facilmente visíveis. E agora quero mostrar porque só isso não ajuda em nada e tanto esse seu site quanto a autenticação antiga do Windows com NTLM são ruins.</p>
<p>O Mitnick usa um programa que não mostra qual é pra quebrar essa senha. Provavelmente usa uma mistura de rainbow tables, ataque de dicionário e processamento paralelo em GPUs pra gerar hashes e tentar quebrar na força bruta. Ele já tem o hash que quer achar, e a GPU vai gerando hashes a partir do dicionário e comparando pra ver se acha. Já vou explicar o que é cada uma dessas coisas, mas olhem no video. A máquina dele de 2018 tem nada menos que 8 GPUs GTX 1080 em paralelo. Lembrando que essas GPUs são 3 gerações defasadas, depois veio a RTX 2080, depois saiu a RTX 3080 que por acaso é a que eu tenho, e já foi lançada a nova RTX 4090.</p>
<p>E é isso, em menos de 1 minuto rodando, aquela nossa senha que parecia forte, foi quebrada. Nem 1 minuto. 17 caracteres, com números e letras especiais misturado. Só 1 minuto pra quebrar em 2018. Em 2022, com meia dúzia de RTX 3090 Ti rodando, essa senha pode ser quebrada em segundos. E agora? Tá lembrando a senha que você usa pra proteger seu email? Suas contas pessoais? Olha só, é a mesma senha pra tudo? E sua senha é mais curta e mais fraca que essa que eu mostrei? Nem sei pra que você se incomoda de colocar senha, deixa tudo aberto de uma vez, é a mesma coisa.</p>
<p>&ldquo;Ahhh, mas ninguém nem sabe que eu existo.&rdquo; É verdade. Todo mundo pensa que ninguém vai se importar com você. Afinal você é insignificante, ninguém te segue nas redes sociais, ninguém vai tentar quebrar justamente suas contas. E é aí que você se engana. Todo ano dezenas de sites ao redor do mundo tem sua infraestrutura comprometida por diversos motivos. Por exemplo, em Janeiro de 2022, via uma conta de funcionário interno, vazou uma quantidade gigante de emails, números de telefone e muito mais. Você tem conta no Twitter? Provavelmente seu email agora está no banco de dados de gente que vai tentar quebrar contas.</p>
<p>Mas não é só isso. Existe um site chamado &ldquo;Have I been Pwned&rdquo; que documenta incidentes como esse do Twitter. São eventos grandes e de muita repercussão e no banco de dados deles, no momento que estou escrevendo este episódio, tem documentado a invasão de mais de 600 sites, mais de 11 bilhões de contas que vazaram. Sim, seu email e possivelmente o hash da sua senha, já vazaram e tem robôs nesse exato momento tentando entrar em uma de suas contas.</p>
<p>Antes de falar de como dificultar isso. Vamos entender um pouco mais por baixo dos panos pra você que é programador. Todo site bem feito nunca grava sua senha aberta. Sempre vai passar por um algoritmo de hashing. Mas não é qualquer algoritmo como MD5 ou SHA512. Não, ele vai usar um algoritmo de derivação de chaves, como bcrypt ou PBKDF2 ou o mais novo Argon2. Se você já usou um framework web como Rails ou Django, já deve ter visto esses nomes.</p>
<p>Qual a diferença de um algoritmo de hash como SHA512 e um algoritmo de derivação de chaves como bcrypt? É o tempo de execução. Um SHA512 foi desenhado pra ser eficiente e rápido. Ele é usado pra rapidamente conseguirmos gerar uma impressão digital de um PDF ou algo assim pra assinarmos digitalmente. É bom pra passarmos um arquivo que fizemos download como a ISO de uma distro Linux e checar com o SHA no site deles pra ver se o download veio perfeito. Mas justamente porque é eficiente, que ele não presta pra fazer hash de senhas. Bizarro isso né? Pensa: por que ser eficiente é ruim nesse caso?</p>
<p>Quando um hacker como um Mitnick roda o tal programa dele, precisa ir tentando diversas combinações de letras até achar a sua senha. E a cada tentativa ele precisa rodar pelo mesmo algoritmo de hash pra comparar com o hash que está no banco de dados que foi vazado. Se o algoritmo de hash for eficiente, é rápido gerar milhões ou até bilhões de hashes por segundo. Por isso usamos um bcrypt ou PBKDF2, porque não tem jeito de fazer esses algoritmos serem mais rápidos, faz parte do design deles serem lentos de rodar. Isso dificulta o trabalho de quebrar senhas. As GPUs dele vão executar bem mais lento, de bilhões de hashes por segundo com um SHA512 vai cair pra centenas de hashes de bcrypt. Estamos dificultando a vida do hacker, e isso sempre é bom.</p>
<p>Por isso, se você ver um SHA512 sendo usado pra hashear senhas, tá errado, tem que ser bcrypt ou PBKDF2. Eles são uma categoria diferente. Não são algoritmos de hash e sim de derivação de senhas. Como o nome diz, foram feitos pra pegar uma senha curta ou fraca e tentar derivar uma senha mais longa e mais forte. Além disso, esse é o motivo de porque todo sistema de autenticação minimamente seguro também faz uso de salts.</p>
<p>Salt é um número aleatório. Ele vai gravado aberto na tabela e o hash gerado não é só da senha, e sim da senha concatenado com esse salt. Digamos que duas pessoas no site usem a mesma senha &ldquo;teste1234&rdquo;, que todo mundo usa. O hash dos dois acabaria sendo o mesmo. Portanto quando o hacker quebrar a senha de um, automaticamente tem acesso à conta dos outros usuários que tem a mesma senha. Mas se o salt for aleatório pra cada usuário, o hash dessa senha, concatenado com o salt, vai ser diferente. Agora dificultamos mais ainda pro hacker. Se ele conseguir quebrar a senha de um deles, não tem como saber que o outro também usa a mesma senha. Ele vai precisar repetir a força bruta pros dois, pra chegar na mesma senha.</p>
<p>Mas os hackers não são burros. Obviamente todo mundo sabe que um site bem feito vai usar bcrypt da vida. E sabe que ficar gerando hash de bcrypt é lento. Por isso eles colaboram pra gerar tabelas pré-computadas. Começa pegando um dicionário grande de palavras em inglês. E gera o bcrypt de todas e grava num banco de dados. Agora pega cada palavra e concatena com milhões de combinações diferentes de números e letras especiais, antes e depois. Daí também gera o bcrypt de todas as palavras convertidas em leet code. E assim por diante.</p>
<p>Nas deep web da vida você não acha só o banco de dados vazado dos sites com Twitter ou Adobe. Também tem pra comprar esses bancos de dados de hashes pré-computados. É isso que chamamos de Rainbow Tables. Em vez de gastar tempo computando o bcrypt de tudo na mão, é mais fácil gastar gigabytes ou terabytes de espaço num banco de dados e cruzar com o hash do banco vazado. Comparar milhões de linhas de um banco de dados é super rápido hoje em dia. Em questão de minutos, a poucas horas, dá pra achar todo mundo que usou senhas fracas. Esses vão se foder primeiro. Portanto o hacker troca o trabalho de computar hashes pesadas pelo trabalho mais rápido de procura num banco de dados gigante de hashes pré-computadas.</p>
<p>A única forma de ficar minimamente seguro é usar senhas que não são facilmente deriváveis de palavras em dicionários. Por isso que toda vez falamos que você precisa usar senhas geradas aleatoriamente. Porque não vai ter em nenhum dicionário, o hacker vai ser obrigado a tentar achar via força bruta testando todas as combinações de todos os caracteres possíveis. O alfabeto tem 26 letras. Dobre isso porque tem maiúsculo e minúsculo. Temos 10 números. E temos pelo menos 30 caracteres especiais como asterisco ou parênteses.</p>
<p>Isso dá um total de 26 mais 26 mais 10 mais 30 caracteres que são 92 possíveis letras. Se fizermos uma senha de 10 caracteres aleatórios, o hacker na pior das hipóteses vai precisar calcular hash pra 92 vezes 92 vezes 92 vezes 92 &hellip; 10 vezes. Isso dá nada menos que 43 quintilhões de possibilidades. Se aumentar só um caractere pra 11, aumentamos as possibilidades mais 92 vezes e o número sobe pra 3 sextilhões. Cada caracter a mais dificulta 92 vezes. Quanto mais longa for a senha, mais fode a vida do hacker.</p>
<p>Então vou repetir: nunca use senhas derivadas de palavras que existem. Todas as combinações que você imaginar de embaralhar um pouquinho, tipo colocar arroba no lugar do &ldquo;a&rdquo; ou exclamação no lugar do &ldquo;i&rdquo; ou &ldquo;l&rdquo;, tudo isso já foi pré-computado e existe numa rainbow table. Sempre use senhas totalmente aleatórias. E sempre use senhas o mais longo quanto for possível. 50 caracteres, 80 caracteres. O máximo que o formulário de cadastro do site deixar. E sim, você não deve ser capaz de conseguir decorar essa senha, e esse é o objetivo. Se você consegue decorar, significa que a senha é fraca e facilmente quebrável.</p>
<p>Na pior da pior das hipóteses, todo navegador moderno como Firefox, Safari, Edge ou Chrome, tem já embutido um gerenciador de senhas. Não recomendo usar deles porque tem alternativas melhores, mas eu diria que se você for nesse nível de preguiçoso, pelo menos use do navegador. Único inconveniente é que vai precisar usar o mesmo navegador no seu PC de casa, no notebook e no smartphone, pra ter acesso às suas senhas, porque esses navegadores conseguem sincronizar o banco de dados via sua conta da Apple ou Google ou Microsoft da vida. Significa que pelo menos a versão encriptada dessas senhas pode estar armazenada nos servidores do Google ou Apple. Mas é um risco que vale a pena correr, muito menos risco do que usar a mesma senha fácil pra tudo.</p>
<p>O ideal é não ter esse banco de senhas, mesmo encriptado, no servidor de ninguém e por isso eu gosto do aplicativo Bitwarden. Ele é código-aberto, então você mesmo pode auditar o código se quiser. E tem a opção de você mesmo configurar um servidor pra manter seu próprio banco encriptado de senhas. Daí a extensão no seu navegador vai puxar tudo de um servidor que só você controla. Eu só recomendo fazer isso se você já for um devops avançado e experiente. Pra 99% das pessoas o melhor é confiar mesmo na hospedagem de algum serviço que vai ter segurança melhor do que a sua. Seja o próprio Bitwarden, 1password, Lastpass ou similares.</p>
<p>Olhem um exemplo de senha gerado pelo Bitwarden. É esse tipo de senha que você deve usar nos sites por aí. E como vai estar gravado e encriptado, não precisa se preocupar em decorar nada. Gere uma senha gigante e aleatória pra cada site. Entre em cada um dos sites que tem conta e altere sua senha hoje mesmo. Lembra que eu falei que vira e mexe algum site é hackeado e o banco de dados acaba na mão de algum hacker? Mesmo se ele conseguir quebrar sua senha desse site, ter senhas diferentes significa que mesmo que ele mande os robôs dele ficar tentando em dezenas de outros sites, essa senha não vai abrir nada pra ele, porque só é usado nesse um site. Quando você usa a mesma senha pra tudo, basta um site que você usa se ferrar que ele ganha acesso a todos os outros sites que você usa. Esse é o risco real e imediato.</p>
<p>A única senha que vai precisar decorar é a senha mestra que abre o banco encriptado desses aplicativos. Nesse caso, infelizmente, vai precisar decorar então não dá pra ser uma senha totalmente aleatória, então no mínimo use uma passphrase e não um password. Literalmente pense numa frase longa, de mais de 20 caracteres, no mínimo. Alguma coisa tipo &ldquo;acomidaqueeumaisgostoémilho&rdquo; ou &ldquo;aviagemqueeumaisgosteifoiprojapao&rdquo;. Todos os bons aplicativos vão pedir essa senha da primeira vez pra sincronizar o banco encriptado de senhas e eles costumam ter opção pra habilitar autenticação via impressão digital, no caso de smartphones. Daí fica mais prático de usar.</p>
<p>&ldquo;Akita, qual desses aplicativos é melhor?&rdquo; Foda-se. Use qualquer um deles. Tem gente que morre se não for 1password. Tem gente que acha o LastPass feio. Tem gente que acha um Bitwarden mais ou menos. Tem gente que prefere mesmo usar o do próprio Google ou Microsoft no navegador. Tanto faz. O mais importante é a passphrase mestra e você hoje mesmo sair alterando a senha de todos, todos os sites que acessa, e cadastrar senhas super longas e aleatórias diferentes pra cada uma.</p>
<p>A próxima recomendação é habilitar two factor authentication, o famoso 2FA ou autenticação de dois fatores. Que é aquele código que muda a cada 30 segundos e que recebe via SMS ou usa um aplicativo como Microsoft Authenticator ou Authy. Já de cara vou falar pra evitar o Google Authenticator. Sempre dá problema essa bosta, especialmente se você troca de celular e precisa instalar de novo. Use qualquer um, menos do Google. Aplicativos que gerenciam senhas como 1password ou o Bitwarden, já tem suporte a 2FA também, então pode usar tudo no mesmo aplicativo.</p>
<p>Outra coisa: eu não recomendo de jeito nenhum receber esse código por SMS. Celulares podem ser roubados. E não importa se seu celular tem senha. Basta tirar o SIM card e colocar em outro celular. Ou posso usar engenharia social. Ligar na Vivo ou Claro e enganar o atendente. Um hacker bom de engenharia social já tem seus dados, seu nome, RG, CPF, email, nome da mãe e tudo mais. Não são informações difíceis de achar dado que diversos sites do governo já foram hackeados e bancos de dados com todas essas informações já vazaram. Todo hacker já sabe seus dados.</p>
<p>Conseguir roubar seu número de celular é questão de tempo. Fora o inconveniente se você realmente perder o celular. E agora? Como você faz login nos sites que vão pedir código de dois fatores? Vai te mandar por SMS pro celular que você perdeu ou foi roubado. Até conseguir outro SIM card, vai ficar uns dias sem conseguir acessar suas coisas. Um puta pé no saco. E como Murphy sempre é eficiente nessas horas, vai ser justo num domingo de feriado prolongado e bem num dia que você precisava acessar um site importante no meio de uma viagem.</p>
<p>Por que, lógico, durante essa viagem você encheu a cara na balada e só percebeu que tava sem celular quando voltou pro hotel. É assim que você se ferra à toa. E nem precisa de muito. Basta estar num lugar com sinal fraco de celular ou sem sinal. Ou estar viajando sem roaming. Como vai receber SMS sem sinal?</p>
<p>O que é esse tal de two-factor? É uma função que recebe pelo menos dois parâmetros, um segredo compartilhado e uma hora do sistema e gera um código. O padrão mais conhecido é o TOTP ou Time-based One-Time Password, ou literalmente senha descartável baseada em tempo, que é definida na RFC 6238. O que acontece é o seguinte, digamos que você entre num site novo e vai cadastrar sua conta. Preenche tudo, coloca sua senha e manda habilitar two factor.</p>
<p>O site te dá um QR Code, que tem um código que chamamos de seed ou semente, esse é o segredo compartilhado entre o servidor e nosso app. Se estiver num app como o Bitwarden, vai ter um campo chamado Authenticator Key (TOTP) que é onde se copia e cola o seed, que costuma estar embaixo do QR Code. Ou se estiver num app separado como o Authy, pode abrir a câmera dele e ler o QR Code. Além do seed, nesse QR Code vai ter informações extras pra facilitar o cadastro como o nome do site, URL e tals.</p>
<p>E pronto. O site vai pedir pra confirmar se tudo correu bem, e pra isso é só digitar o código que o app mostra. Ele vai checar e confirmar. Agora, toda vez que fizer um novo login, depois da checagem de senha,  vai pedir o código de two factor. Só abrir o app, e digitar o código dentro da janela de tempo que ele indica.</p>
<p>O que acontece é o seguinte, depois do cadastro via QR Code, o servidor tem um segredo gravado e seu app tem esse segredo também gravado no app, ambos encriptados. Quando você abre o app, ele pega o horário atual, arredondado em períodos de 30 ou 60 segundos, por isso tem o timerzinho toda vez. O código que aparece é derivado de passar o segredo que é o seed mais o horário atual e gerar um hash baseado num algoritmo como HMAC-SHA1. Ele trunca o resultado em 6 dígitos e é isso que você vê no app.</p>
<p>Quando digita o código e manda pro servidor, lá vai fazer a mesma coisa, pegar o segredo, o horário no servidor, e gerar o mesmo código via HMAC-SHA1. Se o código bater com o que você digitou, significa que é você mesmo. Se não bater, pode ser que justo passou o período de 30 ou 60 segundos e vai pedir pra digitar de novo. A vantagem de uma senha secundária dessas é que mesmo que alguém veja esse código, ele só tem validade dentro de uma janela de menos de 1 minuto. Parece pouco tempo, mas na realidade é bastante, já falo sobre isso. De qualquer forma, como é baseado no horário, por isso é importante seu celular ter horário sincronizado com um servidor de NTP. Quando você tem problemas, primeira coisa é checar isso.</p>
<p>Na prática parece um troço complicado. Já tem que ter senha complicada, agora depois de digitar a senha ainda tem esse troço que dá gatilho na minha ansiedade com esse timer maldito mandando digitar rápido. Não é a toa que muita gente mais sensível ou mais preguiçosa realmente não quer ter que lidar com isso. Mas esse é o preço que se paga pela internet ter ficado popular.</p>
<p>Vamos resumir o que falamos até agora: instale um aplicativo de gerenciador de senhas que, de preferência, também tenha suporte a two factor authentication. Minhas escolha pessoal é o Bitwarden, mas é mais por costume. Garanta que a senha mestra é uma frase longa e que realmente só você saiba. Não anote num post-it embaixo do seu teclado. E a partir daí crie senhas hiper longas e aleatórias e habilite two factor em todas as suas contas. Com isso, mesmo se outro site que você tenha conta for invadido e alguém conseguir fazer força bruta pra quebrar sua senha, na pior das hipóteses não vai conseguir usar essa senha em nenhum outro site que você tenha conta, porque são todas com senhas diferentes.</p>
<p>A gente fala two factor ou autenticação de dois fatores, porque em segurança se fala em ter no mínimo 3 fatores de segurança. O mais simples é o alguma coisa que você sabe, que é sua senha. O segundo fator é alguma coisa que você tem, que pode ser seu aplicativo de two factor como o Authy ou o equivalente em hardware disso que seria algo com um Yubikey. Eu nem vou falar de Yubikey hoje, mas teoricamente ele é melhor do que só um app de two factor. Problema que nem tudo suporta hardware keys e a usabilidade não é exatamente a mais simples. Mas vale a pena dar uma pesquisada se tiver interesse.</p>
<p>O terceiro fator é alguma coisa que você é. No caso costumamos nos referir à biometria, como sua face num iPhone ou com Windows Hello, ou o mais comum que é impressão digital. Hoje em dia isso é mais comum em smartphones e muitos notebooks. Antigamente eram muito mau feitos, mas acho que hoje em dia a maioria das implementações é segura o suficiente. Em smartphones como iPhone, sua impressão digital não fica armazenada no mesmo lugar que seus dados e aplicativos. Ele fica numa memória separada que o sistema operacional não tem acesso e controlado por um chip independente da sua CPU.</p>
<p>Isso é o que chamamos de Secure Enclave, que num iPhone é o chip T2, num PC é o chip de TPM ou Trusted Platform Module, que o Windows 11 quis tentar ser obrigatório e deu treta e acho que voltaram atrás. Mas o correto é mesmo ter um TPM no seu sistema, um chip e memória encriptada que mesmo que seu computador seja comprometido por malwares, esse chip continuaria intacto.</p>
<p>E falando em malwares, agora vem a parte mais problemática: evite ao máximo pegar virus e malwares. Parece meio óbvio, mas todo mundo a essa altura já pegou um malware. Se você baixou pirataria recentemente, como photoshop, com certeza já tem malware instalado. Filmes e música tem baixa probabilidade de ter. Só se alguém descobriu um bug nas bibliotecas que fazem decoding de filmes e consegue injetar malware via algum tipo de buffer overflow da vida. Antigamente tinha em imagens, não lembro se era PNG. Mas esses componentes costumam ser bem maduros e não mudam mais, então as chances são baixas.</p>
<p>O problema são executáveis mesmo. Instaladores de Photoshop por exemplo, esses são campeões de carregar coisas como Ransomwares. Se você tem Photoshop instalado, eu faria backup de todos os seus dados importantes num HD externo hoje, logo depois de terminar de assistir, porque provavelmente tem um ransomware encriptando seus arquivos aos poucos em background e você nem tá sabendo.</p>
<p>Povo que é gamer e baixa cheats e trainers pra games, muito provavelmente vocês estão infectados também. Você acha que seu computador tá ficando lento porque tem muitos games. Mas na realidade é porque tem virus e malware rodando aí já. E não adianta baixar um antivirus e tentar rodar. Muitos virus conseguem se esconder de antivirus. Uma vez infectado já é meio tarde.</p>
<p>Se quiser pelo menos checar, tem vários antivirus que tem opção de queimar num pendrive e bootar direto dele. Assim o antivirus roda antes do seu Windows bootar, aí o virus não tem chance de se esconder. Acho que marcas como Kaspersky, Comodo, ESET, Avira e outros tem essa opção. Vá num computador que tem mais certeza que tá limpo, baixe a ISO. Num Windows, use um programa como o Rufus e grave a imagem num pendrive. E agora boote pelo pendrive e mande escanear seu computador. Mas lembre-se, só porque o antivirus não achou nada, não quer dizer que você tá limpo.</p>
<p>A regra é simples: não baixe pirataria. Eu sei, você quer jogar de graça. Mas não rola. Pelo menos tenha um PC numa rede isolada que usa só pra jogar e nunca pra acessar coisas como seu banco, no mesmo computador. Se você instala games piratas baixado em bittorrent ou de sites como mega da vida, as chances de já estar contaminado são enormes. Nem tô brincando, serião. Em particular se você roda Windows.</p>
<p>Quem joga em Linux, usando um ambiente isolado de emulação como Wine com Proton, tem menos chances de ter problemas, porque um virus feito pra Windows não vai infectar um Linux ou um Mac. Virus é específico pro sistema operacional que quer contaminar. Problema de jogar em Linux é que muitos jogos não permitem jogar online em ambiente emulado porque eles não conseguem carregar os programas de anti-cheat. Depende do jogo, acho que um Overwatch da vida já dá pra jogar, mas Fortnite parece que ainda não. Se for como eu que só joga single player, aí Proton já é bem mais útil.</p>
<p>O tipo de malware que ficou mais famoso são os ransomware. Eles ficam trabalhando escondidos no seu PC sem você perceber. Vão encriptando seus arquivos um a um. E quando você percebe que tem algo de errado, a maioria dos seus arquivos tão lá ainda mas encriptados. Daí aparece a famosa janela de resgate, onde falam que tem a chave pra desencriptar, mas só vai te dar se mandar uma certa quantia de bitcoins pra eles. Tem garantia que eles vão te dar a chave? Não. Tem como recuperar os dados depois que foram encriptados. Não. Você se fodeu. Só aceita que dói menos, e você foi avisado, se arriscou porque quis.</p>
<p>A única forma de garantia que você tem caso seus arquivos sejam encriptados por um ransomware é ter backup. E tomar cuidado pra ter várias semanas de backups, porque quando o ransomware começou a criptografar seus arquivos, você pode estar fazendo backup dos arquivos já criptografados. Então precisa garantir que tem versões antigas dos arquivos, de antes do ransomware ter começado a te foder. Por isso eu vim falando do meu NAS e como sistemas de arquivos com suporte a snapshot como btrfs ou ZFS fazem a diferença nesses dados. Eu tenho snapshots de várias semanas e meses no meu backup, então mesmo que um ransomware apareça no meu PC, consigo recuperar praticamente tudo sem pagar nada.</p>
<p>Se você usa Linux, recomendo usar o btrfs e o programa Timeshift pra configurar tudo. Não é tema do video de hoje, mas snapshots são leves, ocupam pouco espaço, e você pode recuperar arquivos que foram apagados meses atrás se configurar direito, e é nativo do sistema operacional. Se usa Windows, precisa ter um backup que suporte um sistema de snapshots, mesmo que seja online. Serviços como o BlackBlaze. Dêem uma olhada. Backup em cloud é melhor que backup nenhum. A desvantagem é que depende da sua conexão de internet. Se perder tudo e precisar recuperar, vai demorar horas ou dias pra baixar tudo de volta via internet. Por isso o melhor é backup físico local, tipo HDs externos ou um NAS de verdade.</p>
<p>Na prática, não instale jogos nem programas piratas. Você vai pegar malwares. Se precisar muito rodar um programa pirata, abra uma máquina virtual, seja o VirtualBox, ou se for pra rodar só uma vez, use o Windows Sandbox, que vem no próprio Windows. Se não sabia disso, basta abrir o programa de ligar funcionalidades opcionais, ticar aqui em Windows Sandbox e deixar ele se instalar e reiniciar. E pronto, olha só, eu abro o Sandbox e é um Windows vazio numa máquina virtual. Quando fechar essa janela, tudo que tem dentro é apagado. Perfeito pra rodar programas estranhos sem riscos pro seu Windows principal.</p>
<p>Pra rodar máquina virtual seu PC precisa ser mais forte que a média. Nada abaixo de 16 giga de RAM. E quanto mais cores tiver sua CPU, melhor, de preferência uma CPU com 8 cores ou mais. Meu PC é um Ryzen 9 5950X com 64 giga de RAM. Isso já é demais pra maioria das pessoas, você não precisa de tudo isso. Eu gosto de brincar com máquinas virtuais porque posso fazer experimentos destrutivos e se eu quebrar a máquina, basta voltar o último snapshot dele que funcionava. Pra testar coisas, estudar sobre redes, estudar sobre vulnerabilidades e segurança, é a melhor coisa.</p>
<p>Falando em Windows, muita gente ignora isso mas depois dos tempos sombrios de Windows XP, que era totalmente furado, o Windows 10 e 11 até que são bonzinhos em segurança. Se seu PC não for muito antigo, configure sua BIOS pra habilitar duas coisas: o TPM que falei antes e o Secure Boot. Secure Boot em particular é bem importante. Se algum malware tentar se injetar em arquivos importantes pro boot do Windows, a própria BIOS vai detectar e impedir o boot. Uma das formas de um virus se esconder de um antivirus é justamente se injetar na kernel ou em drivers e se carregar no boot, daí ele consegue se esconder dos antivirus.</p>
<p>Vale a pena fuçar o aplicativo de Security do Windows. Mas não desligue nada nele. Tem muito tutorial bosta que fala pra desligar coisas como o Defender. Sim, o Defender custa recursos e em máquinas fraquinhas pode deixar um pouco mais lento, mas não, não compensa o risco de desligar segurança. Se tem máquina fraca, o melhor é desistir de Windows e migrar pra Linux se puder. Ou fazer upgrade de hardware, como instalar um SSD caso não tenha.</p>
<p>Um dos grandes problemas do Windows são drivers. Hoje em dia muitos drivers já vem pelo Windows Update e esses teoricamente são checados e são seguros. Mas muito hardware de fabricante bosta ainda manda você baixar driver do site deles, isso quando tem. E quando procura no Google tem sites falsos com esses drivers modificados. Aí você baixa achando que é legítimo e instala um malware. Aliás, nunca baixe nenhum desses programas que prometem atualizar seus drivers automaticamente pra você e também otimizar a performance do seu PC. Muitos são malwares, a maioria é só fearware, software que fica dizendo &ldquo;olha só, achei problemas na sua máquina, me paga que eu conserto&rdquo;. É golpe. Não existe isso.</p>
<p>Não tem performance de graça, mesmo otimizando manualmente, você não vai notar tanta diferença assim de verdade. É tudo fraude. A performance que você tem logo que instala o Windows do zero é tudo que vai ter. Se quiser um pouco mais, de novo, instale Linux e mesmo assim não vai ser nada revolucionário. Trabalhe, economize e compre SSD, mais RAM ou um PC novo.</p>
<p>Por último, se tiver notebook, pense em investir num programa de VPN. Seja ExpressVPN ou NordVPN ou SurfShark ou qualquer um desses. Especialmente se usa muito redes públicas como Wifi em cafés, hotel, ou qualquer lugar fora da sua casa. Eu já expliquei nos episódios de rede como é possível ficar escutando seu tráfego. Além disso também é possível fazer &ldquo;DNS poisoning&rdquo; que é literalmente envenenar o DNS. Lembra como seu computador acha o endereço IP das coisas perguntando pro DNS? E se o DNS ou DHCP que o Wifi público que você se conectou estiver contaminado?</p>
<p>Você pode tentar baixar um programa qualquer e na realidade o DNS envenenado te redirecionou pra um site falso, tipo pra baixar a porcaria do programa de banco de um Itaú da vida. Aí você baixa um instalador falso que vai te contaminar com um malware. Todo cuidado é pouco. Não baixe nada a partir de redes que você não confia, como redes públicas. Sempre use uma VPN pelo menos pra garantir. Em alguns países do mundo como uma China ou no Oriente Médio, seu tráfego pode realmente estar sendo monitorado por agências do governo.</p>
<p>E falando em viagens, a última coisa que você precisa fazer se tem notebook é habilitar encriptação do seu HD. No Windows isso se chama BitLocker, em Macs se chama FileVault e em Linux se chama LUKS. Não importa qual sistema operacional usa, habilite encriptação. Eu não sei se não é óbvio, mas vou dizer o óbvio: aquela senha que você usa pra se logar na sua conta, não protege nada.</p>
<p>Se eu roubar seu notebook, posso tirar o SSD, NVME e plugar em outro computador e vou ter acesso a 100% dos seus dados. Simples assim. Ah sim, você é aquele animal que guarda senha num arquivo de texto no desktop do seu computador? Se eu tiver acesso ao seu computador, vou ler tudo. Macs são mais convenientes ainda. Você pode bootar ele em Target Disk Mode e ele literalmente vira um HD externo USB gigante. Basta eu conectar via USB em outro computador e vou enxergar como se fosse um pendrive gigante. Se não sabia disso, reinicie e deixe apertado a tecla T antes do boot.</p>
<p>Eu já tive um macbook roubado. Bobeei uma vez a noite, numa rua escura, com as janelas abertas e minha mochila no banco de passageiro. Motoqueiro parou do meu lado, apontou uma arma e mandou eu dar a mochila. E lá se foi meu Macbook. Minha única consolação é que tava tudo encriptado, então ninguém ia poder ver nenhum arquivo. E por sorte eu tava com Find my Mac ativado. Isso é uma grande vantagem de Macs e iPhones, que tem isso por padrão. Semanas depois alguém ligou esse Macbook e eu consegui achar ele online e mandei comando de apagar tudo remotamente. Se você tem produtos Apple, ative o Find my Mac e Find my iPhone.</p>
<p>Eu sou tão paranóico com essas coisas que mesmo HDs velhos, eu nunca jogo fora. Mesmo a maioria deles estando encriptado. Até pra destruir HDs você precisa fazer direito, porque um bom investigador forense tem técnicas pra recuperar dados de HDs em condições que você não acredita. Se for jogar um HD fora, pegue pregos, martelo e fure todos os discos. E mesmo usando partição encriptada eu ainda crio discos virtuais encriptados dentro usando um programa que uso faz anos, o Veracrypt.</p>
<p>Pra quem não conhece, o Veracrypt é o antigo TrueCrypt. Ele serve pra criar os discos virtuais mais seguros do mundo. Mesmo partição encriptada, com algum trabalho e força bruta, dá pra quebrar. Isso porque eles usam o mesmo algoritmo toda vez e é conhecido. Já no Veracrypt você pode escolher dentre uma gama de algoritmos, e criar uma partição escondida separada dentro do disco virtual encriptado, então ele teria uma partição falsa que você pode abrir caso esteja com alguém com uma arma na sua cabeça, e uma segunda partição no mesmo arquivo escondido que ninguém vai saber. Sério, dá uma estudada em Veracrypt, é animal e funciona em todo sistema operacional. Arquivos mais confidenciais, eu sempre deixo em discos criptografados por Veracrypt, porque a chance de ser quebrado é muito perto de zero.</p>
<p>So de curiosidade, deixa eu criar um disco virtual com Veracrypt. Aqui na opção de Volume Type posso escolher Hidden volume, ou volume escondido. Seguindo, agora eu posso escolher o algoritmo de encriptação. Pra ser veloz, o certo é escolher AES porque hoje em dia toda CPU acelera encriptação AES em hardware. Mas pra ser seguro, performance não é importante. Eu posso escolher qualquer outro, como esse kuznichik ou sei lá como pronuncia. E o algoritmo de hash vai ser o streebog. Vou criar um volume de 50 mega só pra demonstração aqui.</p>
<p>Agora eu coloco, lógico, uma senha super longa e super complicada como já expliquei antes, um passphrase gigante. Como é um disco virtual, posso escolher que sistema de arquivos quero que seja formatado dentro. Se quiser compatibilide com múltiplos sistemas operacionais, posso formatar em FAT já que o volume é pequeno ou exFAT. Só pra Windows posso escolher NTFS. Se for pra Linux posso escolher ext4. Vou escolher FAT mesmo.</p>
<p>Nesse outro passo ele captura o movimento do meu mouse pra gerar um número aleatório seguro. Em vez de usar um gerador pseudo aleatório como o dispositivo /dev/random que tem no Linux, eu como ser humano me mexendo, vou gerar posições aleatórias de verdade, e ele vai usar isso internamente pra criptografia dele.</p>
<p>Agora vem o truque. Ele terminou de criar o volume, formatar como ext4 e montar como drive. Lembram quando expliquei sobre imagens, drives virtuais e pontos de montagem no video de Slackware? Revejam se não lembram. Olha só, ele montou como &ldquo;/mnt/veracrypt1&rdquo;. É como se fosse um pendrive virtual. Mas essa é a partição falsa. Aqui eu vou gravar arquivos quaisquer que não são secretos de verdade. Por exemplo, vou arrastar uma imagem aqui dentro.</p>
<p>Pronto, ele fechou aquela partição e agora vai abrir outra, a de verdade que é a escondida. De novo, posso escolher outros algoritmos de encriptação pra essa partição, que vai ser protegida com outra senha. Pra simplificar, vou manter as mesmas escolhas de antes. Ele pede pra escolher o tamanho, eu tinha escolhido 50 antes, gravei uma imagem dentro, e agora posso escolher o resto do tamanho pra essa nova partição. Vou escolher tipo 20 mega só. Pra ainda ter espaço sobrando na partição falsa de antes e não ficar muito óbvio que tem uma partição escondida. Pronto, mesma coisa, ele vai formatar e no final eu posso montar.</p>
<p>Vamos montar a partição escondida e criar um arquivo secreto onde eu escreveria segredos altamente confidenciais. Pronto, montou. Navego até o ponto de montagem e olha só, tá vazio. Vamos criar o arquivo &ldquo;segredo.txt&rdquo;, abrimos e vou escrever um texto super secreto. Pronto. Tá salvo. Volto pro Veracrypt e posso desmontar.</p>
<p>Por que eu fiz tudo isso. Digamos que algum malfeitor me pegou de refém e ele sabe que eu tenho segredos nesse arquivo. E ele vai cortar meus dedos se eu ficar falando que esqueci a senha. Então não tem jeito. Vamos montar a imagem, mas vou usar a primeira senha e não a segunda. Olha só o que vai acontecer. Montou, vamos navegar lá. Lembra que na primeira partição eu tinha só gravado uma imagem? É isso que abriu. O malfeitor não sabe que existe uma segunda partição. Mas como eu abri essa, ele não tem porque duvidar de mim. Eu dei a senha pra ele, só não a que ele queria. Nosso segredo está salvo na partição escondida. Sacaram o poder do Veracrypt?</p>
<p>Pra finalizar, quando estava terminando este script alguém me mandou uma DM no Insta com um site no mínimo curioso. Olha só isso. Eu esqueci de gravar a imagem original mas postei esta story no insta no mesmo segundo. Admirem a beleza desse site só por um segundo. É o símbolo de que tudo que eu disse até agora não serve pra absolutamente nada. Porque no final do dia, um monte de gente realmente vai digitar os dados do próprio cartão num site estranho, por livre e espontânea vontade. Os hackers não precisam fazer nada muito avançado. As pessoas deixam as portas de casa abertas elas mesmas. Esse tipo de golpe não só funciona, como funciona muito bem. Não seja um otário.</p>
<p>Não importa as proteções que você tem se você é um otário que sai clicando em tudo que te mandam. Eu fiz um teste outro dia com um tweet de gemidão. Centenas de pessoas caíram e saíram xingando. Foi um dos tweets com mais acesso que eu tive. Todo mundo clica nas coisas. É impressionante. Eu nunca clico em nada. Na verdade, se vem algum email ou mensagem com link, eu imediatamente bloqueio. Nem quero saber quem é. Não tem botão que eu uso mais em rede social que de bloqueio e botão de spam no Gmail.</p>
<p>Olha alguns exemplos de emails que tá no meu spam. Eu não clico em nenhum link. Se eu não sei quem é a pessoa, ela nem devia estar me mandando mensagem. &ldquo;Ah, mas pode ser importante&rdquo;. Eu sempre parto do princípio que se for importante mesmo, essa mesma pessoa vai achar outras formas de entrar em contato comigo. Vai me ligar. Mas se ela acha que eu vou clicar num link, tá muito enganada. O tipo de golpe que de longe é o mais aplicado e de maior sucesso se chama phishing, que é ficar bombardeando as pessoas com mensagens aleatórias assim até que algum idiota clica no link. Vem via email, whatsapp, sms e muito mais. Regra de ouro: JAMAIS CLIQUE EM NENHUM LINK, JAMAIS, ZERO.</p>
<p>E é isso aí. Hoje eu dei uma pincelada rápida por vários conceitos e várias ferramentas simples. São coisas que eu realmente uso no meu dia a dia e venho usando faz anos em Windows, Mac e Linux. Recomendo que todo mundo, seja programador ou não, se interesse por esse assunto, porque no final do dia você só pode contar com você mesmo. Ninguém vai te dar segurança, você precisa criar sua própria segurança, e isso começa com as coisas mais básicas, seu próprio celular, seu próprio computador. Não se preocupar com essas coisas é que nem fazer sexo sem proteção. Uma hora vai dar merda, e quando der não tem mais como voltar atrás. Se ficaram com dúvidas, mandem nos comentários abaixo. 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>seguranca</category><category>produtividade</category><category>akitando</category></item><item><title>[Akitando] #130 - Rant: Projetos, TESTES e Estimativa??? | Rated-R</title><link>https://akitaonrails.github.io/2022/10/25/akitando-130-rant-projetos-testes-e-estimativa-rated-r/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/10/25/akitando-130-rant-projetos-testes-e-estimativa-rated-r/</guid><pubDate>Tue, 25 Oct 2022 10:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/H_-7o_pLn1s"
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 vou discutir um pouco sobre Programação Orientada a Testes e o que isso tem a ver com gerenciamento de projetos, equipes, e um pouco sobre como todo mundo ainda encara estimativas da maneira errada.&lt;/p&gt;
&lt;p&gt;Este video é pra você programador, gerente, cliente, empreendedor, que tem dificuldades de entregar projetos de qualidade. Vamos entender tudo que vocês estão fazendo de errado e porque dá errado.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/H_-7o_pLn1s"
    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 vou discutir um pouco sobre Programação Orientada a Testes e o que isso tem a ver com gerenciamento de projetos, equipes, e um pouco sobre como todo mundo ainda encara estimativas da maneira errada.</p>
<p>Este video é pra você programador, gerente, cliente, empreendedor, que tem dificuldades de entregar projetos de qualidade. Vamos entender tudo que vocês estão fazendo de errado e porque dá errado.</p>
<p>== Conteúdo</p>
<ul>
<li>00:00 - Intro</li>
<li>01:13 - CAP 01 - Suas Premissas estão Erradas - Esqueça Bullshit</li>
<li>05:33 - Programadores não são Pedreiros!</li>
<li>08:31 - CAP 02 - Provas de Conceito - Comecem a Fazer!</li>
<li>13:01 - CAP 03 - Programação Orientada a Testes - Comecem a Fazer!</li>
<li>17:53 - Demonstração de testes de Firefox</li>
<li>19:02 - Demonstração de testes de React</li>
<li>19:27 - Demonstração de testes de Tailwind</li>
<li>19:57 - Demonstração de testes de Django</li>
<li>20:28 - Demonstração de testes de Laravel</li>
<li>21:06 - Demonstração de testes de VSCode</li>
<li>22:14 - Demonstração de testes de Rust</li>
<li>24:02 - Demonstração de testes de Spring Boot</li>
<li>27:25 - Vocês não SABEM fazer testes!</li>
<li>28:45 - CAP 04 - Como a falta de Testes afeta Produtividade? A Raíz do Ágil</li>
<li>32:27 - Como é um fluxo de trabalho Ágil?</li>
<li>37:01 - CAP 05 - O que significa Estimar? Estimar não é Prever</li>
<li>47:27 - CAP 06 - Removendo Maus Elementos. Não acumule problemas.</li>
<li>50:07 - CAP 07 - Resumindo os Princípios: O Mínimo pra Começar</li>
<li>52:27 - Bloopers</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>Vamos dar uma pausa na série de videos técnicos. Se não assistiu ainda, fiz uma minissérie falando sobre redes e logo em seguida começando a falar sobre Linux. Como foi muita coisa técnica, eu me entediei, então resolvi mudar a marcha hoje pra falar sobre gerenciamento de projetos, em particular finalmente quero discutir sobre o famigerado desenvolvimento orientado a testes. É um puta tema cabeludo que daria pra fazer um canal inteiro sobre, mas a idéia é discutir só alguns pontos básicos, pra iniciantes no assunto mesmo. Então vamos lá.</p>
<p>(&hellip;)</p>
<p>Se você já tem experiência, trabalha em grandes empresas e grandes projetos, certamente vai discordar de algumas coisas que vou falar hoje e não está errado. Processos de gerenciamento de projetos de software variam bastante e devem ser customizados especificamente pro seu tipo de empresa, pro seu estilo de trabalho em equipe. Não existe nenhuma metodologia que vai se encaixar magicamente em todos os lugares. Simplesmente não existe, é inútil tentar fazer isso. Eu falei sobre isso no video de Esqueça Metodologias Ágeis. Recomendo assistir esse também depois.</p>
<p>A expectativa é que existe algum conjunto de procedimentos que toda equipe consegue seguir pra manter os projetos estáveis e eficientes. Certamente deve existir algum tipo de PMI, Agile, CMMi, bla bla bla Alguma fórmula que o Spotify, o Netflix, a Microsoft, alguém inventou, que resolve todos os problemas. E não tem. Quanto mais cedo aceitarem isso, menos dor de cabeça vão ter. Se você se ver dizendo &ldquo;a gente segue o modelo Spotify&rdquo; ou segue qualquer modelo, certamente está fazendo errado. Veja no meu video do Guia Definitivo de Organizações.</p>
<p>Além disso, se você é um empreendedor não-técnico, certamente assistiu um monte de palestras motivacionais, bullshit de startups e tá fazendo um monte de coisas erradas. Eu fiz 3 videos pra vocês também, o de Empreendendo Errado com Software e o video em duas partes dos 10 Mitos sobre Tech Startups. Vou tentar não repetir o que já disse lá, então assistam depois também.</p>
<p>Vamos lá, no final do dia, você, como membro de uma equipe de desenvolvimento de software, precisa entregar alguma coisa que seu chefe ou seu cliente precisam no produto deles. Ou mesmo no seu próprio produto. Se for iniciante, é muito mais fácil integrar uma equipe que já existe, que tem membros experientes e processos já em andamento. Um iniciante sempre vai fazer errado da primeira vez, especialmente sozinho. Não tem como acertar de cara sem nunca ter feito, e isso não é um defeito, é natural. São muitas peças móveis que você nunca viu funcionando. É que nem eu dar um carro desmontado e mandar montar tudo e ainda funcionar direito no final. Impossível.</p>
<p>Vai parecer que é simples, só encaixar as peças e apertar os parafusos. Mas não é só encaixar. Precisa monitorar cada pedaço, testar, ver se não tem vazamentos, se tá tudo alinhado, balanceado, etc. Porque se sair só montando tudo sem testar nada, quando terminar e tentar ligar o carro, vai explodir. Ou, o mais provável, nem vai ligar, e ninguém vai saber porque. E agora que tá tudo montado, vai precisar desmontar tudo e ir testando. No mínimo vai ser uma grande perda de tempo.</p>
<p>Antes de mais nada, você precisa de pelo menos um membro da equipe um pouco mais experiente. Nem vou dizer sênior porque não quero ficar tentando definir hoje o que é junior ou sênior, mas alguém que já entregou no mínimo um projeto de sucesso. Porque essa pessoa precisa se responsabilizar pelas decisões técnicas. Decidir qual framework, bibliotecas, módulos, como as coisas vão ficar organizadas, como vai ser feito deployment. E lógico, todo mundo da equipe pode e deve contribuir com idéias, sugestões, mas alguém precisa bater o martelo e todo mundo seguir em frente.</p>
<p>Senão toda hora se perde tempo com discussão inútil. Um grupo cheio de opiniões que leva horas ou dias pra decidir as coisas é ineficiente e o resultado sempre vai ser uma droga. Aliás, se isso acontece, é um bom sinal que o tal sênior responsável é ruim. Nenhuma equipe respeita alguém que só fala mas não faz. Um bom sênior fala menos e mostra mais, ensina como faz na prática. E o melhor é errar cedo, ter evidências de porque a primeira decisão deu errado, e isso servir de input pra uma segunda decisão melhor. Do que ficar horas discutindo o melhor caminho, pra no final dar errado de qualquer jeito.</p>
<p>Isso talvez seja a coisa mais fundamental numa equipe. Errar antes e consertar rápido é infinitamente melhor do que horas e horas de discussão tentando chegar em consenso e bikeshedding. Ninguém tem certeza? Foda-se. Cara ou coroa. Faz um teste. Vê se funciona. Se falhar, agora veja porque falhou, conserta, e segue em frente. Essa é a essência do processo de lidar com o desconhecido. Todo mundo pensa que quando algo é desconhecido o certo é tentar planejar infinitamente até ter todos os passos minuciosamente detalhados. Mas tá errado. É desconhecido, qualquer planejamento que tentar fazer, quanto mais detalhado for, mais errado vai estar.</p>
<p>Acho que esse é o ponto principal da discussão de hoje. Quando você e sua equipe são inexperientes, das duas uma, ou vão andar super lento porque vão tentar planejar em excesso, discutir em excesso, ou vão ser kamikazes retardados que vão começar a fazer e só ir fazendo sem parar pra ver se estão indo na direção certa. Ambos estão errados. Não consigo entender porque o desespero não leva ao mais óbvio, que é errar cedo, consertar rápido e o mais importante: garantir que o que já foi feito e está certo, não quebre à toa, porque isso seria o equivalente a andar pra trás.</p>
<p>Não tem nenhum problema rascunhar o que precisa ser feito. Faça um checklist de funcionalidades. Faça post-its com cada tarefa. Faça uma planilha. Não importa. Todo mundo da equipe precisa ter uma noção geral do que precisa ser feito. Mas não tente detalhar em excesso. Só coloque detalhes no que estiver muito incerto, no momento em que precisa ser feito e não com meses de antecedência. Portanto, qualquer um que tenha estudado engenharia de software ou gerenciamento de projetos tradicional, e viu coisas como requisitos, casos de uso, ou até mesmo stories, pára. Você só precisa de uma lista, curta, com poucas palavras. Não é pra levar dias escrevendo, é pra levar horas, no máximo. Vou voltar nesse ponto mais pro fim.</p>
<p>Não importa quantos diagramas, fluxogramas, photoshops ou words vocês produzam, nada disso é o software. Pra um amador, dá impressão que o ideal é ter o máximo de páginas de requerimentos detalhados quanto possível. Tá errado. Essa noção errada vem pelo seguinte: qualquer projeto de engenharia civil, pra fazer uma casa ou um prédio, precisa de meses de planejamento, várias plantas baixas, diagramas de encanamento, diagrama elétrico e tudo mais, pra garantir que o prédio seja construído perfeitamente dentro das especificações.</p>
<p>Portanto, software deveria ser a mesma coisa. Vamos fazer quinhentas páginas de diagramas UML, que é o equivalente a planta baixa, porque daí, quando for escrever o software propriamente dito, vai sair tudo perfeitamente dentro das especificações. Essa premissa está errada. Todo mundo acha que um programador é um pedreiro. Não querendo desvalorizar o trabalho de pedreiros, mas um programador, na realidade, é o arquiteto. O trabalho de pedreiro, é 100% automático desde sempre. O pedreiro de software é o compilador ou interpretador. A planta baixa É o código fonte do software. Portanto todo programador, quer ele queira ou não, faz trabalho de um arquiteto.</p>
<p>Lógico, se sua linha de trabalho é só instalar Wordpress pra diversos clientes. Não se está produzindo software novo, só copiando e colando o que já existe e esse trabalho, sim, parece mais uma linha de fábrica. Mas estamos falando de fazer software que não existe, quase do zero. Reusando vários componentes como frameworks ou bibliotecas, sim, mas o software final não seria possível fazer numa linha de fábrica. Cada passo no desenvolvimento desse software requer tomadas de decisões de cada programador envolvido. Não existe receita.</p>
<p>O famoso exemplo de fazer um botão com cor na tela. Dá pra fazer de dezenas de jeitos diferentes. Posso fazer todo o CSS e HTML do zero. Posso escolher usar um Tailwind da vida. Mas se escolher isso, não pode ser só pra um botão, o certo é usar em todos os botões e elementos gráficos de todas as telas. Eu quero isso? Posso escolher fazer todo o sistema de eventos do zero ou usar React ou Vue. Mas de novo, isso vai afetar todo o sistema. Qual minha equipe tem mais facilidade pra usar? Já tinha começado como um sistema em Angular? Se eu decidir que quero usar Vue no lugar, quanto custa reescrever tudo? Vale a pena?</p>
<p>Tudo são decisões. Muitas sem grandes consequências, mas algumas podem quebrar seu projeto inteiro se feito de forma irresponsável. Como se toma esse tipo de decisão? Não tem receita pra isso. Na maioria das vezes, seu prazo tá apertado, e o ideal é não escolher decisões que envolvam jogar tudo fora e fazer tudo de novo, então isso já elimina várias escolhas. Mas algumas são incertas, se der certo talvez vocês ganhem produtividade pra frente. Pra isso se fazem provas de conceito.</p>
<p>Todo mundo ignora o poder das provas de conceito. Prova de conceito ou protótipos são pedaços de código, uma pequena parte da aplicação, que deve ser feito com o objetivo de potencialmente jogar fora depois. Portanto não precisa ser integrado ao código principal. Pode ser feito como uma branch do repositório, o que num GitHub chamamos de pull requests ou num GitLab chamamos de merge requests. Tecnicamente é um desvio no repositório de código. Enquanto todo mundo continua trabalhando no tronco principal, a branch <code>main</code>, alguém trabalha nesse desvio, num galho, por alguns poucos dias, pra ver na prática se a mudança sugerida realmente trás benefícios ou não.</p>
<p>Provas de conceito tem que ser um empreendimento curto, um micro-projeto de prazo fixo. Coisa de 2 dias, uma semana no máximo. Mesmo estando incompleto, no final desse período deve dar pra equipe ver na prática como a idéia se materializa em código, os impactos que causa, e se todo mundo está confortável em seguir em frente. Daí pode até jogar fora esse galho e todo mundo voltar pro tronco principal aplicando seja lá qual seja essa mudança técnica. Ou então se prova que a idéia era mais complexa do que se imaginava, e todo mundo descarta essa idéia. Mas isso não foi perda de tempo.</p>
<p>Você tem 10 grandes dúvidas técnicas que podem custar muito tempo, coisas no nível: devemos usar React ou Vue? Devemos ficar no Postgres ou migrar pra Firebase? Devemos ficar só em APIs REST ou já começar tudo em GraphQL? Vamos dar deploy das coisas em Vercel ou criar Terraform pra AWS? Só que ninguém da equipe nunca teve muita experiência em nenhuma dessas coisas. Cada uma pode potencialmente custar 2 a 4 meses. Sua equipe só tem 3 pessoas. Então fácil, se for fazer tudo, não vai sair por menos que 10 meses pra equipe, e com a falta de experiência, fácil vai virar o ano.</p>
<p>Em vez disso, pára tudo, cada um da equipe vai fazer provas de conceito de cada coisa, num prazo máximo de 2 semanas. E esse prazo é fixo, depois de 2 semanas vamos ver o que saiu. No final, mais da metade dessas coisas se provam realmente mais complicadas ou o custo claramente não vai compensar. Em vez de ir em frente com 10 coisas incertas que vai custar pelo menos 1 ano pra fazer, vocês concordam agora em desistir de 7 delas, porque a prova de conceito deu evidências que não vai dar certo. Por outro lado, 3 dessas coisas se provaram boas, e vocês vão seguir em frente.</p>
<p>Ao custo de 2 semanas da equipe, vocês economizaram mais de meio ano de trabalho que ia dar errado, e vão prosseguir com 3 coisas que vão custar um mês da equipe e que todo mundo tem mais certeza que vai dar certo. Esse é o grande truque: não se comprometa com coisas de longo prazo que ninguém tem certeza. Pague um tempo curto pra descobrir se dá pra cobrir essa incerteza. Não é jogar tempo fora, é pagar um pouco pra não jogar muito mais fora. Essa noção deveria ser simples, mas na minha experiência, pouca gente parece entender que mais vale a pena jogar fora 2 semanas do que se comprometer cegamente com um projeto incerto que vai levar um ano.</p>
<p>Na cabeça de muita gente, como 1 ano tá longe, fica aquela sensação de &ldquo;vamos indo, lá na frente a gente vê como faz&rdquo;. Especialmente em tech startups que vivem de dinheiro de investimento, esse tipo de decisão errada acontece muito, porque afinal é muito mais fácil gastar o dinheiro dos outros. E mesmo quem tem que gastar do próprio bolso, fica apreensivo com essa noção de &ldquo;jogar 2 semanas fora&rdquo;. Só que não é &ldquo;jogar fora&rdquo;, é o mesmo que pagar um seguro.</p>
<p>Ninguém gosta de pagar mil reais, dois mil reais, todo ano, que ao final do ano, parece que não serviu pra nada. Só que se durante esse ano tiver um acidente, e você atropelar e machucar alguém, agora não é dois mil reais que vai custar, vai ser 100 mil, 200 mil reais. Quem faz economia porca e fica de bravata achando que &ldquo;não precisa, não vai acontecer&rdquo;, é o primeiro que se envolve em acidentes e depois fica falido. Em projetos é a mesma coisa. Se for coisa que você já faz várias vezes antes, o risco é baixo, já sabe o que tem que fazer, tem noção de quanto vai custar e mais ou menos sabe o que pode ou não dar errado, então consegue ir em frente. Mas se nunca fez, é melhor fazer um teste antes de decidir.</p>
<p>E caindo no dia a dia de programação, é esse o mesmo argumento de porque falamos que todo programador deveria fazer, no mínimo, testes unitários de tudo que está trabalhando. Toda nova funcionalidade, deveria ser coberta de testes. Toda correção de bugs, deveria começar com um teste que simula o bug, pra que já comece falhando, e você sabe que corrigiu quando esse teste passa. Não só isso, esse bug em particular não vai acontecer de novo. Todo teste que você começa escrevendo antes de fazer o código, é como uma mini prova de conceito antes de tentar fazer uma coisa que não tem muita certeza. Se não tem certeza, comece escrevendo um teste.</p>
<p>Muitas pessoas já me pediram pra fazer um video sobre testes, mas eu realmente não tenho vontade, porque testes é um assunto que tem já toneladas de documentação, livros, tutoriais, cursos, blog posts, PDFs gratuitos online, pra cada linguagem diferente, pra cada framework. Testes não é um troço que você aprende em 5 minutos e faz igual pra sempre. É um conjunto de técnicas. O ideal é um junior parear com alguém mais experiente e aprender na prática: quando precisa fazer, e com que nível de detalhes. É muito importante que sua equipe tenha pelo menos uma pessoa mais experiente que entende a importância de ter testes e oriente os demais a fazer.</p>
<p>Deixa eu fazer o sales pitch de 2 minutos de porque todo programador deve fazer testes, de porque nenhum pull request pode ser mergeado sem testes, e porque uma pessoa que se diz sênior e é contra fazer testes, por definição, não deveria se considerar sênior. Toda regressão é tempo e dinheiro jogado fora. E o que é uma regressão?</p>
<p>Regressão é exatamente o que o nome diz. Se até este momento, digamos que alguém de QA testou tudo e tinha zero bugs. Alguém me faz um pull request que ficou trabalhando sem integrar por duas semanas. Sem testar. E dá merge na master. Faz deploy, e o que acontece? Surgem um monte de bugs. Não só na funcionalidade que ele tava trabalhando, mas em outros lugares que antes funcionava. Esse amador regrediu o trabalho de todo mundo. Tudo deu passos pra trás.</p>
<p>Agora o certo é dar revert nesse pull request, reverter mesmo. Só que esse pull request não tem mais como voltar atrás, porque dar deploy, ele alterou o schema do banco de dados e não tem como voltar mais ao schema anterior sem um grande downtime. Um desastre. E, claro, tenha certeza que esse indivíduo fez esse deploy na tarde de sexta-feira. Lógico. Cancela o happy hour porque a noite de sexta vai ser longa, fim de semana adentro, pra consertar essa cagada.</p>
<p>O fluxo é simples: todo programador, seja fazendo testes-antes ou testes-depois, quando diz que concluiu seu pull request, também adicionou um mínimo de testes, fez rebase com a master, e indicou pra equipe que tá tudo pronto. Alguém da equipe sempre deve avaliar esse pull request, normalmente alguém mais experiente. Pelo menos pra bater o olho e ver que não tem nada que parece muito mau feito ou muito fora do lugar e, principalmente, se tem testes. Se não tiver, já rejeita.</p>
<p>Se for uma equipe minimamente bem equipada, está usando repositórios privados de Git seja num GitHub ou GitLab. Ambos tem suporte a rodar testes automatizados num serviço separado. Nesse momento ele já puxou o que está nesse pull request, subiu um container de docker, e executou os testes automatizados do projeto. E quando o avaliador for checar o pull request, já vai saber inclusive se os testes passaram ou não. O GitLab tem a própria ferramenta que é o GitLab CI, de Continuous Integration. Tem diversos serviços externos que integram com GitHub como CircleCI, Travis CI, Semaphore e outros. É super simples configurar no seu projeto e não vejo nenhum motivo pra não usar.</p>
<p>Se o pull request parece que está ok, e o CI diz que os testes passaram, só agora é permitido fazer merge desse código novo na master. No mínimo, sabemos que as funcionalidades que funcionavam antes, cobertas com testes, continuam funcionando depois, mesmo com a inclusão desse código novo. É um bom indicativo pra toda a equipe que as coisas estão andando pra frente, e não ficando com débito técnico acumulado pra trás. Eu vi um tweet alguns dias atrás, de alguém que tava relatando como um idiota da equipe resolveu atualizar a versão do Java, acho que do 16 pro 18 e fez deploy pra produção. Óbvio que deu pau. Óbvio que o pau apareceu na sexta-feira.</p>
<p>Tem idiota que acha que porque usa uma linguagem compilada, com tipagem estática como Java, não tem problema não ter testes, porque se tudo compila, obviamente é porque funciona. Não tem nada que é mais certificado de júnior, do que pensar assim. Ahhnnnn como assim? Voltando ao exemplo de software livre. Vamos ver o projeto do navegador Firefox. Sabe Firefox? Sim, aquele Firefox. Eu baixei o código aqui na minha máquina. Firefox é feito na maior parte em C++, um pouco de Rust, um pouco de Javascript. É um navegador bem estável e quem usa acho que raramente tem algum problema dele renderizar errado, crashear ou coisas estranhas assim.</p>
<p>Se você é desenvolvedor de front-end, certamente já usou o Developer Tools certo? Pra debugar o que está programando. E esse Developer Tools tem uma suite de testes, pra garantir que toda nova versão, antes de lançar pra você baixar na sua máquina, passa todos os testes e não introduz nenhuma regressão. Ou seja, tudo que funcionava numa versão continua funcionando na mais nova. No repositório de código fonte, só rodar <code>mach test devtools/*</code> e olha só, abre a versão que acabei de compilar direto do código fonte e roda uma suite de testes de integração, simulando cada funcionalidade do Developer Tools. É assim que a Mozilla garante que código novo não introduz regressões óbvias.</p>
<p>Se der uma fuçada no código, olha só, cada diretório tem um sub-diretório de testes. E muitos deles tem testes específicos pra bugs que foram reportados. Assim eles garantem que bugs corrigidos também não vão aparecer do nada de novo. Bugs que foram corrigidos e aparecem de novo porque não tinha um teste pra cobrir, se chama &ldquo;jogar dinheiro fora&rdquo;. Porque tem que corrigir mais de uma vez. Ao longo do tempo, você está perdendo tempo, dinheiro, eficiência, e mercado pra outro projeto que vai fazer melhor que você com menos recursos.</p>
<p>Tem gente que acha que porque é programador front-end, não precisa se preocupar com testes. Acho que todo mundo gosta do projeto React do Facebook, quer dizer &ldquo;meta&rdquo;, certo? Eu baixei aqui o código fonte da biblioteca React. E olha só, se rodar <code>yarn test</code>, o React tem uma suite de testes feita em Jest. Pelo mesmo objetivo. Mais de 7 mil testes unitários garantem que quando você atualiza o React, não vai quebrar tudo.</p>
<p>Sabe aquela biblioteca de estilos, o TailwindCSS? Um amador poderia imaginar que é só uma coleção de CSS, obviamente não precisa de testes, certo? Errado. Eu baixei o código fonte do Tailwind e adivinha, se rodar <code>yarn test</code>, mais testes unitários em Jest. Quase 900 testes unitários, fora checagens com eslint que por acaso deu alguns erros. Eu baixei do branch principal, ainda estão mexendo e na próxima versão esses bugs serão corrigidos. Mas está claro o que funciona e o que não funciona pra quem está contribuindo.</p>
<p>Bah. Esse povo de front tá muito fresco. Ficar fazendo testes é coisa de hipster. Certamente povo de Python, mais calejado, mais raíz, não se importa com essas besteiras. Por isso eu baixei aqui o código fonte do framework Django. E olha só, tem um diretório chamado <code>tests</code> e dentro eu posso rodar esse script chamado <code>runtests.py</code>. E lá vai ele rodar centenas de testes pra garantir também que versões novas do Django não tá quebrando nada que já funcionava antes. Mais de 16 mil testes. Passando bonitinho.</p>
<p>Foda-se povo de Javascript e Python. Vamos mudar pra PHP. PHP sempre foi conhecido por ser Quick and Dirty, rápido e sujo. Código raíz. Programador Cowboy que programa orientado a gambiarra. POG-zão na veia. Eu baixei aqui o código fonte do framework Laravel que é um dos mais usados hoje. Só que não. Eu posso rodar <code>composer exec phpunit</code> e olha só, quase 8 mil testes, de novo, pra garantir que a versão mais nova não introduz nenhuma regressão. Por acaso falhou, povo ainda tá mexendo, próxima versão estável certamente já vai ter corrigido esse bug. Mas nós sabemos que tem o bug, porque tem suite de testes.</p>
<p>Aliás, sabe o editor de textos mais usados por programadores hoje? O tal do Visual Studio Code? Provavelmente até você assistindo que não gosta de testes aí usa pra fazer seus códigos porcaria. Eu baixei o código fonte também e tem testes automatizados rodando <code>yarn smoketest</code>, que segundo a documentação deles, é rodado antes de gerar o binário final que você baixa pra instalar. Igual o Firefox, tem testes de integração onde abre o aplicativo e vai testando cada funcionalidade que a gente aceita que simplesmente funciona em toda nova versão. E tudo só funciona, porque tem testes pra garantir isso.</p>
<p>E voltando ao Firefox. Parte dele é feito em Rust. E pra você, seu infeliz, que ainda acha que só porque o código compila, tá tudo bem? Não era o Rust que tem o tal compilador mais inteligente de todos os tempos? Que muita gente acha que é mágica e garante não só binário super performático como se tivesse sido feito em C e sem bugs de gerenciamento de memória e problemas de segurança? Muita gente só falta dizer que o compilador escreve o código pra você, de tão bom que ele é. Certamente, se o código em Rust compila, não precisa de testes, certo?</p>
<p>Bom, eu baixei o código fonte da biblioteca Tokio, que é o framework pra desenvolvimento assíncrono em aplicativos feitos em Rust. Todo projeto Rust acompanha um arquivo <code>Cargo.toml</code>, assim como todo projeto Javascript acompanha um <code>package.json</code>. E assim como existe npm ou yarn, em Rust temos o comando <code>cargo</code>. <code>cargo install</code> é equivalente a um <code>npm install</code>. E adivinha, o próprio cargo, ferramenta padrão que já vem com o Rust, tem essa opção <code>test</code>. E o que isso faz? Vamos ver. Só executar <code>cargo test</code>. Olha só ele rodando a suite de testes da biblioteca Tokio.</p>
<p>Ou seja, nem mesmo um projeto em Rust, que é armado com o compilador que é considerado a obra prima dos compiladores, evita os autores do Tokio de não fazer testes. Aliás, pode ver, ele sai rodando centenas de testes, não é um ou dois. Ahhh mas só de compilar já devia ser suficiente &hellip; não, sua múmia paralítica, os criadores do Rust armaram o Cargo pra rodar testes. Você acha que você, logo você, entende mais do que os criadores do Rust?</p>
<p>E você que é de Java e também detesta testes, que tem essa noção atrasada de porque o Java compila, tem tipos estáticos, então se compila tá tudo certo. Você que acha que só linguagens dinâmicas não compiladas como Ruby ou Javascript que precisam de testes. Sabe onde nasceu o conceito de TDD? Foi com Kent Beck, que fez o primeiro jUnit, em Java. Desde os anos 90 a gente sabe que só porque alguma coisa compila, não quer dizer que funciona. Só porque compila não significa que bugs não foram introduzidos. E por ficarem cansados de toda hora ter que correr atrás do próprio rabo, de ficar corrigindo coisa que já funcionava, que os agilistas originais formalizaram o conceito de testes.</p>
<p>Aliás, falei de Java e não mostrei Java. Só de exemplo, baixei o código fonte de um dos frameworks web mais usados que é o Spring Boot. Rodando o bom e velho <code>gradlew build</code> olha só. Baixa todas as dependências, junto com a internet inteira duas vezes, e roda todos os testes automatizados na sequência. O build só é considerado ok se compila e os testes rodam sem erro até o fim. E são milhares de testes. Ué, mas não era só compilar que já tava garantido que funciona? Obviamente não.</p>
<p>Muito antes de existir o conceito de TDD a gente já fazia testes automatizados de um jeito ou de outro. Toda empresa de software madura faz testes. É por isso que seus programas e seu sistema operacional não quebram o tempo todo, toda hora. E mesmo com tudo isso, ainda assim bugs acontecem. Significa que se não tivessem todos esses testes, teríamos ordens de grandeza mais bugs em tudo. Novas versões iam demorar ordens de grandeza mais pra sair. Ia precisar de ordens de grandeza mais pessoas testando. Mas não. Hoje em dia quase todo dia algum programa que você tem instalado ganha uma versão nova, mas você raramente é incomodado. Bugs acontecem, mas são raros. E a razão disso é que todo software maduro, proprietário ou de código aberto, tem testes.</p>
<p>Veja meu blogzinho feito em Ruby on Rails. Ele é ridiculamente simples. Eu mesmo que fiz em 2012. Não lembro se foi em Rails versão 3 ou 4. De lá pra cá eu atualizei até o Rails 6 rodando em Ruby 3. Toda vez que sai versão nova da linguagem ou do framework, eu atualizo, pra ganhar as correções de segurança principalmente. Eu venho atualizando faz 10 anos. E toda vez atualizo as bibliotecas também. Mas diferente do tweet da pessoa que falou que o cara atualizou o Java e subiu pra produção. Antes eu rodo meus testes em Rspec. Se não passa, obviamente não subo. Faço pequenos ajustes quando precisa, se a API de alguma biblioteca mudou. Mas meus testes pegam a maior parte dos problemas. Uma vez por ano, gasto 30 minutos, atualizo as coisas e subo.</p>
<p>Ter testes não é burocracia, não é perda de tempo. É um seguro. Não fazer testes é a mesma coisa que dirigir sem seguro. Na maior parte do tempo, não parece ter muito valor mesmo. Sempre dá pra fazer tudo rápido, sujo e na gambiarra. Você se sente o herói de entregar as coisas rápido. Mas é inevitável, sua produtividade, e da sua equipe, vão gradativamente caindo. Porque mais e mais se perde tempo corrigindo bugs do que fazendo coisa nova. Em breve, a maior parte do seu tempo é só corrigindo bugs. E o que o amador fala? Ahhh, vamos mudar pro framework mais novo. Vamos mudar pra linguagem mais da hora. Quantos de vocês conseguiram manter e atualizar o mesmo sistema por uma década?</p>
<p>Não existe nenhuma linguagem inventada na história que só pelo fato de compilar garante que não precisa ter testes. Todo mundo que entendia isso já fazia testes de alguma forma. A gente fazia alguns scripts separados do código pra rodar partes que eram mais sensíveis se quebrasse, ou que sabíamos que sempre alguém fazia merda e quebrava. Era uma das coisas que separavam os amadores dos espertos. O que aconteceu foi que as comunidade ágeis deram nomes aos bois e criaram convenções e semânticas pra todo mundo falar a mesma língua. Mas não existiu uma data especial que divide antes, com zero testes, e depois, com testes. Foi gradual, onde décadas atirando no próprio pé naturalmente levaram os programadores mais experientes a chegar na mesma conclusão.</p>
<p>Enfim. O problema é que a maioria de vocês assistindo detesta fazer testes. A desculpa é perda de tempo. Mas a verdade é porque vocês não conseguem fazer. Vocês acham que ninguém percebe. Todo mundo que se sente incapaz de fazer alguma coisa, tenta denegrir esse alguma coisa. E é óbvio, não sabe fazer porque nunca faz. Assim como tudo em programação, fazer testes depende de treino e prática. Não existe nenhum livro ou curso que dá pra ler e automaticamente sair fazendo testes perfeitos.</p>
<p>Você que é front, assistiu um curso e já saiu criando interfaces do nível de complexidade de um Canvas instantaneamente? Ou precisou de semanas e semanas apanhando de CSS obscuro? Ou você que é back, só leu um tutorial e já saiu criando APIs perfeitas em GraphQL integrado com backend de Firebase? Em 5 minutos já saiu codando como se não houvesse amanhã? Duvido.</p>
<p>Não adianta ficar só procurando coisa pra ler. Precisa praticar. Assim como tudo que já fez, todos os seus primeiros testes vão ser uma bosta. Mas é assim que começa. Eu citei vários projetos de código aberto. Dá uma olhada nos testes deles pra se inspirar. Tem centenas de outros projetos mais parecidos com o que estiver trabalhando agora. Baixa e lê os testes deles. Copia. Altera. Testa. Joga fora. Começa de novo. Repete. É assim mesmo.</p>
<p>Outra desculpa é falar que o projeto já tá faz meses ou até anos sem nenhum teste. É o cúmulo da preguiça ouvir isso. Ótimo. Faça você o primeiro teste. Pelo menos essa uma funcionalidade que acabou de entregar, está coberto e vai ser mais difícil de aparecer bug aleatório pela falta de atenção de outro programador depois. E se alguém pisar em cima do seu código, só de rodar esse um teste, dá pra saber que quebrou, quem foi, em qual commit, e corrigir mais fácil. De 1 teste em 1 teste, a cobertura naturalmente aumenta.</p>
<p>Alguém reportou um bug? Abriu um ticket? Faça o teste que simula o bug reportado. Agora corrige. Pronto, esse é 1 bug que dificilmente vai aparecer de novo. E olha só, do nada, você começou a andar pra frente em vez de ficar só olhando pra trás. E total de cobertura? Precisa ser 100%? Isso é outra pergunta idiota que sempre aparece numa discussão de quem é desleixado. Foda-se. Você não fez nem 1% ainda, pra que quer discutir 100%? Não interessa. Faz 50%, faz 500%. Só, faz, a, porra, dos, testes.</p>
<p>O que acontece é o seguinte. Sua equipe tem 3 pessoas. Cada um consegue pegar 2 ou 3 tarefas do backlog por semana. 6 a 9 tarefas entregas toda semana. Se o sistema começa a ser usado por usuários de verdade, rapidamente começam a aparecer bugs e problemas. Surge 1 ticket, 2 tickets. De repente a equipe só consegue entregar 3 a 6 tarefas por semana, porque precisa resolver bugs urgentes. Mas continua entregando coisas. Só que essas coisas novas, causam mais bugs. Na próxima semana a produtividade caiu pra 2 a 4 tarefas. E o número de bugs aumenta em vez de diminuir. Em pouco tempo, a equipe male male entrega 1 ou 2 tarefas novas, o resto do tempo é só resolvendo bugs.</p>
<p>E qual a decisão do gerente do projeto? &ldquo;Ah, é normal, precisa contratar mais programadores.&rdquo; Ele convence a diretoria. Dobra a equipe. E voilá, a equipe volta e entregar 6 a 9 tarefas novas por semana. E todo mundo continua sem se importar com testes ou qualidade em geral, é crunch, só entregar e entregar. O volume de código só aumenta, ninguém joga nada fora, vai só acumulando débito técnico. Mais código novo significa mais bugs. No final acontece a mesma coisa, nas semanas seguintes a produtividade cai pra 3 a 6 tarefas, depois pra 2 a 4 tarefas, e de novo, a nova equipe, com o dobro de integrantes, tá male male entregando 1 ou 2 coisas novas por semana, o resto é só apagando incêndio.</p>
<p>Digamos que todo mundo é ruim em testes. Se lá no começo tivesse decidido entregar tudo com testes, em vez de 6 a 9 tarefas, a equipe estaria entregando só de 4 a 6 coisas, talvez menos. Parece bem ruim. A diferença é que vai ser 4 a 6 TODA semana. Surgiu um bug novo? O programador que pega pra corrigir adiciona testes, e segue todo o processo que descrevi antes. Esse bug em particular não aparece de novo. Entregou coisa nova? A suite de testes garante que o que tinha antes não quebrou por conta disso. E assim, toda semana, de forma sustentável, a equipe entrega 4 a 6 tarefas. Não é o máximo de 9, mas não cai pra 1 ou 2.</p>
<p>Daí a empresa decide contratar mais gente e dobra a equipe. Mas a equipe é diligente e treina os novos integrantes sobre a importância de qualidade. A produtividade quase dobra, de 4 a 6 pra 8 a 10 tarefas. A produtividade só dobra se todo mundo segue o protocolo, ninguém pisa no calo de ninguém, e tudo que já foi feito e testado continua funcionando no futuro. Todo upgrade de bibliotecas e correções de segurança tem garantia que o sistema continua funcionando, porque a suite de testes tá passando. Daí nenhum deploy de upgrade feito na sexta feira causa desastre que vai precisar varar fim de semana corrigindo. Olha só que mágico.</p>
<p>Muita gente vai inventar um monte de desculpas pra não fazer testes, e eu garanto que pra cada desculpa nós já temos soluções. Nenhum programador precisa rodar a suite de testes o tempo todo se essa suite demora muito pra executar. Basta rodar alguns poucos ao redor do trabalho que está fazendo naquele momento. Daí sobe um pull request que vai ser pego por um GitLab CI e esse servidor de CI é quem vai rodar a suite inteira de testes. Não só isso, se a suite passar, esse CI pode fazer CD, que é continuous deployment, e automaticamente subir em ambiente de staging e notificar a equipe de QA.</p>
<p>Obviamente eu não posso mostrar projetos confidenciais dos meus clientes, mas posso mostrar esse projeto interno que nós usamos. Olha um pull request. Se olharmos o que mudou podemos ver que além do código, temos ajustes em specs também, que são testes. Quando o desenvolvedor subiu o código, o CI pegou e iniciou o job pra rodar os testes. Nesse caso levou pouco mais de 5 minutos. Passou com sucesso e, lógico, só porque todos os testes passaram não significa que não tem nenhum bug, só que não tem nenhum bug muito óbvio. Mais do que isso, no nosso caso tem configurado também checagens de segurança. No ecossistema Ruby temos coisas como Brakeman, que avalia o código por buracos de segurança conhecidos como injections.</p>
<p>Essa aplicação sobe no Heroku, que tem uma funcionalidade chamada review apps. Podemos ter um deployment separado de testes pra cada pull request que ainda está aberta. Justamente pra alguém conseguir testar de verdade antes de aprovar. Um review app é um ambiente de staging, só que isolado pra cada pull request. Dá pra ter dezenas de review apps em paralelo. Assim não precisa ficar acumulando e integrando tudo no mesmo ambiente de staging pra testar, o que seria uma zona. Testa tudo isolado. Vai aprovando no ritmo do responsável pelos testes. E no final, quando estiver tudo testado, aprovado e mergeado no branch principal, daí faz um deployment pra produção aqui do lado.</p>
<p>Neste exemplo, no dia que fui tirar foto de tela, não tinha nenhum pull request aberto. Mas eles apareceriam listados aqui. Cada um com uma URL separada de testes. Esse é outro motivo de porque eu sempre recomendo Heroku. Ele não é o mais barato, mas esse tipo de recurso facilita o fluxo de trabalho em ordens de grandeza. Só as horas de desenvolvimento que se economiza de cada desenvolvedor já se paga.</p>
<p>Enfim, uma vez cada pull request de cada desenvolvedor sendo aprovado, tudo vai sendo integrado no branch principal e podemos fazer um deploy de tudo integrado pro ambiente de staging. Agora o pessoal de QA pode fazer o trabalho deles direito, que é testar as coisas novas, e não ficar retestando tudo de novo que já funcionava antes. Todo report de bugs volta pro programador, que faz a correção e ajusta os testes pra cobrir os casos que não tinha pensado.</p>
<p>Com o pull request foi ajustado, os testes passam, QA já viu em staging, o sênior da equipe pode dar a última olhada e fazer o merge na branch principal. E aí alguém de devops ou alguém responsável pela produção pode pegar o que está na branch principal e fechar uma versão pra produção, com a segurança de saber que se está na branch principal, então está funcionando corretamente.</p>
<p>E se estiver usando Heroku, ainda tem uma facilidade extra. Digamos que todo mundo fez tudo direitinho, mas mesmo assim surgiu um bug que só aparecem em produção. Basta ir nesta aba de atividades e clicar em roll back pra voltar pra versão anterior. Heroku é caro, mas é nessas horas que ele se paga. Medo de deploy na sexta-feira só existe em equipes disfuncionais que não fazem esse básico que acabei de falar.</p>
<p>Falando em disfuncional, o maior desafio em toda equipe de projetos, é coordenar a comunicação. Ao ter um repositório de códigos como Git, um processo de testes de integração contínua, pareamento entre sêniors e juniors pra compartilhar conhecimento, e um protocolo como de pull requests, estabelecemos um conjunto simples e objetivo de entendimento nessa equipe. Não seguir esse protocolo é um desrespeito à equipe. Você não programa pra você mesmo, você contribui com sua equipe. O importante não é andar rápido, o importante é não estragar o que já funcionava.</p>
<p>Da mesma forma, ninguém deve sair adicionando coisas no projeto que ninguém está sabendo. Por isso que no mundo ágil se inventou os tais rituais como de planejamento e revisão. Rituais não devem ser obrigações, mas pontos de coordenação. Se os rituais estão sendo meramente pras pessoas aparecerem mas não participar, então não servem pra nada e devem ser eliminados e repensados. Esse é um ponto não-técnico que não quero tentar detalhar demais, mas preciso falar de um ponto específico: estimativas.</p>
<p>Se tem uma coisa que programador odeia fazer mais do que testes é estimar tarefas. Já disse isso em outros videos, mas existem duas palavras que todo mundo não-técnico confunde: estimar e prever. O programador estima, mas os gerentes ou diretores acham que receberam previsões. Estimativas são ordens de grandeza, não está certo nem errado. É uma ordem de grandeza. Previsões sim, é pra serem precisas. Quando um programador diz que pode levar uns 2 ou 4 dias, é isso que pode levar, 2 a 4 dias. Talvez leve 5. E aí o gerente que achava que tinha uma previsão na mão, fica puto, porque tava esperando 2 dias, recebeu em 5 e quer sair chutando tudo. E é isso que eu chamo de um idiota.</p>
<p>Eu acho que todo gerente de projetos deveria gastar um tempo movimentando o próprio dinheiro no mercado financeiro. Comprar e vender ações, ou opções, ou criptomoedas, não importa. Porque todo mundo gostaria de saber pra onde vai o mercado, se vai subir ou se vai cair. É impossível acertar 100% do tempo. Se alguém soubesse prever isso, quebraria o mercado financeiro. O máximo que podemos fazer é dar chutes educados. Estimativas. A gente tenta acertar, comprar na baixa, vender na alta. Ou no mínimo planejar pra alta e se proteger da baixa. Criamos contingências, e sabemos que é impossível ganhar sempre. O máximo que podemos fazer é tentar minimizar perdas. E as perdas nunca vão ser zero.</p>
<p>Gerenciamento de projetos é a mesma coisa. Um gerente inteligente não se interessa em tentar prever cada tarefa individualmente. Ele observa o movimento geral da equipe e do produto. Tá em tendência de alta de produtividade? Baixa de produtividade? Tem como influenciar a baixa? Qual é o obstáculo? Algum diretor deu carteirada e quebrou o planejamento? Alguém ficou doente? Um bom gerente está numa posição que um analista financeiro não tem: de influenciar as tendências.</p>
<p>Lógico, quanto mais um programador ganha experiência e tem mais segurança no que realmente sabe e o que não sabe, mais e mais suas estimativa se aproximam de previsões. Ele já tem uma boa noção de quanto tempo leva a maioria das coisas. Quanto mais inexperiente, mais longe vai ser essa estimativa. Faz parte do crescimento profissional de cada um começar a quantificar suas próprias capacidades. Ninguém tem obrigação de &ldquo;acertar&rdquo; estimativas, mas claro, quanto mais precisas forem, mais todo mundo consegue confiar mais e mais em você. É uma das formas de subir na carreira. Entregar o que promete.</p>
<p>Tarefas, stories ou seja lá como quer chamar, são meras formalidades. Quanto mais curtas, objetivas, direto ao ponto forem, melhor. O objetivo não é um concurso de literatura onde todo mundo tem que fazer uma dissertação. A quantidade de palavras deve ser o suficiente pra que toda a equipe envolvida saiba no que está sendo trabalhado. Só isso. Tem quinhentos blog posts, livros e cursos tentando ensinar &ldquo;a melhor forma&rdquo; de escrever requerimentos. E a maioria é bullshit. Quer dizer, até faz sentido, pode ser usado como inspiração, mas não existe nenhuma receita que dá pra só seguir. Assim como testes e estimativas, escrever requerimentos também depende de prática. Estude o que se sugere, mas não siga ao pé da letra.</p>
<p>User Stories tem aquele formato idiota de &ldquo;eu, como usuário, quero conseguir adicionar um produto no carrinho e ter o total do pedido, para poder pagar minha compra&rdquo; ou algo assim. Pode ser, é bonitinho, tem o objetivo de forçar as pessoas a pensar em pra quem é a funcionalidade sendo feita, qual o objetivo e facilitar pensar na melhor forma de fazer. Tem coisa que precisa de mais detalhe que isso. Um protótipo de tela no Canvas. Um rascunho de conta de imposto. Tem coisa que nem precisa de tanto detalhe assim. Bugs costumam ser mais diretos &ldquo;quando o usuário abre no firefox de mac, o box de items fica em cima do box de cartão e não dá pra digitar o código de segurança do fucking pedido e a fucking compra não finaliza&rdquo;. Tá super claro.</p>
<p>Eu tive um cliente que o imbecil do CTO mandava todo programador descrever em detalhes como ia programar, antes de escrever uma linha de códigos. Tinha que listar que classes novas ia criar, com que métodos, com quais nomes. Era a coisa mais idiota que eu já vi, só perde pra época que povo achava que precisava fazer diagramas de classe UML precisos meses antes de escrever uma linha de código. É idiótico. É da época que falei que se achava que programador era só pedreiro e havia uma forma mágica de descrever tudo, antes de escrever o código. É uma noção boçal.</p>
<p>O resumo é simples: a descrição deve ser a mais concisa, com o menor número de palavras possível. A equipe deve conseguir se reunir e em pouco tempo entender o que precisa ser feito, quem vai fazer, quem precisa de ajuda. O gerente, ou Product Owner, e o equivalente tech lead ou programador mais sênior, deveriam estar alinhados. Daí quando seja lá qual programador entregar o pull request praquela funcionalidade, o tech lead consegue avaliar rápido e mandar pro QA. O PO e o QA precisam estar alinhados, pra ver se o que foi entregue resolve o que se queria resolver.</p>
<p>Burocracia e formalidades em excesso aparecem em empresas onde esses personagens tem preguiça de conversar ou pior: não sabem o que estão fazendo. Porque se soubessem, poderiam ir direto ao assunto. Toda burocracia em excesso é defendida por aqueles que não sabem o que estão fazendo, porque aí podem usar a desculpa de &ldquo;mas, mas, eu segui os procedimentos, a culpa não é minha&rdquo;. Toda burocracia é uma muleta pra cabideiro que não deveria estar ocupando as posições que estão ocupando. É assim simples.</p>
<p>Voltando pra estimativas. Não existe nenhuma fórmula nem procedimento que, dado uma descrição em texto de um caso de uso, ou user story ou seja lá o que for, chega num número de horas ou dias exatos pra resolver essa tarefa. Cocomo e outras bullshitagens são lixo. Não tem como. Só a experiência dos programadores envolvidos é capaz de dar uma ordem de grandeza, cuja qualidade vai ser proporcional à experiência da equipe. Se a equipe for super experiente, mesmo assim vai ser uma ordem de grandeza.</p>
<p>Não tem nada mais retardado do que gente que pega esses números de estimativas, número de horas realizadas, vai pondo num gráfico e fica tentando derivar coisas desses números, fazer integral, aplicar fórmulas. Só porque se tem números, não signfica que existe cálculos que dê pra fazer. Eu posso sair jogando dados, colocar todos os resultados num gráficos e gerar um monte de conclusões. É assim que nasce pseudo-ciência, numerologia, astrologia e todas essas logias idiotas. Estimativa é um chute. Estimativas nunca estão erradas, porque não são previsões. Fazer contas com esses números não faz nenhum sentido.</p>
<p>Dá pra saber que vai ser perto de 6 meses mas menos de 1 ano. Agora, se vai ser 5 meses, 2 semanas, 4 dias e 5 horas, exatamente. Isso é impossível. Gastar horas tentando estimar tudo é um exercício de futilidade. Não perca tempo tentando prever com exatidão. Aprenda a lidar com ordens de grandeza. Simplesmente escreva o que se sabe que realmente precisa fazer. E nem tente detalhar 6 meses de trabalho de uma só vez. É inútil. Descreva o curto prazo em detalhes, 1 mês, no máximo 2 meses de trabalho. Tenha a direçao do longo prazo em mente, mas ajuste enquanto vai andando. Deixe a equipe se dividir em quem faz o que. E siga o que falei até agora sobre pull requests e merge.</p>
<p>Mesmo sem ter estimativa nenhuma, você como gerente ou como qualquer membro da equipe vai ver que numa semana a equipe entregou 10 tarefas, stories ou seja lá como quer chamar. Na semana seguinte, entregou 9. Na outra semana conseguiu entregar 11. Na semana seguinte entregou 7. O que o gerente ou facilitador dessa equipe começa a notar? Uma tendência. Parece que, seja lá como se descreve tarefas nessa equipe, eles conseguem entregar uma média de 8 a 9 unidades dessas &ldquo;coisas&rdquo;. E se for esperto, vai notar que quando entrega menos coincide quando sobe versão nova pra produção, daí o suporte recebe reports de bugs, que entram no backlog da equipe e isso diminui a quantidade de &ldquo;coisas&rdquo; entregues.</p>
<p>O gerente fala com o tech lead, que revê o processo, e vê que ele tava deixando passar pull requests com testes não tão bons. Ele pareia com o programador júnior que tava fazendo errado. Ensina na prática onde tava errando. E agora ele passa a produzir melhor. E é assim que uma equipe evolui, um passo de cada vez. Não é via metodologia, nem aumentando formalidades e burocracias, é via bom senso. Resolvendo problemas na raíz em vez de ficar inventando band-aids, ou pior, mais burocracias inúteis. E o principal: reorganizado a lista de coisas a fazer pra garantir que as coisas mais importantes estão sendo feitas primeiro e tudo que é opcional ou de pouco valor está no fim da lista. Assim, caso não dê tempo de fazer tudo, pelo menos você sabe que o mais importante foi feito.</p>
<p>Cuidado, não estou dizendo que não se deve perder tempo descrevendo tarefas ou que não se deve fazer estimativas. Estou falando que a formalidade não é tão importante, tem que começar a escrever e aceitar que vai estar mau escrito. Aceitar o feedback de que o programador fez errado porque do jeito que tava escrito ficou ambíguo. E aí vai ajustando os detalhes pra próxima vez, até o nível que faz sentido. &ldquo;Ah, se tivesse descrito essa conta com um exemplo, ele teria entendido&rdquo;, ou algo assim. Aceitar que uma estimativa nunca vai estar certa ou errada. É só uma referência. Não tente fazer projeções baseado em estimativas, porque isso só vai multiplicar um erro com outro erro.</p>
<p>Em vez disso veja em ordens de grandeza como falei. Seja lá como organiza as tarefas da equipe, veja, no geral, que tipos de funcionalidades ou correções de bugs a equipe entrega num certo período de tempo, como toda semana. Daí nem precisa perguntar pra equipe se o que tem no backlog cabe ou não na semana. Você já sabe essa resposta pela velocidade que já conhece. Quando sabe que não vai caber, seja honesto e faça seu trabalho: corte ou simplifique. Não tem mágica. Se não cabe, não cabe. Se tentar fazer caber, obviamente a qualidade vai cair. É onde vai vir o &ldquo;se eu pular fazer testes, talvez caiba&rdquo;. E quando se cede uma vez, isso vai acumular. Na semana seguinte já começa a vir a conta. E uma decisão feita no desespero numa semana, vai prejudicar todo o resto do andamento daqui pra frente.</p>
<p>Não existe almoço de graça. Toda vez que vai se deixando passar uma pequena coisa errada, isso vai acumulando. Em breve, vai estar tudo errado. Ser sustentável é exercitar disciplina. E não tem coisa mais maçante, mais entediante, do que ser disciplinado. Quando tudo é uma bagunça, é difícil elencar os problemas. Mas numa equipe disciplinada, fica fácil ver quem tá puxando a equipe pra trás. Sem ter a muleta da burocracia, fica fácil ver quem é a pessoa inconstante, aquele que promete demais, entrega de menos, e quando entrega outra pessoa precisa consertar. A equipe inteira sabe quem é essa pessoa. Eles devem ensinar, ajudar, mas quando a pessoa é resistente a trabalhar em equipe, ela deve ser removida. É assim simples.</p>
<p>A decisão errada é manter essa pessoa e contratar outra pra aumentar produtividade. Muitas equipes que eu já vi teriam a produtividade aumentada só de cortar os maus elementos, porque agora eles não precisam ficar consertando o que o mau elemento tá entregando de mau jeito. Automaticamente a produtividade aumenta, o stress diminui, todo mundo trabalha melhor. Uma coisa que aconteceu durante esse período que já passou de vacas gordas, com montes de empresas contratando gente ruim no atacado, é que em vez de aumentar a produtividade, eles prejudicaram a qualidade do produto e das equipes. Imagina um monte de gente ruim, fazendo o contrário de tudo que falei até agora. Só acumulou um monte de código porcaria que funciona em produção na força bruta, com alguns poucos hiper estressados, varando noite, consertando a porcaria que recebe.</p>
<p>Se a equipe não se importa com o que está entregando, nada do que eu falei até agora importa. E todo novo integrante, ao ver que ninguém tá realmente muito interessado, vai aprender do jeito errado: que o melhor é ligar o foda-se e fazer igual todo mundo. E é assim que um projeto entra em colapso, porque ninguém está ligando. Em empresas com dinheiro dos outros demais, é o que mais acontece. Mas se sua empresa realmente depende do código sendo feito, sinto dizer, rapidamente todo mundo vai perder o controle. E daí pra frente é só cavar o buraco mais fundo.</p>
<p>De novo. Todo projeto é uma incerteza, especialmente nos primeiros dias. Especialmente se for uma equipe nova, recém constituída. Precisam aprender a trabalhar juntos ao mesmo tempo que precisam entender o que diabos vão construir. É uma montanha de incertezas. Por isso o ideal é garantir que o básico do básico está garantido: que pelo menos o que está sendo entregue vai continuar funcionando ao longo do tempo. Exercitem o processo de testes, pull requests, integração contínua, deployment em staging. Daí comecem a ver até que nível de detalhes realmente as tarefas precisam ser definidas. E vai ajustando.</p>
<p>Não tente estabelecer regras escritas em pedra no dia um. É inútil. Comece no equivalente a fazer rascunhos. Avalie se o rascunho está num nível de detalhes suficiente. Se não estiver, vai ajustando. O PO, o UX, precisam ir refinando isso. Daí o QA e o suporte precisam também ir refinando o processo de testes. Esse processo volta pra equipe de desenvolvimento, que reflete se está cometendo deslizes técnicos que poderiam evitar bugs óbvios. Todo mundo ainda estava crú em fazer testes, e aos poucos vai aprendendo a realmente testar os casos que interessam e deixar de lado os casos que não precisa. Naturalmente vão se acostumando a saber até onde precisa ou não cobrir determinados tipos de funcionalidades.</p>
<p>Sério. Exercite sua equipe em usar Git direito, em usar os recursos de testes que certamente já tem no seu framework. Como falei, Rails tem, Django tem, Laravel tem, Spring Boot tem, Express tem, React tem. Todo mundo tem suporte a testes que vocês só escolheram não usar. Só que vocês não são seres ungidos que fazem código sem erros. Pelo contrário, todo programador vai mais fazer erros do que acertar. Especialmente se for júnior. É normal. Só que hoje todo mundo deixa esses erros acumularem no repositório, porque os deploys pra produção acontecem só 1 vez por mês, ou 1 vez por bimestre. Daí toda vez que faz deploy fica todo mundo apreensivo, porque já sabe que vai dar merda. Todo mundo sabe que empurrou os problemas com a barriga. Daí o dia do deploy é o dia da pizza na madrugada. Todo mundo correndo atrás do próprio rabo pra consertar os erros que vão aparecendo. Um caos.</p>
<p>Não seja esse clichê. Faz direito um pouco todo dia pro dia de deploy em produção ser só mais um dia normal, tedioso, sem nenhuma adrenalina, porque todo mundo tem segurança que tá tudo testado e funcionando. Fazer as coisas direito é muito chato, eu sei. Deixa pra fazer código porco gambiarrado nos seus projetinhos pessoais. Mas em projeto dos outros, aprenda a ser profissional. Imagina se seu médico também fizesse programação orientada a gambiarras sem testes como você faz. Imagine se o médico do seu filho fizesse como você faz. Acho que não ia gostar né? Cresce, e age como profissional. Se ficaram com dúvidas, mandem nos comentários abaixo. 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>testes</category><category>engenharia-de-software</category><category>agile</category><category>akitando</category></item></channel></rss>