<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Bancos-De-Dados | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/bancos-de-dados/</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/bancos-de-dados/index.xml" rel="self" type="application/rss+xml"/><item><title>RAG Está Morto? Contexto Longo, Grep e o Fim do Vector DB Obrigatório</title><link>https://akitaonrails.github.io/2026/04/06/rag-esta-morto-contexto-longo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/06/rag-esta-morto-contexto-longo/</guid><pubDate>Mon, 06 Apr 2026 11:00:00 -0300</pubDate><description>&lt;p&gt;Já tem um tempo que essa coceira não me larga. No começo da onda de LLMs, lá em 2022/2023, a gente tinha 4k de contexto no GPT 3.5, 8k se esticasse, 32k era luxo. Pra fazer qualquer coisa com documento real, você não tinha escolha: tinha que recortar o texto em pedaços, gerar embeddings, jogar num vector database, fazer similarity search, pegar os top-5 chunks e rezar pra que os pedaços certos aparecessem.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Já tem um tempo que essa coceira não me larga. No começo da onda de LLMs, lá em 2022/2023, a gente tinha 4k de contexto no GPT 3.5, 8k se esticasse, 32k era luxo. Pra fazer qualquer coisa com documento real, você não tinha escolha: tinha que recortar o texto em pedaços, gerar embeddings, jogar num vector database, fazer similarity search, pegar os top-5 chunks e rezar pra que os pedaços certos aparecessem.</p>
<p>Daí virou indústria. Pinecone, Weaviate, Qdrant, Chroma, Milvus, pgvector, LangChain, LlamaIndex, Haystack. Tutorial em todo canto, &ldquo;construa seu chatbot com seus PDFs&rdquo;, consultorias inteiras vivendo disso. Virou meio que o &ldquo;hello world&rdquo; de LLM aplicado: documento → chunk → embed → vector DB → query.</p>
<p>Hoje, em abril de 2026, o Claude Opus 4.6 tem 1 milhão de tokens de contexto. O Sonnet 4.6, idem. O Gemini 3.1 Pro também. O GPT 5.4 fica numa janela menor mas ainda confortável, na casa das centenas de milhares. E pra alguns modelos já tem modos experimentais de 2M tokens. A pergunta que não me larga é: pra que cargas d&rsquo;água eu preciso montar uma stack vector pra resolver problema que cabe na janela do modelo?</p>
<p>E mais: vector database tem problemas reais que ninguém gosta muito de falar. Falsos vizinhos. Chunking arbitrário que parte definição do uso. Embeddings que envelhecem mal. Sem dizer que quando o resultado vem errado, você não tem a menor ideia do porquê.</p>
<p>A tese que eu venho amadurecendo é simples: na maioria dos casos, um <code>grep</code> bem feito mais uma janela de contexto generosa do modelo bate uma stack RAG completa. É mais barato, é mais fácil de manter, e quando dá pau você consegue debugar. Bora destrinchar.</p>
<h2>O que o vazamento do Claude Code mostrou<span class="hx:absolute hx:-mt-20" id="o-que-o-vazamento-do-claude-code-mostrou"></span>
    <a href="#o-que-o-vazamento-do-claude-code-mostrou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de entrar na parte teórica, vale falar de algo que aconteceu há poucos dias e que reforça muito essa discussão. Em 31 de março de 2026, a Anthropic, sem querer, publicou no npm a versão 2.1.88 do pacote <code>@anthropic-ai/claude-code</code> com um source map de quase 60 MB anexado, e cerca de 512 mil linhas de TypeScript da ferramenta interna deles vazaram pro mundo. Eu já tinha <a href="/2026/03/31/codigo-fonte-do-claude-code-vazou-o-que-achamos-dentro/">escrito sobre o incidente na semana passada</a>, com mais detalhe sobre o que apareceu no código.</p>
<p>O que interessa pra essa discussão é o sistema de memória do Claude Code. Em vez de jogar tudo num vector DB, a arquitetura tem três camadas. Um <code>MEMORY.md</code> que fica permanentemente carregado no contexto, mas não guarda dado nenhum: é só um índice de ponteiros, umas 150 caracteres por linha, mantido abaixo de 200 linhas e uns 25 KB. Os fatos de verdade ficam em &ldquo;topic files&rdquo; buscados sob demanda quando o agente precisa. E os transcripts brutos das sessões anteriores nunca são relidos inteiros, só pesquisados com grep atrás de identificador específico. Sem embedding. Sem Pinecone. Disciplina de escrita (topic file primeiro, índice depois) e busca lexical, só isso.</p>
<p>O loop principal do Claude Code também tem um sistema escalonado pra lidar com contexto enchendo. Como <a href="/2026/03/31/codigo-fonte-do-claude-code-vazou-o-que-achamos-dentro/">eu detalhei no post anterior</a>, são cinco estratégias diferentes de compactação de contexto, com nomes tipo <code>microcompact</code> (limpa resultados de tool antigos por tempo), <code>context collapse</code> (resume trechos longos da conversa), e <code>autocompact</code> (que dispara quando o contexto chega perto do limite). O CLAUDE.md, que muita gente pensava que era só uma convenção, é primeira classe na arquitetura: o sistema relê o arquivo a cada iteração da query.</p>
<p>O que isso me diz: a melhor ferramenta de coding agent que existe hoje, feita pela empresa que vende o modelo mais caro do mercado, <strong>não usa vector DB</strong>. Usa arquivos no disco, índice em markdown, busca lexical, e estratégias inteligentes de compactação quando o contexto estoura. Eles poderiam ter botado embedding, eles têm dinheiro pra rodar o que quisessem, e escolheram não. O motivo, na minha leitura, é exatamente o que esse post defende: pra recuperar texto de arquivos que você controla, com contexto generoso disponível, vector DB é peso morto. Melhor investir em compactar bem o que você já tem na janela do que indexar tudo num banco externo.</p>
<p>Tem um detalhe curioso de segurança que veio junto: a galera percebeu que o pipeline de compactação tem uma vulnerabilidade chamada de &ldquo;context poisoning&rdquo;. Conteúdo que parece instrução, vindo de um arquivo que o modelo lê (tipo um CLAUDE.md de um repo clonado), pode acabar sendo preservado pelo modelo de compactação como se fosse &ldquo;feedback do usuário&rdquo;, e o modelo seguinte segue isso como instrução genuína. É um vetor de ataque novo. Mas isso é assunto pra outro post.</p>
<h3>O sistema &ldquo;Dream&rdquo; e a consolidação de memória<span class="hx:absolute hx:-mt-20" id="o-sistema-dream-e-a-consolidação-de-memória"></span>
    <a href="#o-sistema-dream-e-a-consolida%c3%a7%c3%a3o-de-mem%c3%b3ria" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mas o que mais me chamou atenção pro debate de RAG, e que eu já <a href="/2026/03/31/codigo-fonte-do-claude-code-vazou-o-que-achamos-dentro/">destrinchei na semana passada</a>, é o sistema chamado <code>autoDream</code>. É um subagente forkado, com bash read-only no projeto, que roda em background enquanto você não está usando a ferramenta. O job dele é literalmente sonhar: consolidar memória. O nome não é à toa, e a analogia óbvia (que eu não consegui evitar) é a do cérebro humano consolidando memória durante o sono, transformando experiência de curto prazo em conhecimento mais estável.</p>
<p>Pra um sonho rodar, três portas têm que abrir ao mesmo tempo: 24 horas desde o último sonho, no mínimo 5 sessões desde o último, e um lock de consolidação que impede sonhos concorrentes. Quando dispara, segue quatro fases. Orient (faz <code>ls</code> no diretório de memória, lê o índice). Gather (busca sinais novos em logs, memórias desatualizadas e transcripts). Consolidate (escreve ou atualiza os topic files, converte data relativa em absoluta, deleta fato que foi contraditado). E Prune, que é a faxina final que mantém o índice abaixo das 200 linhas.</p>
<p>A decisão de fazer o <code>autoDream</code> como subagente forkado é o detalhe que importa aqui. Ele não roda no mesmo loop do agente principal. Por quê? Porque consolidação de memória é processo barulhento. O modelo tem que reler transcript antigo, comparar com o que está no <code>MEMORY.md</code>, decidir o que fica e o que sai, fazer hipótese sobre coisa que viu em sessão anterior. Se isso rodasse no contexto principal, poluía o &ldquo;train of thought&rdquo; do agente que está tentando ajudar você na tarefa do momento. Forkando, separa as duas coisas. O agente principal continua focado no que você pediu, o <code>autoDream</code> faz a faxina em paralelo, sem permissão de escrita no projeto.</p>
<p>E o jeito como ele acha o que tem que consolidar é busca lexical, pura e dura. Os transcripts ficam em arquivos JSONL no disco, e o <code>autoDream</code> usa grep pra procurar sinais novos. Grep mesmo, em log de texto. Pensa nisso por um segundo. A consolidação de memória do agente mais avançado do mundo, feita por uma das empresas mais ricas de IA, é um subagente forkado fazendo grep em log de texto. Se vector DB fosse a resposta certa pra esse tipo de problema, a Anthropic tinha botado vector DB. Não botaram.</p>
<p>E tem um detalhe que pra mim é o ouro escondido do leak inteiro, e que cabe perfeitamente nesse argumento. No <code>autoDream</code>, memória é tratada como pista. O sistema parte do princípio de que o que está armazenado pode estar velho, errado, contraditado por algo que aconteceu depois, e o modelo tem que verificar antes de confiar. O pitch do vector DB é o oposto disso: indexa tudo, busca por similaridade, devolve top-k, confia no resultado. O Claude Code escolheu o caminho conservador. Indexa pouco, busca por palavra, devolve pista, e desconfia até bater o olho no fato real.</p>
<p>A estratégia inteira tem duas camadas. Dentro da sessão, contexto generoso mais grep mais compactação inteligente (<code>microcompact</code>, <code>context collapse</code>, <code>autocompact</code>). Entre sessões, um subagente que consolida memória de forma assíncrona, usando grep nos transcripts e tratando o resultado como dica, não como verdade. Embedding e vector DB não aparecem em nenhum dos dois lugares. A escolha consciente foi leitor inteligente comendo texto bruto, não leitor burro consumindo top-k de embedding.</p>
<p>A lição prática pro nosso debate é simples. Os agentes mais avançados do mercado tão indo na direção de contexto generoso, busca lexical e compactação inteligente, não na direção de pipeline RAG clássico. Se a Anthropic, com toda a infraestrutura e talento que tem, escolheu esse caminho pra Claude Code, a gente que tá construindo aplicação interna com uma fração do orçamento deveria pelo menos considerar a mesma direção.</p>
<h2>Onde a história começou a virar<span class="hx:absolute hx:-mt-20" id="onde-a-história-começou-a-virar"></span>
    <a href="#onde-a-hist%c3%b3ria-come%c3%a7ou-a-virar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando o teto era 32k de contexto, retrieval era o gargalo do problema inteiro. Você tinha que pré-filtrar agressivo, porque qualquer coisa que entrasse na janela era espaço sagrado. Vector DB foi a única forma decente de fazer essa pré-filtragem semântica. A lógica era: &ldquo;o leitor (LLM) é caro e burro, então o retriever tem que ser inteligente e seletivo&rdquo;.</p>
<p>Hoje a equação virou. O leitor agora é o cara mais inteligente da mesa, e a janela cresceu pra caber documento inteiro. Aí o retriever pode (e talvez deva) voltar pra ser burro. Quanto mais burro, melhor. Você quer alta cobertura e baixa precisão, e deixa o modelo fazer a parte fina. Grep faz exatamente isso. BM25 também. E ripgrep voa em cima de milhões de linhas sem pestanejar.</p>
<p>E não é só achismo meu. Os benchmarks BEIR já mostraram faz tempo que BM25 bate ou empata com vários retrievers densos quando o domínio sai de onde os embeddings foram treinados. A própria Anthropic publicou um post sobre <a href="https://www.anthropic.com/news/contextual-retrieval"target="_blank" rel="noopener">Contextual Retrieval</a> basicamente dizendo a mesma coisa: sinal lexical mais julgamento de LLM bate embedding puro na maior parte das tarefas de conhecimento. E olha o Claude Code, a ferramenta que eu uso todo dia há 500 horas: ele navega o repositório com <code>Glob</code> e <code>Grep</code>. Sem vector DB, sem embedding, sem LangChain. Funciona ridiculamente bem.</p>
<h2>Os problemas reais do vector database que ninguém anuncia<span class="hx:absolute hx:-mt-20" id="os-problemas-reais-do-vector-database-que-ninguém-anuncia"></span>
    <a href="#os-problemas-reais-do-vector-database-que-ningu%c3%a9m-anuncia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A propaganda de vector DB vende o sonho da busca semântica perfeita. A realidade é mais bagunçada.</p>
<p>Falsos vizinhos é o primeiro. Cosine similarity premia similaridade tópica, não relevância. Você pergunta &ldquo;como tratamos erro de autenticação&rdquo; e o DB devolve todo chunk que menciona autenticação. O chunk que efetivamente responde pode estar em décimo lugar, ou nem ter aparecido porque o redator do doc usou &ldquo;login&rdquo; em vez de &ldquo;auth&rdquo;.</p>
<p>Chunking é o segundo, e é um desastre disfarçado. Janela de 512 tokens, overlap de 64, parece razoável até você perceber que sua tabela importante foi cortada no meio, a definição de uma função ficou separada do uso, e o pedaço da documentação com o comando exato ficou órfão sem o contexto da seção. A fronteira do chunk costuma ser exatamente onde a resposta morava.</p>
<p>Quando falha, falha sem deixar pista. Quando o BM25 não acha, você sabe o porquê: a palavra não tá lá. Quando o vector DB devolve lixo, você recebe um chunk plausível e errado, sem nenhum sinal diagnóstico. Boa sorte debugando isso em produção às duas da manhã.</p>
<p>Índice envelhece. Cada update do documento pede re-embedding. Se você tem 10 mil docs e uns 200 mudam por dia, isso vira processo de batch, monitoramento, fila, retry, custo de API de embedding, e uma janela de inconsistência inevitável entre o que tá no disco e o que tá no índice. Grep não tem nada disso. Arquivo mudou? Próxima query já vê.</p>
<p>E tem o custo de operação que ninguém soma. Pinecone cobra por vector. Weaviate pede cluster pra manter. pgvector evita servidor novo mas você continua com schema, índice e pipeline de re-embedding. Cada uma dessas coisas pede tempo de engenheiro, monitoramento, teste, deploy. Tudo isso pra fazer uma busca que muitas vezes o <code>rg</code> resolve em 200ms.</p>
<h2>Comparando a complexidade<span class="hx:absolute hx:-mt-20" id="comparando-a-complexidade"></span>
    <a href="#comparando-a-complexidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olha o desenho:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/06/rag/rag-vs-grep-complexity.png" alt="Complexidade: RAG clássico vs grep &#43; contexto longo"  loading="lazy" /></p>
<p>De um lado, oito etapas, quatro ou cinco serviços, índice externo que precisa ser mantido e atualizado. Do outro, quatro etapas, zero infraestrutura nova. Não é caricatura: é literalmente o que você precisa montar pra cada caso.</p>
<p>A pergunta honesta é: a coluna da esquerda compensa? Em 2023, sim, porque a coluna da direita não existia (não tinha LLM com janela de 200k). Em 2026, na maior parte dos casos, não.</p>
<h2>Prós e contras de cada lado<span class="hx:absolute hx:-mt-20" id="prós-e-contras-de-cada-lado"></span>
    <a href="#pr%c3%b3s-e-contras-de-cada-lado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>RAG clássico (vector DB)<span class="hx:absolute hx:-mt-20" id="rag-clássico-vector-db"></span>
    <a href="#rag-cl%c3%a1ssico-vector-db" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>A favor:</strong></p>
<ul>
<li>Funciona pra bases de documento gigantes, da ordem de centenas de GB, onde nem <code>rg</code> resolve sem indexação prévia</li>
<li>Acerta consultas paráfrase pesada e cross-lingual (&ldquo;como cancelo&rdquo; vs &ldquo;encerramento de assinatura&rdquo;), onde o vocabulário do usuário não bate com o do documento</li>
<li>Funciona pra modalidades não-textuais (imagem, áudio) onde grep não tem o que olhar</li>
<li>Economiza tokens de input se você tá apertado de orçamento ou de latência absoluta</li>
</ul>
<p><strong>Contra:</strong></p>
<ul>
<li>Stack complexa: embedding, vector DB, chunking, reranker, pipeline de re-indexação</li>
<li>Falhas opacas, difíceis de debugar</li>
<li>Chunking destrói contexto de tabelas, código, definições longas</li>
<li>Overhead operacional (índice, fila, monitoramento, custo de re-embedding)</li>
<li>A busca semântica vendida no marketing raramente funciona como o marketing promete</li>
</ul>
<h3>Grep + contexto longo<span class="hx:absolute hx:-mt-20" id="grep--contexto-longo"></span>
    <a href="#grep--contexto-longo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>A favor:</strong></p>
<ul>
<li>Praticamente zero infraestrutura nova: ripgrep, sqlite, ou um simples <code>LIKE</code> em Postgres</li>
<li>Sempre fresco: o arquivo mudou, a próxima query já vê</li>
<li>Falhas transparentes: a palavra está ou não está</li>
<li>Carrega o documento em pedaços generosos, o modelo faz a filtragem fina com semântica de verdade</li>
<li>Mais barato em dev e ops, mais barato pra pivotar de domínio</li>
</ul>
<p><strong>Contra:</strong></p>
<ul>
<li>Não escala pra terabytes de texto bruto sem alguma indexação</li>
<li>Sofre quando o usuário usa vocabulário muito diferente do documento</li>
<li>Não funciona pra modalidade não-textual</li>
<li>Latência por query é maior em termos absolutos (carregar 100k tokens sempre custa mais que carregar 5k)</li>
<li>Custo de input por query é mais alto se você não tem prompt caching</li>
</ul>
<h2>Mas e o custo?<span class="hx:absolute hx:-mt-20" id="mas-e-o-custo"></span>
    <a href="#mas-e-o-custo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse é o argumento que mais me jogam na cara quando defendo a tese do &ldquo;carrega tudo no contexto&rdquo;. &ldquo;Vai ficar caríssimo, 200k tokens de input por query é absurdo.&rdquo; Vamos fazer a conta de verdade.</p>
<p>No <a href="/2026/04/05/testando-llms-open-source-e-comerciais-quem-consegue-bater-o-claude-opus/">meu artigo do benchmark de LLMs de ontem</a> eu mapeei o preço por token de cada modelo. Pega o Claude Sonnet 4.6: $3 por milhão de tokens de input, $15 por milhão de output. Pega o GLM 5 (que provei que funciona): $0.60 input, $2.20 output. Pega o GPT 5.4 Pro lá em cima: $15 input, $180 output (esse aí machuca, eu sei).</p>
<p>Antes de fazer a conta de &ldquo;200k tokens&rdquo; em dólar, vale aterrissar isso em algo tangível, porque &ldquo;100k tokens&rdquo; não diz nada pra ninguém. Token, na média, é mais ou menos 0,75 palavra em inglês (em português é parecido, talvez um pouco mais por causa de palavras maiores). Então, traduzindo:</p>
<ul>
<li><strong>100k tokens</strong> ≈ 75 mil palavras ≈ um romance curto inteiro, tipo <em>O Velho e o Mar</em> do Hemingway com sobra, ou uns três artigos longos da Wikipedia juntos.</li>
<li><strong>200k tokens</strong> ≈ 150 mil palavras ≈ um romance grande, tipo <em>Crime e Castigo</em> na íntegra, ou metade do primeiro livro de <em>Game of Thrones</em> (que tem ~298k palavras, daria uns 400k tokens).</li>
<li><strong>400k tokens</strong> ≈ 300 mil palavras ≈ <em>A Game of Thrones</em> completo, livro 1 da série inteiro na janela.</li>
<li><strong>1M tokens</strong> ≈ 750 mil palavras ≈ a trilogia inteira de <em>O Senhor dos Anéis</em> mais <em>O Hobbit</em>, ou a Bíblia inteira (King James ~783k palavras, daria por volta de 1M tokens), ou cerca de dois livros e meio de <em>Game of Thrones</em> empilhados.</li>
</ul>
<p>Então quando eu falo &ldquo;joga 200k tokens de input no modelo&rdquo;, o que isso significa no mundo real é &ldquo;joga <em>Crime e Castigo</em> inteiro como contexto da pergunta&rdquo;. É muita coisa. E é exatamente isso que torna o argumento desse post viável: os modelos de hoje conseguem ler um romance inteiro de uma vez e ainda responder uma pergunta específica sobre ele. Em 2023, isso era ficção científica. Em 2026, virou o caso base.</p>
<p>Imagina então uma query que joga 200k tokens de input (lá vai <em>Crime e Castigo</em> de novo) e produz 2k tokens de output (umas três páginas de resposta):</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Input ($)</th>
          <th style="text-align: right">Output ($)</th>
          <th style="text-align: right">Total por query</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td style="text-align: right">$0.60</td>
          <td style="text-align: right">$0.03</td>
          <td style="text-align: right"><strong>$0.63</strong></td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td style="text-align: right">$3.00</td>
          <td style="text-align: right">$0.15</td>
          <td style="text-align: right"><strong>$3.15</strong></td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td style="text-align: right">$0.12</td>
          <td style="text-align: right">$0.0044</td>
          <td style="text-align: right"><strong>$0.12</strong></td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td style="text-align: right">$0.40</td>
          <td style="text-align: right">$0.024</td>
          <td style="text-align: right"><strong>$0.42</strong></td>
      </tr>
      <tr>
          <td>GPT 5.4 Pro</td>
          <td style="text-align: right">$3.00</td>
          <td style="text-align: right">$0.36</td>
          <td style="text-align: right"><strong>$3.36</strong></td>
      </tr>
  </tbody>
</table>
<p>Agora bota prompt caching no meio. O Claude tem cache que faz o input cacheado custar uma fração do preço cheio (na ordem de 10%, dependendo do modelo). O Gemini tem mecanismo similar. Quando você faz uma sequência de queries em cima do mesmo dump de 200k tokens, o custo das queries seguintes despenca pra centavos. Com Sonnet cacheado, dá pra falar em uns $0.10 por query subsequente sem inventar muito.</p>
<p>Agora compara isso com o custo de manter um Pinecone, ou um Weaviate, ou um pgvector. Ignorando o preço da assinatura em si (que varia bastante), você precisa de engenheiro pra montar a pipeline, manter, monitorar, lidar com falha de embedding, refazer chunking quando o domínio muda. Conservadoramente, fala em algo entre 40 e 80 horas de engenharia pra deixar a coisa estável. A R$ 200/hora, isso é entre R$ 8.000 e R$ 16.000. Em USD, na faixa de $1.600 a $3.200 só pra colocar de pé.</p>
<p>Com $3.200, no Sonnet 4.6 com prompt caching, você roda algo na ordem de 30 mil queries de 200k tokens. Trinta mil queries, dependendo da escala do projeto, dão vários meses ou até um ano inteiro de uma ferramenta interna média. E você não pagou engenheiro pra montar pipeline. Não tem servidor de vector DB pra manter. Se o documento mudar, o sistema já vê na próxima query.</p>
<p>A conta do &ldquo;RAG é mais barato em tokens&rdquo; ignora que token é a coisa mais barata da equação inteira. Engenheiro custa caro, servidor custa caro, bug em produção custa muito caro. Token virou commodity, e tá ficando mais barato a cada release de modelo novo.</p>
<p>O argumento clássico do RAG era &ldquo;modelo é caro, retrieval é barato&rdquo;. Hoje é o oposto: modelo é a parte barata da pilha, retrieval inteligente é o que sai caro pra montar e manter.</p>
<h2>Os pontos onde a tese não cola<span class="hx:absolute hx:-mt-20" id="os-pontos-onde-a-tese-não-cola"></span>
    <a href="#os-pontos-onde-a-tese-n%c3%a3o-cola" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não quero parecer fanboy. Tem casos onde RAG clássico ainda ganha:</p>
<ol>
<li><strong>Bases gigantescas.</strong> Se você tem 500 GB de texto bruto, nem <code>rg</code> resolve em tempo aceitável. Aí precisa de algum tipo de indexação. Pode ser BM25 indexado (Tantivy, Elasticsearch), pode ser vector DB. Mas observa: a primeira opção ainda é lexical, não vetorial.</li>
<li><strong>Vocabulário muito disperso.</strong> Suporte ao cliente, onde o usuário fala &ldquo;tô sem net&rdquo; e a documentação fala &ldquo;perda de conectividade na camada física&rdquo;. BM25 não pega isso. Embedding pega. Aí vector DB ganha o ponto.</li>
<li><strong>Modalidade não-textual.</strong> Busca de imagem por imagem, áudio por áudio. Embedding é obrigatório.</li>
<li><strong>Latência absoluta crítica.</strong> Se você precisa responder em 100ms e tem 5k de orçamento de input, dump generoso não cabe. Aí pré-filtragem é necessária.</li>
<li><strong>Compliance e auditoria.</strong> Se você precisa provar que tal documento foi consultado pra responder tal query, ter chunks indexados rastreáveis ajuda. Dump de 200k de contexto é mais opaco em auditoria.</li>
</ol>
<p>Pra esses casos, RAG clássico continua fazendo sentido. Mas observa o tamanho da lista. São casos específicos. O caso geral, tipo &ldquo;chat com nossos documentos internos&rdquo; ou &ldquo;pergunte ao manual do produto&rdquo;, quase todo cai no balde do &ldquo;grep + contexto longo resolve melhor&rdquo;.</p>
<h2>Lazy retrieval: a receita que eu defendo<span class="hx:absolute hx:-mt-20" id="lazy-retrieval-a-receita-que-eu-defendo"></span>
    <a href="#lazy-retrieval-a-receita-que-eu-defendo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se eu fosse construir uma ferramenta de &ldquo;chat com docs&rdquo; hoje, do zero, seria mais ou menos assim:</p>
<ol>
<li><strong>Mantém os documentos brutos.</strong> Markdown, PDF convertido, código, o que for. No disco mesmo, organizado em pastas que façam sentido pro domínio.</li>
<li><strong>Filtro lexical rápido.</strong> <code>ripgrep</code> com regex, ou BM25 com Tantivy/SQLite FTS5, ou um <code>LIKE</code> no Postgres se já tiver. Devolve uns 100-300 hits.</li>
<li><strong>Carrega generosamente.</strong> Pega não só o trecho que bateu, mas o arquivo inteiro, ou uma janela grande em volta. Joga tudo no contexto.</li>
<li><strong>Deixa o LLM fazer a parte fina.</strong> Passa a pergunta original, manda o modelo encontrar o que importa, descartar o resto, e responder com citações.</li>
<li><strong>(Opcional) Adiciona embeddings só pra classes de query onde lexical falha</strong>, depois de você ter dados reais mostrando que falha.</li>
</ol>
<p>Isso é o oposto do conselho antigo (&ldquo;comece com vetores, fallback pra keyword&rdquo;). É: <strong>comece com keyword, e adicione vetor só se sentir falta</strong>. Na maioria dos projetos, você nunca vai sentir.</p>
<h2>Uma implementação de brinquedo em Ruby<span class="hx:absolute hx:-mt-20" id="uma-implementação-de-brinquedo-em-ruby"></span>
    <a href="#uma-implementa%c3%a7%c3%a3o-de-brinquedo-em-ruby" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra deixar concreto. Eis um script Ruby usando o gem <a href="https://github.com/crmne/ruby_llm"target="_blank" rel="noopener"><code>ruby_llm</code></a> (o mesmo do benchmark de ontem) que faz exatamente esse fluxo: grep nos arquivos, carrega os trechos com contexto, manda pro Claude, recebe a resposta. Sem vector DB, sem chunking, sem embedding, sem LangChain.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;ruby_llm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;open3&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">DOCS_DIR</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;./docs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="no">QUERY</span>    <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="ow">or</span> <span class="nb">abort</span> <span class="s2">&#34;uso: ./ask.rb &lt;pasta&gt; &lt;pergunta&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Filtro lexical rápido com ripgrep.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#    -i case insensitive, -l só nomes de arquivo, --type-add cobre md/txt/pdf-extraído.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">lexical_search</span><span class="p">(</span><span class="n">dir</span><span class="p">,</span> <span class="n">query</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">terms</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">downcase</span><span class="o">.</span><span class="n">scan</span><span class="p">(</span><span class="sr">/\w{4,}/</span><span class="p">)</span><span class="o">.</span><span class="n">uniq</span><span class="o">.</span><span class="n">first</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>  <span class="c1"># palavras com 4+ letras</span>
</span></span><span class="line"><span class="cl">  <span class="n">pattern</span> <span class="o">=</span> <span class="n">terms</span><span class="o">.</span><span class="n">join</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="n">cmd</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;rg&#34;</span><span class="p">,</span> <span class="s2">&#34;-l&#34;</span><span class="p">,</span> <span class="s2">&#34;-i&#34;</span><span class="p">,</span> <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">dir</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">files</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="no">Open3</span><span class="o">.</span><span class="n">capture2</span><span class="p">(</span><span class="o">*</span><span class="n">cmd</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">files</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:empty?</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Carrega os arquivos inteiros (até um teto razoável).</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">load_context</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="ss">max_chars</span><span class="p">:</span> <span class="mi">600_000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="n">files</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">path</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">body</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">total</span> <span class="o">+</span> <span class="n">body</span><span class="o">.</span><span class="n">size</span> <span class="o">&gt;</span> <span class="n">max_chars</span>
</span></span><span class="line"><span class="cl">    <span class="n">total</span> <span class="o">+=</span> <span class="n">body</span><span class="o">.</span><span class="n">size</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;## </span><span class="si">#{</span><span class="n">path</span><span class="si">}</span><span class="se">\n\n</span><span class="si">#{</span><span class="n">body</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="o">.</span><span class="n">compact</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">---</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Manda pro Claude com a pergunta e os documentos.</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">ask</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat</span> <span class="o">=</span> <span class="no">RubyLLM</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="ss">model</span><span class="p">:</span> <span class="s2">&#34;anthropic/claude-sonnet-4-6&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">prompt</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">PROMPT</span>
</span></span><span class="line"><span class="cl">    <span class="no">Você</span> <span class="n">tem</span> <span class="n">acesso</span> <span class="n">aos</span> <span class="n">documentos</span> <span class="n">abaixo</span><span class="o">.</span> <span class="no">Responda</span> <span class="n">a</span> <span class="n">pergunta</span> <span class="k">do</span> <span class="n">usuário</span>
</span></span><span class="line"><span class="cl">    <span class="n">usando</span> <span class="n">apenas</span> <span class="n">o</span> <span class="n">que</span> <span class="n">está</span> <span class="n">nos</span> <span class="n">documentos</span><span class="o">.</span> <span class="no">Cite</span> <span class="n">o</span> <span class="n">nome</span> <span class="k">do</span> <span class="n">arquivo</span> <span class="n">nas</span>
</span></span><span class="line"><span class="cl">    <span class="n">referências</span><span class="o">.</span> <span class="no">Se</span> <span class="n">a</span> <span class="n">resposta</span> <span class="n">não</span> <span class="n">estiver</span> <span class="n">nos</span> <span class="n">documentos</span><span class="p">,</span> <span class="n">diga</span> <span class="n">isso</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">---</span> <span class="no">DOCUMENTOS</span> <span class="o">---</span>
</span></span><span class="line"><span class="cl">    <span class="c1">#{context}</span>
</span></span><span class="line"><span class="cl">    <span class="o">---</span> <span class="no">FIM</span> <span class="no">DOS</span> <span class="no">DOCUMENTOS</span> <span class="o">---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="ss">Pergunta</span><span class="p">:</span> <span class="c1">#{query}</span>
</span></span><span class="line"><span class="cl">  <span class="no">PROMPT</span>
</span></span><span class="line"><span class="cl">  <span class="n">chat</span><span class="o">.</span><span class="n">ask</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span><span class="o">.</span><span class="n">content</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="n">lexical_search</span><span class="p">(</span><span class="no">DOCS_DIR</span><span class="p">,</span> <span class="no">QUERY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">abort</span> <span class="s2">&#34;nenhum arquivo bateu&#34;</span> <span class="k">if</span> <span class="n">files</span><span class="o">.</span><span class="n">empty?</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="s2">&#34;Encontrei </span><span class="si">#{</span><span class="n">files</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2"> arquivos. Carregando contexto...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">context</span> <span class="o">=</span> <span class="n">load_context</span><span class="p">(</span><span class="n">files</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">puts</span> <span class="n">ask</span><span class="p">(</span><span class="no">QUERY</span><span class="p">,</span> <span class="n">context</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>São umas 40 linhas. Sem dependência de Pinecone, sem schema de vector, sem pipeline de re-indexação. Você roda como <code>./ask.rb ./docs &quot;como configurar o webhook do pagamento&quot;</code> e pronto.</p>
<p>Esse exemplo aí é one-shot. Você roda, ele responde, acabou. Pra chat de verdade, com várias perguntas em sequência em cima dos mesmos documentos, o desenho muda. Em vez de fazer o <code>lexical_search</code> lá no começo e empurrar tudo de uma vez pro contexto, você expõe a busca como tool pro modelo. Aí é o agente que decide quando precisa puxar mais doc, que termo vai procurar, qual arquivo vale a pena abrir inteiro. É assim que o Claude Code funciona, na real: <code>Glob</code>, <code>Grep</code> e <code>Read</code> são tools, e o modelo é quem escolhe a sequência. O <code>ruby_llm</code> suporta tool calling, então dá pra fazer a mesma coisa em Ruby. A cara fica 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;ruby_llm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;open3&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">DOCS_DIR</span> <span class="o">=</span> <span class="s2">&#34;./docs&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">SearchFiles</span> <span class="o">&lt;</span> <span class="no">RubyLLM</span><span class="o">::</span><span class="no">Tool</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="s2">&#34;Procura arquivos cujo conteúdo casa com o padrão dado (regex). Retorna lista de paths.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">param</span> <span class="ss">:pattern</span><span class="p">,</span> <span class="ss">desc</span><span class="p">:</span> <span class="s2">&#34;Padrão regex pra busca lexical (case-insensitive)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="ss">pattern</span><span class="p">:)</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="no">Open3</span><span class="o">.</span><span class="n">capture2</span><span class="p">(</span><span class="s2">&#34;rg&#34;</span><span class="p">,</span> <span class="s2">&#34;-l&#34;</span><span class="p">,</span> <span class="s2">&#34;-i&#34;</span><span class="p">,</span> <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="n">pattern</span><span class="p">,</span> <span class="no">DOCS_DIR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:empty?</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ReadFile</span> <span class="o">&lt;</span> <span class="no">RubyLLM</span><span class="o">::</span><span class="no">Tool</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="s2">&#34;Lê o conteúdo completo de um arquivo do projeto.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">param</span> <span class="ss">:path</span><span class="p">,</span> <span class="ss">desc</span><span class="p">:</span> <span class="s2">&#34;Caminho relativo do arquivo&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="ss">path</span><span class="p">:)</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">e</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;erro: </span><span class="si">#{</span><span class="n">e</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="no">RubyLLM</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="ss">model</span><span class="p">:</span> <span class="s2">&#34;anthropic/claude-sonnet-4-6&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">with_tools</span><span class="p">(</span><span class="no">SearchFiles</span><span class="p">,</span> <span class="no">ReadFile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">.</span><span class="n">with_instructions</span><span class="p">(</span><span class="o">&lt;&lt;~</span><span class="no">SYS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="no">Você</span> <span class="n">responde</span> <span class="n">perguntas</span> <span class="n">sobre</span> <span class="n">os</span> <span class="n">documentos</span> <span class="n">em</span> <span class="c1">#{DOCS_DIR}.</span>
</span></span><span class="line"><span class="cl">              <span class="no">Use</span> <span class="n">search_files</span> <span class="n">pra</span> <span class="n">encontrar</span> <span class="n">arquivos</span> <span class="n">relevantes</span> <span class="n">e</span> <span class="n">read_file</span>
</span></span><span class="line"><span class="cl">              <span class="n">pra</span> <span class="n">ler</span> <span class="n">o</span> <span class="n">conteúdo</span><span class="o">.</span> <span class="no">Sempre</span> <span class="n">cite</span> <span class="n">o</span> <span class="n">arquivo</span> <span class="n">na</span> <span class="n">resposta</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">            <span class="no">SYS</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kp">loop</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="nb">print</span> <span class="s2">&#34;&gt; &#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">msg</span> <span class="o">=</span> <span class="nb">gets</span><span class="o">&amp;.</span><span class="n">chomp</span>
</span></span><span class="line"><span class="cl">  <span class="k">break</span> <span class="k">if</span> <span class="n">msg</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="n">msg</span><span class="o">.</span><span class="n">empty?</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">chat</span><span class="o">.</span><span class="n">ask</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span><span class="o">.</span><span class="n">content</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O modelo recebe a pergunta, decide se precisa procurar, chama <code>search_files</code>, vê o que voltou, decide se precisa abrir algum arquivo, chama <code>read_file</code>, e só depois responde. Em pergunta seguinte ele já tem o contexto anterior na sessão e pode pedir mais coisa se precisar. O contexto vai recebendo só o que o modelo pediu, não o despejo inteiro do grep que o exemplo anterior fazia.</p>
<p>A mesma ideia funciona pra banco de dados: troca o <code>rg</code> por uma query SQL com <code>LIKE</code> ou <code>tsvector</code> (full-text do Postgres), carrega as linhas relevantes, joga no contexto. Se você tiver 10k registros num banco interno, isso resolve. Se tiver 10 milhões, aí você começa a precisar de paginação inteligente ou de uma camada de pré-filtragem mais séria. Mas a estrutura mental é a mesma: <strong>filtro burro + leitor inteligente</strong>.</p>
<h2>O ponto que importa<span class="hx:absolute hx:-mt-20" id="o-ponto-que-importa"></span>
    <a href="#o-ponto-que-importa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O mais interessante nisso tudo nem é a economia de Pinecone. É que a natureza do gargalo mudou. Em 2023, o gargalo era retrieval: o leitor era pequeno, lento, caro, e você precisava de um retriever esperto pra encher a janela com o mínimo possível. Em 2026, o gargalo é raciocínio sobre contexto bagunçado: o leitor é grande, relativamente rápido, e barato. Aí faz mais sentido um retriever burrão de alta cobertura e deixar o modelo fazer o trabalho pesado.</p>
<p>Quem ainda projeta sistema com a cabeça de 2023 tá pagando caro pra resolver um problema que mudou de forma. RAG não morreu, o &ldquo;R&rdquo; ficou mais burro e mais barato, e isso é uma melhoria. Quem vende vector DB não vai te contar, mas é o caminho que a galera mais experiente vem seguindo na surdina.</p>
<p>A próxima onda de aplicação LLM, na minha aposta, vai ser dominada por quem entendeu essa inversão. Stack menor, infraestrutura mais simples, contexto generoso, e muito menos LangChain.</p>
<h2>O que a literatura recente diz<span class="hx:absolute hx:-mt-20" id="o-que-a-literatura-recente-diz"></span>
    <a href="#o-que-a-literatura-recente-diz" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de fechar, fui dar uma olhada no que a galera de pesquisa publicou sobre isso. Achismo de blog nessa área envelhece em três meses, então melhor olhar paper.</p>
<p>O <a href="https://arxiv.org/abs/2407.16833"target="_blank" rel="noopener"><strong>Retrieval Augmented Generation or Long-Context LLMs?</strong></a>, do Google DeepMind, publicado na EMNLP 2024, é o mais citado no debate. A conclusão deles: quando o modelo tem recurso suficiente, long context bate RAG na média de qualidade, mas RAG continua sendo bem mais barato em tokens. Eles propõem o Self-Route, uma abordagem onde o próprio modelo decide se precisa do retrieval ou se manda direto pelo contexto. A economia de tokens é grande e a perda de qualidade é pequena.</p>
<p>Já o <a href="https://openreview.net/forum?id=CLF25dahgA"target="_blank" rel="noopener"><strong>LaRA</strong></a>, apresentado na ICML 2025, é mais comedido. Os autores montaram 2326 casos de teste em quatro tipos de tarefa de QA e três tipos de contexto longo, rodaram em 11 LLMs diferentes, e a conclusão foi: não tem bala de prata. A escolha entre RAG e long context depende do modelo, do tamanho do contexto, do tipo de tarefa e da característica do retrieval. RAG ganha em diálogo e queries genéricas, long context ganha em QA estilo Wikipedia.</p>
<p>O <a href="https://arxiv.org/abs/2501.01880"target="_blank" rel="noopener"><strong>Long Context vs. RAG for LLMs: An Evaluation and Revisits</strong></a>, de janeiro de 2025, é o que mais reforça a tese deste post. Long context costuma bater RAG nos benchmarks de QA, especialmente quando o documento base é estável. Retrieval baseado em sumarização chega perto, e retrieval baseado em chunk fica atrás. Ou seja: o jeito antigo, chunk mais embed mais top-k, é o que sai pior.</p>
<p>Tem que ter no radar também o original <a href="https://arxiv.org/abs/2307.03172"target="_blank" rel="noopener"><strong>Lost in the Middle</strong></a> (Liu et al., 2023, publicado no TACL em 2024). Foi o paper que mostrou que mesmo modelos com janela grande têm performance dependente da posição da informação relevante. Coisa no começo ou no fim do contexto é encontrada fácil; coisa no meio degrada. Por muito tempo isso foi usado como argumento contra long context, mas o paper é de 2023, com modelos de 2023. Os modelos de hoje, tipo Claude 4.x e Gemini 3.x, lidam muito melhor com a parte do meio. Não é problema resolvido, mas é bem menor do que era.</p>
<p>Pelo lado de retrieval lexical, o <a href="https://arxiv.org/abs/2104.08663"target="_blank" rel="noopener"><strong>BEIR</strong></a> continua sendo a referência canônica. O resultado clássico é que BM25, lá dos anos 90, segue competitivíssimo em cenário out-of-domain. Os modelos densos só ganham consistentemente quando você tem dados do próprio domínio pra fine-tunar os embeddings. Em cenário zero-shot, que é onde a maioria dos projetos vive, BM25 é difícil de bater sem trabalho pesado.</p>
<p>Pra fechar, o post da <a href="https://www.anthropic.com/news/contextual-retrieval"target="_blank" rel="noopener"><strong>Anthropic sobre Contextual Retrieval</strong></a>, de setembro de 2024, é a peça mais prática da lista. Eles mostram que combinando embedding contextualizado com BM25 contextualizado, dá pra cair de 5.7% pra 2.9% de taxa de falha no top-20. Adicionando reranker, cai pra 1.9%. Detalhe importante: BM25 é peça central do resultado deles, não auxiliar. A leitura correta é &ldquo;lexical mais vetor mais reranker é a combinação que funciona&rdquo;. Quem só pode escolher um, escolhe BM25 e ainda chega longe.</p>
<p>Resumindo o que dá pra cravar: a literatura não diz que &ldquo;RAG morreu&rdquo;. Diz que long context, quando dá pra usar, costuma vencer em qualidade. Diz que o custo de RAG ainda é o argumento principal a favor dele. Diz que BM25 lexical é bem mais forte do que a propaganda de vector DB faz parecer. E diz que, quando você realmente precisa de retrieval pesado, a combinação robusta é hybrid (lexical mais vetor mais reranker), não vetor puro. Tudo isso bate com o que eu venho defendendo na prática.</p>
<h2>Fontes<span class="hx:absolute hx:-mt-20" id="fontes"></span>
    <a href="#fontes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Li, Z. et al. (2024). <a href="https://arxiv.org/abs/2407.16833"target="_blank" rel="noopener">Retrieval Augmented Generation or Long-Context LLMs? A Comprehensive Study and Hybrid Approach</a>. EMNLP 2024 Industry Track.</li>
<li>Yuan, K. et al. (2025). <a href="https://openreview.net/forum?id=CLF25dahgA"target="_blank" rel="noopener">LaRA: Benchmarking Retrieval-Augmented Generation and Long-Context LLMs – No Silver Bullet for LC or RAG Routing</a>. ICML 2025.</li>
<li>Yu, T. et al. (2025). <a href="https://arxiv.org/abs/2501.01880"target="_blank" rel="noopener">Long Context vs. RAG for LLMs: An Evaluation and Revisits</a>. arXiv:2501.01880.</li>
<li>Liu, N. F. et al. (2023). <a href="https://arxiv.org/abs/2307.03172"target="_blank" rel="noopener">Lost in the Middle: How Language Models Use Long Contexts</a>. TACL 2024.</li>
<li>Thakur, N. et al. (2021). <a href="https://arxiv.org/abs/2104.08663"target="_blank" rel="noopener">BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models</a>. NeurIPS Datasets and Benchmarks 2021.</li>
<li>Anthropic (2024). <a href="https://www.anthropic.com/news/contextual-retrieval"target="_blank" rel="noopener">Introducing Contextual Retrieval</a>. Blog técnico.</li>
<li>Akita, F. (2026). <a href="/2026/03/31/codigo-fonte-do-claude-code-vazou-o-que-achamos-dentro/">O código fonte do Claude Code vazou. O que achamos dentro.</a> — minha cobertura do leak, com mais detalhe sobre arquitetura de memória, KAIROS e <code>autoDream</code>.</li>
</ul>
]]></content:encoded><category>llms</category><category>arquitetura-de-software</category><category>bancos-de-dados</category></item><item><title>[Akitando] #149 - Configurando Docker Compose, Postgres, com Testes de Carga - Parte Final da Rinha de Backend</title><link>https://akitaonrails.github.io/2023/12/16/akitando-149-configurando-docker-compose-postgres-com-testes-de-carga-parte-final-da-rinha-de-backend/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/12/16/akitando-149-configurando-docker-compose-postgres-com-testes-de-carga-parte-final-da-rinha-de-backend/</guid><pubDate>Sat, 16 Dec 2023 09:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/-yGHG3pnHLg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não podia terminar o ano com pendências, então eis a parte final da Saga da Rinha de Backend. Neste video vou aproveitar os temas da rinha pra demonstrar em mais detalhes como configurar um Docker Compose de verdade, como funciona testes de carga com Gatling, como usar esses dados pra configurar coisas como o Postgres melhor. Também vamos ver como avaliar exatamente quanto pesa rodar SQL num Postgres, como determinar coisas com tamanho de pool de conexões e muito mais!&lt;/p&gt;</description><content:encoded><![CDATA[

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

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

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

ARG timezone

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

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

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

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

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

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

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

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

WORKDIR /app

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

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

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

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

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

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

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

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

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

<div><pre><code>services:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que tecnologias de compiladores e estruturas de dados tem a ver com banco de dados? Resolvi brincar de fazer um pequeno banco &ldquo;stupid&rdquo; pra vocês terem uma visão diferente sobre o que é um banco de dados por baixo dos panos. Disclaimer: isso não vai ser um tutorial de SQL, mas acho que pode mudar a visão de vocês sobre bancos.</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:35 Problema de Tutoriais/Cursos</li>
<li>00:02:51 Banco de Dados de Usuários</li>
<li>00:03:29 &ldquo;Simulando&rdquo; banco de dados</li>
<li>00:04:58 &ldquo;Fake&rdquo; SQL?</li>
<li>00:07:16 Construindo o Fake SQL</li>
<li>00:10:59 Experimente com Código!</li>
<li>00:15:39 Testando Fake SQL com Jest</li>
<li>00:19:38 Adicionando &ldquo;SQL&rdquo; no &ldquo;Fake SQL&rdquo;</li>
<li>00:22:01 Conhecendo Antlr4</li>
<li>00:24:39 Copiando do SQLite3</li>
<li>00:30:34 Criando meu Listener de Parse Tree</li>
<li>00:39:41 O que é um Índice?</li>
<li>00:49:10 Bancos de Dados tem Interpretadores!</li>
<li>00:49:51 Transaction Log!</li>
<li>00:55:33 Como &ldquo;grava&rdquo; um banco de dados em disco??</li>
<li>01:05:57 B-Trees</li>
<li>01:08:30 Fazendo Cliente-Servidor!</li>
<li>01:09:55 Conclusão</li>
<li>01:12:26 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>GitHub do Stupid Database - código do episódio (<a href="https://github.com/akitaonrails/akitando_episode_0118"target="_blank" rel="noopener">https://github.com/akitaonrails/akitando_episode_0118</a>)</li>
<li>GitHub do repositório de gramáticas do Antlr (<a href="https://github.com/antlr/grammars-v4"target="_blank" rel="noopener">https://github.com/antlr/grammars-v4</a>)</li>
<li>SQLite Amalgamation (<a href="https://www.sqlite.org/amalgamation.html"target="_blank" rel="noopener">https://www.sqlite.org/amalgamation.html</a>)</li>
<li>Visualização de AVL Tree (<a href="https://www.cs.usfca.edu/~galles/visualization/AVLtree.html"target="_blank" rel="noopener">https://www.cs.usfca.edu/~galles/visualization/AVLtree.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>Finalmente resolvi falar um pouco sobre bancos de dados. Esse é provavelmente o primeiro grande desafio pra todo mundo que começa a carreira de programação e parece uma parede muito alta pra escalar, muitos acham intimidante, e muitos começam a usar, decoram alguns comandos SQL, mas não tem muita certeza do que diabos estão fazendo. O objetivo de hoje é tentar dar um modelo mental que vocês consigam enxergar servidores de bancos de dados de uma forma menos complicada.</p>
<p>(&hellip;)</p>
<p>Alguns avisos antes de começar. O episódio de hoje não vai explicar nenhum banco de verdade, seja Postgres ou Mongo. Quem está esperando um tutorial de SQL, não vai ser este video. O começo vai parecer estranho se estava esperando isso, mas tenha paciência e continue acompanhando, eu acho que vocês vão ficar surpresos. Até pra quem já entende de banco de dados, no mínimo vai ter material aqui pra ajudar vocês a explicarem pros seus colegas iniciantes.</p>
<p>Outra coisa, todos os exemplos de código que vou mostrar serão em Javascript. Eu pessoalmente teria feito tudo em Ruby ou Crystal que são linguagens que sou mais confortável, mas acho que a maioria das pessoas assistindo é mais Javascript, então vamos de Javascript. Quem for de outras linguagens deve conseguir acompanhar porque não vou usar nada exótico. Tudo que eu fizer aqui dá pra fazer em Python, PHP, C# ou o que quiserem. Desta vez todo o código está disponível num repositório no GitHub que vou deixar o link nas descrições abaixo. Inclusive fica de lição de casa refazer o que eu mostrar aqui na linguagem favorita de vocês. O código desse repositório é ingênuo de propósito porque foi um exercício que eu decidi fazer durante o feriado de Tiradentes e eu me coloquei um limite de 2 a 3 dias pra fazer tudo.</p>
<p>A motivação desse episódio é porque a menos que eu esteja muito enganado, a maioria dos tutoriais e cursos sobre banco de dados começa mais ou menos assim. Faça &ldquo;apt install mariadb&rdquo;, que é um fork do MySQL. Daí termina de instalar, inicia o serviço, abre o console que conecta no servidor e começa: agora vamos fazer &ldquo;create table users&rdquo; bla bla e depois &ldquo;insert into users&rdquo; bla bla e finalmente &ldquo;select name from users&rdquo; e já vai direto pra explicar SQL. Pior ainda se começa num tutorial de framework, que aí você já cai num Hibernate ou Sequelize e sequer sabe o que tá acontecendo por baixo.</p>
<p>Só que isso eu acho que é ir do zero a 100 muito rápido. Onde que os dados ficam gravados? Onde é esse tal de &ldquo;banco&rdquo;? Como que uma linguagem como SQL atua em cima desses dados? Pra que serve um índice? Qual a diferença disso pra eu simplesmente gravar um JSON num arquivo? Vamos dar uma pausa e começar do começo, bem do começo. E no começo tudo que vocês sabem é lidar com variáveis e collections como arrays ou sets ou, no caso de Javascript, objects.</p>
<p>Pois bem, pra começar resolvi escrever o banco de dados mais simples que dá pra fazer. Chamei ele de Stupid Database, porque não é pra ninguém achar que isso é um banco de verdade. Ele só se comporta como um. Tenho uma tabela de usuários, num array simples, onde cada linha é um objeto de Javascript, como podemos ver aqui. Na 1a linha, com ID 1 temos ninguém menos que nosso querido Peter Parker. Não esse, esse outro aqui. Com uns 25 anos, da cidade de Nova Iorque. Na 2a linha, com ID 2, temos o Clark Kent com 35 anos, de Metropolis, e assim por diante. Um total de 37 linhas nesse array, com várias pessoas desconhecidas, de nomes comuns.</p>
<p>O que é isso? É um array de objetos. Também podemos chamar isso de &ldquo;tabela&rdquo;. Olha só. Se eu abrir um console de Node.js, carregar esse arquivo com <code>db = await import('./lib/users-database.mjs')</code> e fizer <code>console.table(db.users)</code>, ele vai printar exatamente como qualquer um esperaria ver uma tabela, certo? Pra todos os efeitos e propósitos, essa é uma tabela mínima.</p>
<p>Ok, mas se é uma tabela, é possível inserir novas linhas, certo? Claro. Podemos criar um novo usuário, vamos dar o nome de outra pessoa desconhecida de nome comum, Levi Ackerman, uns 37 anos, de Paradis. E pra inserir num array de Javascript usamos a função <code>.push</code>. E no nosso caso, inventei que o id de cada linha é um número crescente que começa em 1, então essa nova linha vai ser o tamanho da tabela mais um. E pronto, esse seria o equivalente em SQL a fazer um <code>insert into users</code> da vida.</p>
<p>Beleza, mas então qual é o equivalente a um <code>select</code> de SQL? Simples, uma das formas é a função <code>filter</code>. Eu passo pra função outra função que ele vai executar em cada objeto, um por um em cada linha desse array, no caso, procurando o objeto que tem o nome &ldquo;Levi Ackerman&rdquo;. O resultado disso é outro array só com os objetos encontrados. Podemos passar de novo pelo comando <code>console.table</code> e voilá, esse é o resultado correto, que seria o equivalente em SQL a fazer <code>select * from users where name = 'Levi Ackerman'</code>.</p>
<p>Aí nesse ponto você deve estar cético, pensando aí. Porra, Akita tá de sacanagem. Isso aí é só uma porcaria de um array, não é um banco de dados. Eu quero ver comando de SQL, porra! Ok, talvez você tenha razão. Então deixa eu ir pro terminal e executar um programinha que eu escrevi, um REPL. Lembram o que é um REPL? O comando <code>node</code> onde eu estou é um REPL, um console que interpreta os comandos que eu digito imediatamente e já me dá o resultado. O REPL <code>node</code> interpreta javascript e executa. Pra quem é de Python, se digitar <code>python</code> vai abrir o REPL de Python, que executa imediatamente os comandos em Python que digitar, e assim por diante. Várias linguagens tem REPLs que já vem na instalação ou que você pode instalar separadamente, mas só lembrar que REPL é um console interativo da linguagem. É uma ferramenta comum em linguagens dinâmicas.</p>
<p>Enfim, vou executar o REPL que eu escrevi e chamei de <code>repl.mjs</code>. Olha só, abriu uma linha de comando. Vocês queriam ver SQL? Tá bom, vamos começar digitando <code>insert into users (name, age, city) values ('Levi Ackerman', 37, 'Paradis')</code>. Opa, retornou o número 38. Agora vamos ver se inseriu mesmo, vou digitar a query <code>select * from users where users.city = 'Paradis'</code>. E, olha só, achou esse senhor desconhecido de nome Levi, como deveria.</p>
<p>Pronto, não era SQL que vocês queriam ver? Tá aqui. Mas isso não é MySQL, não é Postgres, nada nessa manga, nada nessa outra, é só Javascript puro. Vamos fazer outra query, quero todos os indivíduos nessa tabela com idades entre 60 e 2000 anos, porque alguns indivíduos tem expectativa de vida bem alta ultimamente. Como que faz? <code>select * from users where users.age between 60 and 2000</code>, é assim que vocês aprenderam né? E olha só, obviamente, achou certinho. Olha lá o Steve Rogers com 100 anos, acho que é 100 né? Ou nosso amigo Thor com 1500 anos. Só não comer açúcar, fazer dieta intermitente, e vive saudável muitos anos.</p>
<p>Eu espero que eu tenha conseguido deixar vocês confusos. O que diabos tá acontecendo aqui? Eu abri uma linha de comando bizarra e comecei a digitar SQL como se fosse um Postgres da vida, e ele claramente tá pesquisando em cima daquele array de objetos em Javascript. Como isso é possível? E esse é o real objetivo desse episódio, eu quero mostrar como eu fiz isso e em paralelo vocês entenderem mais ou menos como um banco de dados de verdade é feito. Então vamos lá.</p>
<p>Primeiro de tudo vamos deixar super claro: isto é meramente um exercício educacional, um código que eu me limitei em uns 2 a 3 dias pra fazer. Todo código que vou mostrar a partir de agora não tem condições de ser usado em produção de verdade. Pra isso ele precisaria ser bem mais complicado pra levar em consideração coisas como autenticação e autorização, locks e concorrência, otimizações de performance, otimizações de redundância, etc e tudo isso só ia dificultar a explicação. Por isso eu fiz só o mínimo do mínimo necessário pra ficar fácil de explicar, que é o objetivo de todo código educacional.</p>
<p>O segredo de tudo começa na biblioteca que eu chamei de <code>lib/fake-sql.mjs</code>. Essa é uma coleção de funções que eu escrevi com nomes estratégicos. Deixa eu mostrar. A primeira é a função <code>from</code> que recebe como argumentos as variáveis <code>table</code> e <code>conditions</code>. Lá na frente eu vou carregar o array <code>users</code> e outro array chamado <code>heroes</code>, tudo dentro de um hash chamado <code>database</code>, daí se fizer <code>database['users']</code> eu acesso o mesmo array que tava mostrando até agora.</p>
<p>E aqui uma das vantagens de usar uma linguagens dinâmica interpretada como Javascript. Já que performance e segurança não são preocupações pra hoje, eu posso fazer um protótipo simplesmente montando aquele comando de <code>filter</code> como fiz antes concatenado com a variável <code>conditions.where</code>. Se não ficou claro, deixa eu mostrar o que essa função faz. Vamos abrir o console do Node e deixa eu dar uns copy e paste só pra importar esse arquivo &lsquo;fake-sql.mjs&rsquo; e o mesmo &lsquo;users-database.mjs&rsquo; de antes. Não se preocupem com essa parte que é só carga.</p>
<p>Pronto, agora vamos fazer a mesma pesquisa de antes onde eu quero todo mundo com idades entre 60 e 2000 anos. É só fazer <code>from('users', { where: 'users.age &gt;= 60 &amp;&amp; users.age &lt;= 2000'})</code> que é a mesma coisa que o <code>between 60 and 2000</code> que fiz em SQL. De propósito chamei essa função de <code>from</code> e fiz um hash com uma chave <code>where</code> pra ficar meio parecido com SQL. E olha só, voltou o mesmo resultado de antes. Tô vendo o Steve Rogers, o Thor, a Diana e o Hank Pym. Essa função é o grosso do meu simulador de SQL, todo o resto agora é perfumaria.</p>
<p>Pra parecer mesmo SQL falta começar com <code>select</code> né? Pra que serve o select? Serve pra eu pegar só as colunas, ou chaves, que eu quiser, em vez de todas. Vamos criar a função <code>select</code> lá na biblioteca então. E olha só, parece mais complicadinho. Essa função recebe como argumentos uma lista de colunas e o resultado do <code>from</code>, ou seja, aquele array de resultados.</p>
<p>A primeira metade é outra trapaça. Se eu pedir &ldquo;*&rdquo; ele vai substituir por uma lista com todas as chaves dos objetos. Aqui estou assumindo que todos os objetos do resultado sempre tem as mesmas chaves. Não estou checando nada e não vou introduzir o conceito de Schemas, por isso falei que tô trapaceando. Enfim, daí ele vê se o argumento foi a palavra &ldquo;count&rdquo;, daí devolve direto só o total de objetos no array de resultados. E caso contrário, pega a string que passei e dá split por vírgula, pra transformar num array.</p>
<p>O resultado dessa função é um <code>map</code>. Lembra o que faz um map? Ele pega uma coleção, transforma cada elemento e devolve tudo em outra coleção, convertendo cada objeto da coleção original usando essa função que passo de parâmetro. Em resumo, vai transformar meu objeto original num outro objeto só com as chaves que pedi pra selecionar. Vamos ver na prática lá no mesmo console de Node.</p>
<p>Estou de novo no console e faço <code>select('name,age')</code>. Aqui é uma escolha de design, eu poderia passar como parâmetro um array com os strings &rsquo;name&rsquo; e &lsquo;age&rsquo; mas só pra ficar mais &ldquo;limpo&rdquo; nessa sintaxe que tenta imitar SQL, decidi passar tudo grudado num único string. Por isso, do lado de dentro, preciso fazer um split. Obviamente esse é o jeito menos otimizado de passar parâmetros, mas como eu disse antes, é só um exercício.</p>
<p>Aproveitando a deixa, é isso que eu falo em vários videos: experimente com código. Faça experimentos como esse na sua máquina, código que não é de nenhum projeto do trabalho, é só um treino sem compromisso. E aí experimente designs que normalmente ninguém deixaria você fazer no trabalho. Só quando se experimenta coisas que ninguém faria é que às vezes pode dar inspiração pra coisas que podem ser úteis.</p>
<p>Pra quem curte anime, isso me lembra o Food Wars, já viram, Shokugegi no Souma? É uma história de competição numa escola culinária onde o protagonista, por acaso, adora fazer comidas bizarras que nunca dão certo. Esse é o motivo. Se você nunca tentou fazer os códigos mais impensáveis, nunca vai chegar num design inovador de verdade. Abra um diretório vazio, começa a escrever, apaga, joga fora, vai treinando. Ninguém precisa nem saber.</p>
<p>Enfim, voltando ao console, depois de passar a lista de colunas que queremos, como segundo parâmetro colocamos a mesma função <code>from</code> de antes. Olha o resultado: uma lista filtrada só com as colunas que queremos, sem a coluna de cidade ou id, no caso. E veja de novo a implementação. Eu pego o resultado e faço um map. A função de transformação faz um array só com as colunas que eu escolhi. E no final faço um <code>reduce</code> pra recriar objetos com o nome das colunas como chave.</p>
<p>Parece um pouco complicado se você nunca usou &ldquo;map&rdquo; e &ldquo;reduce&rdquo;, que existem em todas as linguagens e servem pra transformar listas e reduzir o resultado num acumulador. Teste só esse trecho num script com vários tipos de listas pra entender o que isso tá fazendo. É bem básico e você precisa saber. De novo, exercite até conseguir enxergar esse trecho e saber exatamente o que está acontecendo. Tem outras formas de escrever a mesma coisa pra ter o mesmo resultado, eu poderia não ter usado map e reduce e feito só com <code>for</code>.</p>
<p>Enfim. Olha o que estou fazendo. Só de escrever duas funções, esse &ldquo;select&rdquo; e &ldquo;from&rdquo; que recebe um hash de condições com uma chave &ldquo;where&rdquo;, já consigo simular o básico do básico de um select de SQL. Só pra ver mais um exemplo, num SQL você sabe que poderia fazer <code>select name from users where bla bla order by name desc</code> pra ordenar o resultado em ordem decrescente de nome. Como daria pra fazer nesse meu fake SQL? Vamos criar uma nova função chamada <code>orderBy</code> que recebe como parâmetro o nome da coluna por onde queremos ordenar, daí um segundo parâmetro pra dizer se é ordem ascendente ou descendente e como terceiro parâmetro, de novo o resultado da função <code>from</code>.</p>
<p>Essa implementação é simples, é basicamente o que qualquer iniciante de Javascript que já estudou algoritmos simples de ordenação já exercitou: implementar a função de comparação que se passa pra função <code>sort</code> que todo array tem. Novamente, se nunca tentou ordenar um array procure no google &ldquo;como ordena um array em javascript&rdquo; e vai achar a explicação dessa função. Não tem nada de especial aqui.</p>
<p>Vamos voltar pro console de Node. Como que fica pra usar isso? Aqui é que vai ficar um pouco diferente de um SQL normal porque o &ldquo;ORDER BY&rdquo; costuma ir no final do comando, mas na minha versão em Javascript, vai antes. E fica assim <code>select('name', orderBy('name', 'desc', from('users', bla bla bla)))</code>. E olha só, funcionou direitinho, retornou ordenado em ordem decrescente, Lois Lane na frente, Barbara Gordon no final. Pára e pensa, com a função <code>from</code> filtramos a tabela inteira pra pegar só quem é de Gotham ou Metropolis. Agora ordenamos o resultado, e finalmente selecionamos só as colunas que queremos. E por que essa ordem? Porque poderíamos escolher ordenar por idade mas no final só selecionar a coluna de nome. Por isso a ordenação vai antes de selecionar as colunas pro resultado final.</p>
<p>Só pra terminar essa parte, vamos ver rapidamente só mais duas funções. Como faz o equivalente a um &ldquo;insert&rdquo; de SQL? Essa é fácil. é só usar a função <code>push</code> que todo array tem como já mostrei antes. No caso, eu estabeleci que todo objeto vai ter um ID que é numérico e cresce incrementalmente, como muita primary key padrão que todo mundo cria, só pra ficar mais fácil. Vamos no console do Node e testar isso. Mesma coisa do primeiro exemplo <code>insert('users', { name: 'Levi Ackerman', age: 37, city: 'Paradis' })</code>. E devolve 38 que é o ID que ele calculou pra esse objeto.</p>
<p>Agora pra simular delete como no SQL é a mesma coisa que o filter da função <code>from</code> pra encontrar os objetos que queremos, daí só usamos a função <code>pop</code> que todo array de Javascript tem, e isso vai arrancar os objetos da lista. Nesse código-fonte eu implemento outras funções como o equivalente a &ldquo;update&rdquo; e tem uma versão inacabada de como seria um &lsquo;inner join&rsquo; e &lsquo;outer join&rsquo; se tiverem curiosidade de tentar terminar da forma correta. E é assim que vou fazendo um fake-sql pra simular os diversos aspectos que tem num banco de dados SQL comum.</p>
<p>Mas claro, mesmo sendo um exercício, já tem código suficiente aqui pra confundir a gente. Então esse é um bom momento pra fazer alguns testes unitários pelo menos né? Lembram como faz? Primeiro é só fazer um <code>npm install --save-dev jest</code> pra instalar o framework de testes Jest. Agora abrimos o arquivo <code>package.json</code> e colocamos esse comando grandão em scripts, test. Eu precisei usar essa flag experimental pra conseguir carregar os módulos ES certinho. Não sei se é o melhor jeito, mas foi como funcionou aqui.</p>
<p>Agora é só rodar <code>npm test</code> no terminal e ele vai executar os testes que eu já tinha feito no arquivo <code>stupid.test.js</code>. O comando do Jest vai vasculhar por arquivos terminados em &ldquo;.test.js&rdquo; e rodar os testes que tem lá dentro. E como é esse arquivo? Vamos abrir rapidinho só pra ver alguns exemplos. As primeiras linhas são pra carregar as funções do fake-sql e as tabelas de users e heroes.</p>
<p>Criamos um bloco de describe pra função &ldquo;select&rdquo; que é de longe quem tem mais casos de testes. Sabe todos aqueles testes que fizemos manualmente no console do Node? Pois é, é o que queremos automatizar em forma de testes unitários. Por exemplo, o primeiro teste é que deve achar o nome, então executa aquela mesma linha de select name from users where users.name igual Peter Parker. Pega o array de resultados, escolhe o primeiro objeto e espera que a propriedade “name” desse objeto seja Peter Parker, “to be” Peter Parker, se for, esse teste passa. E vai fazendo isso pra outros exemplos.</p>
<p>Vocês podem olhar esse arquivo com calma depois que tá no repositório. Link na descrição abaixo como falei, mas veja o último que é mais um teste integrado. Eu começo fazendo dois novos inserts, um pra tabela users com o objeto do Katar Hol, e outro insert na tabela heroes dizendo que na verdade ele é o Hawkman! Quem diria? E ligando com o que seria equivalente a uma chave estrangeira numa tabela de SQL, fazemos a propriedade user_id desse objeto ser igual ao valor que voltou no insert anterior. E é assim que seria um sisteminha CRUD da Liga da Justiça, pensa o estag do Bruce Wayne fazendo isso.</p>
<p>Só pra comparar depois, pedimos a contagem das duas tabelas e guardamos nessas duas variáveis de total. Agora fazemos um select com o innerJoin que eu não mostrei o código, mas se você já aprendeu SQL, inner join é uma forma de fazer a intersecção de duas tabelas de resultado. Ele vai ligar o resultado desse <code>from</code> feito na tabela users com a tabela heroes via a chave <code>user_id</code>. E esperamos receber uma tabela de resultado com as colunas name e alterego. Alter ego só tem na tabela heroes.</p>
<p>Então esperamos que na primeira linha do resultado, a propriedade name seja Katar Hol e o alterego seja Hawkman. Continuando, digamos que eu errei a idade do Katar, não era 35 e sim 37. Então eu posso usar essa outra função <code>updateFrom</code> que eu não mostrei o código. Quero que o age, a idade, mude pra 37, e isso nos objetos que voltar da mesma função <code>from</code> onde a cidade é Thanagar.</p>
<p>Eu faço de novo um select pela mesma condição e checo que a idade que voltou mudou pra 37 mesmo. E estamos quase no fim. Agora quero testar a função <code>deleteId</code> que também não mostrei o código. Vou apagar os objetos com os ids user_id e hero_id que tinha guardado lá em cima. E pra saber se apagou mesmo, peço um select count pra trazer a contagem das duas tabelas e tem que ter um objeto a menos do que tinha antes de quando eu gravei os totais depois do insert lá em cima. Vamos pro terminal, se rodar <code>npm test</code>, olha só, tudo passa corretamente. E agora eu tenho um mínimo de testes caso eu queira brincar de mexer mais nesse código.</p>
<p>Testes é outro assunto super longo, mas mesmo num exercício de fim de semana como esse, ter testes me ajudou muito. Se não tivesse eu teria levado o dobro do tempo pra fazer as coisas. Mas eu não segui nenhum tipo de TDD, eu escrevia um pedaço e testava depois, mudava o design, mudava o teste, e fui fazendo assim. Não pensa tanto em procedimento, só faz a porra do teste.</p>
<p>Até esse ponto implementei um monte de funções de javascript com nomes como select, insert, updateFrom, from, innerJoin, orderBy e assim por diante pra imitar os nomes dos comandos mais comuns que todo banco de dados SQL tem. E como exercício, eu poderia parar por aqui. Entenderam mais ou menos como é possível simular um fake SQL em cima de arrays de javascript? Mas claro, não é isso que você queria ver. Por mais parecido que seja, isso ainda não é SQL. Funções de Javascript tem um monte de parênteses, o orderBy tá na ordem errada, tá parecido mas tá longe de estar igual a SQL.</p>
<p>O que eu quero é poder ter um script como eu fiz nesse arquivo <code>test.sql</code> que também tá no repositório. Olha só, vários comandos SQL igualzinho o que você conseguiria executar no MySQL, SQL Server. Pra executar esses SQL eu fiz outro script chamado <code>stupid.mjs</code>. Vamos ver o finalzinho dele. Olha só, ele abre o arquivo <code>test.sql</code>, passa o script nessa função esquisita <code>parseScriptWithSplitQueries</code> e pra cada linha devolvida dá um eval. O que acontece se executar isso? Vamos voltar pro terminal e executar.</p>
<p>E voilá, ele executa todos os comandos SQL! Eu dei <code>console.log</code> do que tá rodando, e são minhas funções Javascript e não o SQL. Como que faz isso? Vamos voltar praquele script e o segredo começa naquela função esquisita <code>parseScriptWithSplitQueries</code>. Ela passa o conteúdo do arquivo <code>test.sql</code>, que são aqueles montes de comandos SQL, pra essa classe SplitQueries. Se olhamos lá em cima, ela é importada desse outro arquivo <code>parser/SplitQueries.mjs</code>. Vamos abrir ela.</p>
<p>Pronto, a primeira coisa que ele faz é importar essa biblioteca chamada Antlr4. E vamos pausar aqui pra eu explicar. Como expliquei no começo, no feriado de Tiradentes eu tava pensando em como ia explicar banco de dados pra vocês, aí tive a idéia de fazer um do zero. Se eu terminasse o episódio aqui, acho que já dava pra ter o primeiro gostinho de como um banco funciona por baixo. Esse código que mostrei até agora é super trivial de fazer, não foi mais que algumas poucas horas brincando. Mas a cereja do bolo seria mostrar como que comandos SQL de verdade funcionam e apesar das funções javascript que fiz terem nomes parecidos com SQL, ainda não é SQL. E aí fiquei coçando a cabeça, porque eu queria mostrar SQL, mas eu não queria gastar muito mais que um dia pra isso.</p>
<p>O episódio que lancei antes desse justamente explica o que é uma linguagem compilada e interpretada, e mencionei que tanto um compilador quanto um interpretador começam com um parser. E uma das formas de se fazer um parser é usar um software chamado Antlr4, que é feito em Java. Naquele episódio eu disse que não ia explicar ele. Pois bem, vou explicar rapidinho agora. O antl4 é um gerador de parsers. Lembram o que é um parser? É quando pegamos uma gramática, passamos o código fonte por um lexer pra conseguir uma coleção de tokens e passamos esses tokens pra um parser pra conseguir uma ParseTree.</p>
<p>Num interpretador ou compilador, essa ParseTree é transformada numa Abstract Syntax Tree ou AST, que daí passa por uma série de otimizadores pra reescrever o código da forma mais eficiente possível, e aí finalmente gerar o código de máquina tanto pra executar na hora, caso seja um Just in Time Compiler ou gerar um binário num arquivo, caso seja um Ahead of Time Compiler ou AOT. No meu caso, eu não preciso fazer tudo isso, só preciso ir até o ponto onde gera uma Parse Tree, pra a partir dela, fazer um transpiler, que vai converter SQL em Javascript. Entenderam onde eu quero chegar?</p>
<p>Escrever uma gramática do zero é extremamente tedioso e trabalhoso. Mas minha vantagem é que não estou inventando uma nova linguagem, pelo contrário, eu quero um SQL que seja o mais compatível possível com o SQL de verdade. E aqui vem outra vantagem do Antlr4. Ele tem uma comunidade muito esforçada que já se deu ao trabalho de escrever a gramática de praticamente todas as linguagens que você já ouviu falar e um monte que vocês nem sabiam que existiam. Vou deixar o link do repositório nas descrições abaixo, mas olha essa página do GitHub que coisa linda.</p>
<p>Lembram que eu falei da linguagem BCPL? Quer saber como é a gramática? Basta entrar aqui nessa pasta e abrir o arquivo <code>bcpl.g4</code> que é a extensão de gramática do antlr4. Olha o que eu expliquei no episódio anterior. Começa definindo o que ele entende por &ldquo;letras&rdquo;, são todas as maiúsculas e minúsculas. E essa distinção é importante porque tinha linguagens antigas que só entendiam comandos em maiúsculo. Continua definindo, o que é um dígito em octal, um dígito em hexadecimal, o que são dígitos numéricos.</p>
<p>E qual é o tamanho da gramática pra entender BCPL inteiro? Que é a linguagem que inspirou o C? Nada mais que 131 linhas, contando linhas em branco. E se a gente voltar pra primeira página, olha lá embaixo, uma pasta chamada &ldquo;python&rdquo;, dentro tem python2, e no python3 temos de novo arquivos .g4 que ele separou pro lexer e pra gramática do parser. E mesmo esse arquivo tem menos de 200 linhas.</p>
<p>Mas de volta à primeira página, o que nos interessa mesmo é essa pasta sql lá embaixo. Tem as gramáticas do mysql, postgres mas o que eu queria, e ainda bem que tinha, é do sqlite3, que é o banco de dados sql mais simples e provavelmente o mais usado do mundo. Isso porque enquanto um postgres da vida é usado em servidores, todo dispositivo IoT, internet das coisas, que é qualquer dispositivo com um mínimo de processamento com acesso a internet, precisa de um banco de dados local. Pense um roteador de wifi da sua casa. 99% de certeza que dentro usa um sqlite3. Pensa vários apps no seu celular Android, vários sqlite3. Pensa uma geladeira inteligente, certamente tem sqlite3 dentro.</p>
<p>Aliás, o que estou construindo neste episódio estou chamando de stupid database, porque é o banco mais besta e ao mesmo tempo mais simples com objetivo educacional, mas se quiser ver o menor banco de dados SQL realmente completo e funcional, tem que estudar o código C do Sqlite3. Existe um subprojeto chamado Sqlite Amalgamation que concatena os mais de 100 arquivos de código fonte num único arquivo &ldquo;sqlite3.c&rdquo; e por isso se chama amalgamation.</p>
<p>O sqlite3 inteiro consiste de 75% de código C e o resto é arquivos .h de cabeçalhos. Esse arquivão dá um total de quase 240 mil linhas ou 145 mil se tirar linhas em branco, totalizando 8.4 megabytes de código fonte. Se um dia realmente quiser saber em detalhes como um banco de verdade funciona, precisa entender como o sqlite3 funciona, O código fonte tá disponível pra ler e ir testando pedaço a pedaço. Bom trabalho de iniciação científica ou TCC pra quem tá na faculdade de ciências da computação.</p>
<p>Mas, explicar 145 mil linhas de código em um único episódio seria impossível então espero que se contentem com essa minha versão de stupid database. Enfim, falei tudo isso pra dizer que foi nesse respositório de gramáticas do Antlr4 que peguei emprestado os arquivos &ldquo;SqliteLexer.g4&rdquo; e &ldquo;SqliteParser.g4&rdquo;. E como eu não ia ter comandos como &ldquo;create table&rdquo;, &ldquo;alter table&rdquo; e coisas assim, saí apagando da gramática tudo que não ia suportar até ficar com uma versão mais reduzida. Ainda sobrou muita coisa que eu não implemento, mas apaguei o suficiente pra facilitar meu trabalho.</p>
<p>Daí eu precisei instalar o antl4, e pra isso é só ir no site deles e ver como instala, não vou detalhar aqui. Assumindo que já tá instalado, no terminal é só rodar o comando <code>antl4 -Djavascript</code> pros dois arquivos SqliteLexer e SqliteParser.g4. Esse flag &ldquo;-D&rdquo; diz em que linguagem ele vai gerar o lexer e parser. E dá pra gerar em praticamente todas as linguagens que quiser, seja python, java e no nosso caso, javascript. Isso gera esses arquivos &ldquo;.interp&rdquo; e olha só SqliteLexer.js e SqliteParser.js. Esses arquivos a gente não precisa mexer. Nem vou abrir porque pode assustar os mais fracos de coração.</p>
<p>O mais importante é que ele gerou esse SqliteParserListener.js. Esse podemos abrir e vamos descobrir um monte de funções vazias. Que diabos que são essas funções? E eles estão em pares, veja só aqui no começo temos uma função chamada &ldquo;enterParse&rdquo; e depois &ldquo;exitParse&rdquo;. Se abrirmos aquele arquivo que pegamos emprestado, o SqliteParser.g4 podemos ver aqui no começo que &ldquo;parse&rdquo; tem esse &ldquo;sql statement list&rdquo; e EOF que é End of File ou final do arquivo. Daí vemos que sql statement list tem vários sql statements e sql statement pode ser vários tipos como delete statement, insert statement, select ou update. Entenderam? É assim que ele começa definindo as regras do que esse parser consegue entender, e são justamente as operações que queremos. Então já sabemos que o ponto de entrada no parser vai ser por essa regra “parse” que engloba o arquivo inteiro de comandos SQL que passarmos pra ele.</p>
<p>A gente não mexe nesse arquivo de Listener também, porque se amanhã eu mexer no arquivo .g4 e mandar regerar com o comando <code>antl4</code>, daí vai sobrescrever nesses arquivos todos. Por isso criei esse outro chamado CustomListener.js. Ele é basicamente uma classe que extende da classe SqliteParserListener e lá embaixo eu copio todas aquelas funções vazias pra sobrescrever elas. Agora sim posso começar a brincar.</p>
<p>Eu não vou mostrar tudo em detalhes, depois vocês podem olhar o código fonte no meu repositório. Mas só pra entender vamos voltar pro arquivo principal desse diretório, o SplitQueries.mjs que é pra onde eu tava mandando os comandos SQL do arquivo <code>test.sql</code> lembram? Agora que expliquei o que são os principais arquivos, veja que nesse script eu importo todos eles, o Lexer, o Parser e o meu CustomListener. Ele começa recebendo um input, que pode ser uma linha de SQL ou várias linhas de SQL como o conteúdo do arquivo <code>test.sql</code>.</p>
<p>Uso a função InputStream da biblioteca antlr4 pra quebrar num stream de caracteres. Lembra o que são streams? Lembro que já expliquei isso em algum episódio, mas pra quem não sabe, iniciantes estão acostumados a carregar o conteúdo de um arquivo tudo de uma vez em memória, mas e se o arquivo não couber na memória? O jeito mais eficiente é ir lendo pedaços do arquivo de cada vez e ir processando aos poucos, pra isso abrimos um stream. Enfim, passamos esse stream pro Lexer, que criar uma lista de tokens a partir desse stream. Ou seja, ele recebe a linguiçona de strings e vai quebrando numa lista de tokens.</p>
<p>Agora passamos esses tokens pro Parser e em seguida pedimos pra construir a Parse Tree, a árvore, como expliquei no episódio anterior. Pra você que é de front-end, é como quando o navegador carrega uma página HTML, que é um stream de caracteres, e gera o DOM, a árvore que representa aquele HTML. Agora criamos uma instância do nosso CustomListener passando a referência pra esse array “result” que é onde vamos escrever o javascript final. Pra gerar essa parse tree, começamos da primeira regra que tem no nosso arquivo .g4 do parser, que é a regra chamada &ldquo;parse&rdquo;, por isso chamamos <code>parser.parse</code>.</p>
<p>Finalmente, fazemos o antl4 passear pela parse tree, nó a nó, por isso essa função chama <code>ParseTreeWalker</code> e damos como parâmetro pra ele o Listener, que tem as regras de conversão, e a árvore propriamente dita. E é aqui que entra meu CustomListener. Vamos entrar lá de novo. Aqui eu vou dar uma trapaceada porque quero gerar o javascript baseado no meu fake sql, lembra? As funções em javascript que eu criei chamada &ldquo;select&rdquo;, &ldquo;from&rdquo;, &ldquo;orderBy&rdquo;. Pra isso eu vou pegar os pedaços que a ParseTree vai me dando e vou preenchendo nesse objeto construído pela função <code>buildSqlStruct</code> que eu inventei. Ela tem os pedaços que formam um comando SQL. Olha só como tem um array pra colunas, outro array pra condições, e flags pra saber se tem &ldquo;order by&rdquo; e coisas assim.</p>
<p>Logo embaixo tem a função <code>enterParse</code>, mas nesse momento eu não preciso fazer nada. Deixa ele ir navegando pela árvore até achar um comando como &ldquo;insert&rdquo; ou &ldquo;select&rdquo;. Digamos que eu tenha um comando como <code>insert into users (name, age, city) value ('Katar Hol', 37, 'Thanagar')</code> como mostrei lá no começo. Um comando sql normal de insert. Olha o nosso arquivo de parser .g4, a regra do insert se chama <code>insert_stmt</code> de statement. Então provavelmente quando o parser achar o insert, vai chamar a função <code>enterInsert_stmt</code> passando um contexto nessa variável <code>ctx</code> que é a sub-árvore do comando de insert.</p>
<p>Essa parte foi a mais trabalhosa que é decifrar o que vem nesse contexto. E já aviso que a documentação que achei online não foi muito útil não. Na prática, o que eu aprendi a fazer foi executar o parser com um comando SQL de teste e vasculhar o que vem no contexto usando o bom e velho <code>console.log</code> e eu entendi que o que importa é saber quem são os nós filhos, ou seja <code>ctx.children</code>. Então, vamos lá na função de entrada do insert e deixa eu colocar esse <code>console.log</code> aqui. Se eu printar o ctx inteiro vai ser super longo, então só imprimo o nome da classe a o <code>getText()</code> que é representação em texto desse nó. Vamos abrir o REPL e mandar o comando insert de teste.</p>
<p>Olha só o que ele imprime: os nós, que são instâncias de TerminalNode costumam ser tokens como o comando &ldquo;insert&rdquo; e as outras partes do comando como &ldquo;into&rdquo;. Daí quando tem um nome de tabela, com outra super classe, no caso Table_nameContext, daí vem outro TerminalNode que é a abertura de parênteses. Daí temos um Column_nameContext que é o campo &ldquo;name&rdquo; e assim por diante. Foi isso que o Lexer fez, ele quebrou o comando e o Parser organizou nesse sub-árvore de nós que podemos navegar. Vamos voltar pro código no Listener.</p>
<p>Já sabemos que é só navegar por cada nó dos filhos do contexto, então pra cada <code>child</code> de <code>ctx.children</code> eu faço um switch case. Se a super classe for Table_nameContext eu preencho o nome da tabela naquele objeto sqlStruct. Próximo nó, se for um TerminalNode, não faço nada. Próximo nó, se for um Column_nameContext vou acumulando numa lista de colunas no objeto sqlStruct e mesma coisa se forem ExprContext, são os valores, então vou acumulando na lista <code>values</code> no sqlStruct. Quando acabar a lista de nós filhos, sai dessa função. Daí o Walker vai chamar a função de saída que, por convenção, vai se chamar <code>exitInsert_stmt</code>, por acaso é a função logo embaixo nesse Listener.</p>
<p>Pronto, quando entra nessa função sabemos que navegamos por todo o comando insert. Agora é só pegar o que cadastramos no objeto sqlStruct e literalmente montar uma string com a versão do comando em javascript. Primeiro eu monto uma string combinando o nome de cada coluna como chave, dois pontos, o valor, que é a sintaxe de javascript pra montar um objeto. Depois eu começo a montar a função &ldquo;insert&rdquo;, concateno o nome da tabela e depois junto os valores com vírgula entre chaves. Isso vai gerar esse comando em javascript que eu mostro no REPL antes de executar.</p>
<p>Expliquei meio rápido mas deu pra entender a idéia? Todo comando que eu passar praquela função SplitQueries vai passar pelo Lexer e Parser, e vai gerar uma árvore, a Parse Tree. Daí eu ando nessa árvore, nó a nó, usando as funções do Listener. Toda vez que quero implementar um comando SQL, pego a função de entrada, como a <code>enterInsert_stmt</code>, pego o contexto que é a sub-árvore e vou dividindo os pedaços que preciso e acumulando na minha estrutura sqlStruct. E na função de saída monto a string com a versão em Javascript, que é o que a função SplitQueries vai retornar, no caso pro REPL.</p>
<p>Vamos abrir o script do tal REPL, a linha de comando que vim mostrando até agora. Eu uso uma biblioteca chamada &ldquo;prompt-sync&rdquo; que faz exatamente o que o nome diz: abre um prompt que fica esperando eu escrever algum comando. Tá vendo ele escrevendo &ldquo;stupid query&rdquo;? Daí eu digito o comando insert e ele captura nessa variável <code>query</code>, que vai passar pra função <code>parseScriptWithSplitQueries</code> que tá definido lá em cima. Igual eu tava fazendo no script <code>stupid.mjs</code> que lia comandos do arquivo <code>test.sql</code>.</p>
<p>Essa função passa o script pro SplitQueries, que é o parser, que vai navegar e transformar a árvore e cuspir um string com o comando javascript do nosso fake-sql. Pra cada linha que ele cuspir dou um <code>eval</code> e executo, imprimo o resultado na tela com o <code>console.table</code> e no final abro o prompt de novo, e fica nesse loop, que é o que um repl tem que fazer mesmo. E pronto, é assim que se constrói um interpretador simples de SQL em javascript.</p>
<p>O que eu fiz no Listener pra converter o insert em javascript agora preciso fazer a mesma coisa pros comandos delete, update, e select, que é o mais complicado de todos. Vamos só bater o olho no select. Começa pela função <code>enterSelect_stmt</code>. Diferente do insert que forcei olhar pros nós filhos direto, aqui só crio o objeto sqlStruct que vou precisar preencher e deixo o Walker passear pelos próximos nós pra ir coletando o que eu preciso. Por isso falei que é mais complicadinho, porque precisa implementar várias regras diferentes do Listener que não vou mostrar em detalhes aqui.</p>
<p>Em particular um select é complicado porque nas condições que começa depois do Where eu posso ter uma série de expressões, e expressões dentro de expressões. Começo a capturar lá em cima na função <code>enterExpr</code>ession. E aqui eu chamo uma função que vai ser recursiva, justamente porque pode ter expressões dentro de expressões. Falei que ia ser complicado. Não tenta entender linha a linha num primeiro momento, começa tentando entender só o conceito.</p>
<p>Não vou nem tentar ir passo a passo, de novo, fica de lição de casa, mas só pra mostrar rapidinho, essa função <code>recursiveChildren</code> vai tentando interpretar uma expressão de cada vez. Por exemplo, em SQL podemos fazer algo como &ldquo;age between 60 and 2000&rdquo; e em javascript isso tem que virar &ldquo;age &gt;= 60 &amp;&amp; age &lt;= 2000&rdquo; e esse tratamento começa nesse if que checa se esbarrou num &ldquo;between&rdquo; e cria uma estrutura pra isso.</p>
<p>Ou pior, em SQL podemos ter algo como &ldquo;city in (&lsquo;Gotham&rsquo;, &lsquo;Metropolis&rsquo;)&rdquo; e em javascript isso tem que virar &ldquo;[&lsquo;Gotham&rsquo;, &lsquo;Metropolis&rsquo;].includes(city)&rdquo; e de novo olha aqui no código o if que ativa se esbarrar no token &ldquo;IN&rdquo; pra criar uma estrutura de <code>range</code>. Continuando, em SQL o &ldquo;NOT&rdquo; vem antes da expressão, então quando falo &ldquo;NOT age &gt; 40&rdquo; tem que virar &ldquo;age &lt;= 40&rdquo; que é o inverso. Lá em cima eu fiz uma função chamada <code>invertOp</code> pra inverter todos os operadores. Se for igual vira diferente, se for maior, vira menor ou igual e assim por diante.</p>
<p>Enfim, pra conseguir pegar a maioria dos casos mais comuns de select, eu tive um bom trabalhinho. Por isso que agora consigo ir no meu repl e digitar vários tipos de SELECT e ele vai conseguir montar. Quando terminar de transformar todos os nós que preciso da sub-árvore do SELECT, vai cair na função de saída, o <code>exitSelect_stmt</code>. Só deem uma olhada. No final vai ter preenchido estruturas como sqlStruct.orderby, sqlStruct.table, sqlStruct.conditions e assim por diante. Daí é só montar a string com minha sintaxe de javascript do fake sql e retornar.</p>
<p>Eu sei que parece complicado mas realmente não é. Se você já teve que usar jQuery pra manipular nós de HTML do DOM num navegadow, é basicamente o mesmo conceito. Quando a gente tem um HTML como &ldquo;<ul><li>1</li><li>2</li></ul>&rdquo; que é uma lista, o contexto começa com o nó &ldquo;ul&rdquo;, ele vai ter 2 children que são nós &ldquo;li&rdquo;, o primeiro &ldquo;li&rdquo; vai ter valor 1, o segundo &ldquo;li&rdquo; vai ter valor 2. E se tivermos isso aberto no DOM que podemos manipular com javascript, podemos transformar isso em dois parágrafos, por exemplo. E é algo semelhante que fui fazendo nesse Listener, convertendo nós de SQL em string Javascript, pra todos os comandos que eu quis suportar no banco stupid.</p>
<p>Esse projetinho ainda tá incompleto, como falei antes, coisas como inner join, outer join, sub-selects e coisas assim eu não implementei. Fica de exercício pra vocês se quiserem. Mas vamos recapitular: até aqui fiz um banco de dados em memória bem stupid, que são só arrays de objetos. Fiz funções javascript com nomes que se assemelham aos comandos SQL e chamei isso de Fake SQL. E de bônus usei o Antlr4 pra pegar a gramática do Sqlite3, gerar lexer e parser em javascript e preencher o listener com código pra transpilar SQL de volta em javascript que chama minhas funções de Fake SQL.</p>
<p>Mas até aqui só expliquei como que de comandos SQL dá pra executar alguma coisa por baixo. Mas pra ser um banco de dados de verdade, ainda faltam funcionalidades que vou explicar só em conceito. O mais importante é vocês entenderem índices. Todo iniciante esquece que existem índices porque não entendem a importância. Alguns sabem que é pra aumentar a performance, mas como em máquina de desenvolvimento, que você dá insert em poucas linhas na tabela, não faz quase nenhuma diferença, todo mundo esquece de usar. Então deixa eu explicar o problema.</p>
<p>Pra ilustrar criei esse script de benchmark chamado <code>bench_index.mjs</code>. Agora, esquece o parser de SQL, vamos usar só o fake sql. Notem que lá em cima eu carrego uma nova biblioteca chamada <code>fake-sql-index.mjs</code>. Pula por um segundo. A primeira função aqui é um timer genérico, passo um comando e ele grava a hora logo antes, a hora logo depois, subtrai os dois pra saber quantos milissegundos levou pra processar. Depois faço uma rotina de criação de índices. Vou explicar o que isso significa já já, mas na prática estou criando um índice pra cada coluna da nossa tabelinha.</p>
<p>Em seguida crio uma nova função de insert decorada em cima do insert anterior. Ignorem por enquanto, já explico. Agora vem a parte da carga, vou duplicar aquela tabela de 37 usuários 18 vezes. Só que é acumulativo, então da primeira vez, 37 vira 74. Daí duplica de novo e vira 148, daí duplica de novo e vira 296 e faz isso 18 vezes, aumentando a quantidade de objetos exponencialmente até dar quase 10 milhões de objetos em memória.</p>
<p>Agora olhem o primeiro teste do benchmark, é usando minha função <code>from</code> lembra? Que vai fazer um eval chamando a função <code>.filter</code> desse nosso array de quase 10 milhões de objetos e checando um a um se a propriedade <code>city</code> é igual a &ldquo;Wakanda&rdquo;. Ele vai checar isso quase 10 milhões de vezes, objeto a objeto. Porque não tem outra forma nessa versão simples e no nosso teste vai levar algo na faixa de 150 milissegundos, o que é bem rápido porque já tá tudo na memória e fuçar RAM é muito rápido. Se fosse pesquisar num HD seria 100 ou 200 vezes mais devagar que isso, levando vários segundos inteiros pra fazer a mesma pesquisa.</p>
<p>O segundo teste é diferente, ele usa outras funções que ainda não mostrei e que está naquele arquivo &ldquo;fake-sql-index.mjs&rdquo;. Logo no começo chamei a função <code>createIndex</code> dessa biblioteca. Vamos ver o que ela faz. Olha só, já tinha uma lista chamada <code>databases</code> pra representar o banco de dados e agora criei outra estrutura global chamada <code>indexes</code> pra representar índices. Daí crio um índice pra cada chave da tabela. Então a chave pra idade da tabela users seria <code>indexes['users']['age']</code> e cada uma delas vai ter uma instância desse tal de <code>AVLTree</code>.</p>
<p>AVLTree é parecida com uma Red-Black Tree. Expliquei isso no vídeo de Árvores, lembra? Deixa recapitular o conceito rapidinho aqui. Eu tenho aquela lista de usuários com nome, idade e cidade, certo? Se eu quiser achar todo mundo da cidade de Wakanda como faz? Do jeito que tá agora preciso olhar o 1o elemento e vai ser o Peter Parker em Nova Iorque, não é. Pula pro próximo, vai ser Clark Kent, em Metropolis. Só quando chegar na linha 12 que vamos achar o T&rsquo;Challa em Wakanda. Mas eu não sei se é só ele, vamos checando um a um de novo até chegar na linha 25 e achamos a Okoye. Mas ainda não sabemos se acabou, então tem que ir até a última linha 37 que é a Jubilee em Los Angeles e agora sabemos que só tem 2 usuários de Wakanda, mas pra saber isso tivemos que checar 37 vezes. Tem como melhorar isso? Tem.</p>
<p>O jeito mais ingênuo é jogar numa árvore de procura binária. O processo é simples. Primeiro de tudo ordemos a lista de cidades. Vou simular aqui. Depois, não começamos do começo, da linha 0. Começamos do meio. Metade de 36 é 18. E na linha 18 temos New York. Agora, Wakanda é maior ou menor que New York? Por ordem alfabética é maior, então ignoramos tudo abaixo da linha 18 e vamos pra metade entre 18 e 36, que é a linha 27. E 27 continua sendo outro New York. Wakanda ainda é maior, então pegamos o meio entre 27 e 36 que é 31. De novo Wakanta é maior. O meio entre 31 e 36 é 33. E nessa linha temos San Francisco. Ainda Wakanda é maior. Meio de 33 e 36 é 34. Agora é Themyscira. Wakanda ainda é maior. E finalmente, entre 34 e 36 temos 35, onde encontramos o primeiro Wakanda.</p>
<p>Esse foi por acaso, o pior caso, onde procurei Wakanda que fica no final da lista. Mas olhem só, no pior caso anterior tinha que vascular 37 vezes. Mas desse jeito binário de ir jogando metade fora a cada checagem, tivemos que fazer só 6 comparações. Ou seja, 6 vezes menos comparações no pior caso. Se em vez de eu procurar na tabela principal, procurar no índice ordenado de cidade, vou descobrir as duas últimas linhas depois de só 7 comparações e isso me resulta nos ids 12 e 25. Agora pego esses ids na tabela principal e acho o T&rsquo;Challa e a Okoye, totalizando 7 comparações e 2 operações pra pegar os objetos na tabela de usuários. 9 operações, que é quase 4 vezes menos operações.</p>
<p>Vamos ver o benchmark das 10 milhões de linhas. Vasculhando, que em mundo de banco de dados é o que chamamos de fazer um &ldquo;table scan&rdquo; porque estamos escaneando linha a linha da tabela inteira, 10 milhões de vezes, levou na casa de 150 milissegundos. Mas agora buscando no índice, que é o que faz essa função getFromIndex, vai custar na faixa de 10 milissegundos. 15 vezes mais rápido. Mas aí vai levar uns 35 milissegundos pra pegar os ids e buscar os objetos na tabela, que é o que faz essa função <code>getFromIds</code>, um total de uns 40 milissegundos, que continua sendo pelo menos 3 vezes mais rápido do que um table scan. Lembrando que esses tempos só são em millisegundos porque estamos em RAM, se fosse em disco seria muito mais lento.</p>
<p>No caso, por causa do javascript, essa função <code>getFromIds</code> tá prejudicando nosso benchmark. Mas se fosse em C estaríamos falando de uma diferença ainda maior, na casa de tudo 10 vezes mais rápido mais ou menos. Antes que alguém comece a chiar nos comentários, Javascript não é sempre X vezes mais lento que C, mas nesse trecho específico sim. Enfim, uma AVLTree e uma Red-Black Tree, diferente de uma árvore de procura binária simples, são árvores balanceadas. Eu mostro no outro video a diferença de forma animada, mas em resumo, significa que o processo de ordenar e colocar na árvore exige uma operação de rotação pra fazer o balanceamento, então a inserção na árvore é um pouquinho mais lenta do que uma árvore binária simples, mas na hora de procurar vai exigir menos comparações então a pesquisa é mais rápida.</p>
<p>Pra todas as pesquisas serem mais rápidas, o certo é ter índices pra cada expressão que queira procurar. Se quisesse fazer uma pesquisa por idade e cidade ao mesmo tempo, eu deveria ter um índice que contenha esses dois valores na mesma árvore. Se quiser procurar também separado por idade e em outras pesquisas procurar por cidade, daí preciso ter outro indice separado pra cidade e outro pra idade. Mas aí você pensa, porque diabos tenho que ficar criando índices manualmente, se tudo fica mais rápido com índices, o banco já não cria índices pra todas as colunas automaticamente como eu fiz no meu banco estúpido aqui nesse trecho?</p>
<p>Agora é hora de falar daquela função nova de insert decorada. Eu usei um decorator só pra não ter que mexer na função de insert original e só plugar a funcionalidade de índice. Vamos ver o que faz esse <code>decorateInsert</code>. Abrindo a biblioteca &ldquo;fake-sql-index&rdquo;, vemos que ela recebe a função <code>insert</code> antiga e devolve outra função. Dentro ela chama o insert original, mas na sequência insere também os valores nos índices de cada coluna. Ou seja, se estivermos inserindo um novo objeto com os campos name, age, city e id, são 4 novos inserts. O que antes era só 1 operação de insert no array de users, pra atualizar todos os índices, virou um total de 5 inserts. A operação de insert ficou pelo menos 5 vezes mais lenta.</p>
<p>Imagina uma tabela com 50 colunas. Se todas tiverem índices, cada insert vai exigir 51 operações de insert. Um pra linha na tabela principal e 50 inserts pros índices de cada coluna. Então o insert ficaria 50 vezes mais lento. E não é só isso. Concorda que toda vez que fizer update também precisa atualizar todos os índices? E se fizer delete também tem que deletar de todos os índices? Portanto insert, update e delete todos ficam lentos. E é por isso que não se faz índices pra todas as colunas.</p>
<p>Índices é um trade-off de deixar operações de pesquisa muito mais rápidas ao custo de deixar operações de escrita muito mais lentos. E nós programadores é que temos que avaliar, testar e achar formas de criar o mínimo de índices possível pra ter a maioria das pesquisas rápidas sem prejudicar demais as operações de escrita. Da mesma forma, não ter índice nenhum significa que as pesquisas podem ser 10 vezes mais lentas, porque vai cair num table-scan. Na prática, todo select complexo vai ser uma combinação de pesquisa em índices e table-scan pras coisas que não tem índice. E é por isso que existe o comando EXPLAIN na maioria dos bancos de dados pra explicar qual estratégia que o banco vai usar pra executar sua pesquisa.</p>
<p>Se quiser ficar bom em fazer queries que tiram o máximo dos seus índices, estudem como funciona o comando EXPLAIN. Internamente um banco de dados como Postgres pode pegar seu SELECT com condições WHERE complicadas e dividir o trabalho, por exemplo, pra rodar em paralelo em múltiplos núcleos do CPU. Ele vai tentar sempre otimizar a query pra rodar no menor tempo possível, usando a menor quantidade recursos que puder. É bem mais complicado do que eu dar um &ldquo;filter&rdquo; num array, obviamente. Mas já deve dar pra dar uma idéia.</p>
<p>De curiosidade, além dos comandos básicos de SQL como select, insert, ainda podemos programar stored procedures, que são programinhas em SQL, com condicionais como IF, variáveis e tudo mais. No caso de Postgres, a linguagem mais comum é o PL/pgSQL, num Oracle é o PL/SQL, num SQL Server é o T-SQL. Um postgres vai passar o código por um parser mais complicado do que eu mostrei e vai usar LLVM pra fazer o just in time compile desses programinhas, incluindo a fase de transformation passes pra otimizar o código antes de tentar executar. Então existe um JIT até no seu banco de dados hoje em dia. Todo banco de dados tem um interpretador embutido.</p>
<p>Seguindo no nosso banco estúpido ainda temos um probleminha. Notem que estamos usando o console do Repl do banco stupid pra inserir, apagar linhas, mas quando saímos e entramos de novo, os dados que mudamos não estão mais lá porque sempre carregamos do zero, do mesmo arquivo &lsquo;users-database.mjs&rsquo;, mas nunca salvamos em cima desse arquivo. Portanto os dados são fixos. E se a gente quisesse gravar as mudanças em disco pra persistir? Ah, isso é fácil, alguns poderiam dizer. Basta fazer o que todo tutorial de javascript já explicou: transformar a lista de objetos num arquivo JSON e gravar. Certo?</p>
<p>Totalmente errado. E nem meu banco é stupid o suficiente pra fazer isso. Na realidade vou quebrar o problema em dois. Um banco de dados de verdade tem no mínimo dois mecanismos, pra tanto persistir dados, quanto garantir a integridade desses dados, em particular num banco SQL. Se você estudou o básico de SQL já deve ter ouvido falar que bancos relacionais tem a característica de ser ACID, certo? Atomicidade, Consistência, Isolamento e Durabilidade. Se nunca ouviram falar, é obrigatório estudar isso. Hoje vou falar só de um pequeno aspecto de consistência e durabilidade.</p>
<p>Em particular, tudo que um banco responde &ldquo;gravei&rdquo; depois que você mandou inserir, tem que estar garantidamente gravado de verdade quando pesquisar. Todo conjunto de operações que eu mandar numa transação, ou todas executam ou nenhuma executa. Não pode ter operações feitas pela metade. Tudo isso porque uma característica importante é integridade. Tudo que o banco me confirmar eu preciso acreditar que fez realmente tudo certo, sem exceções. Essa é a característica de todo banco de dados relacional. O mesmo não se aplica a bancos NoSQL como Redis ou Dynamo, eles fazem trade-offs diferentes, mas outro dia falo disso.</p>
<p>Um dos mecanismos pra ter algumas dessas garantias é o transaction log ou journal. Um sistema de arquivos modernos como NTFS, ext4 ou APFS também tem esse mecanismo, como já expliquei no video de sistemas de arquivos. Como o nome diz é basicamente um arquivo de log, cuja característica principal é ser um arquivo append-only, ou seja, que nunca muda o que já foi escrito e só dá pra ir apendando, só adicionando na frente. O conceito é que antes de executar uma operação você primeiro grava que operação vai fazer nesse log e depois executa. Caso dê alguma catástrofe, tipo acabar a luz e o computador desligar, da próxima vez que reiniciar ele consegue dar replay nesse log e refazer as operações pra trazer os dados de volta num estado consistente.</p>
<p>Quero simular isso rapidinho pra vocês verem. Pra isso criei um outro módulo chamado &ldquo;fake-sql-transact&rdquo;. Abrindo o código, as duas primeiras funções são pra abrir o arquivo de log em modo append  - pra isso que serve esse “a” aqui -, e a segunda é pra apendar alguma coisa nesse arquivo. Em seguida, tenho decorators pras três operações principais que modificam os dados, um pra insert, outro pra delete e outro pra update. Repetindo, um decorator recebe como argumento o nome da função original e devolve outra função como retorno. Dentro dessa nova função vou salvar o comando no log de transações e só depois executo de fato o insert, delete ou update.</p>
<p>Pra testar dupliquei o script de repl que estava usando até agora e fiz um <code>replTransact.mjs</code> pra ter essa funcionalidade. Olha o que faço. Primeiro importo as funções insert, deleteFrom e updateFrom do módulo de fake-sql com outros nomes. Daí o resto importa igual. Agora chega nesse trecho onde começo abrindo o arquivo de log de transações que chamei de <code>transact.log</code>. A primeira coisa que faço é checar se tem algum coisa nesse arquivo e, se tiver, essa função de replay vai repetir todas as operações lá dentro. Como toda vez eu carrego sempre os mesmos dados do arquivo de <code>users-database</code>, eu sempre perdia tudo que fazia nela. Agora isso vai recuperar o que fiz baseado no log.</p>
<p>Em seguida abro o arquivo em modo append e recrio as funções de insert, deleteFrom e updateFrom com versões decoradas que recebem o objeto do arquivo de log aberto. A partir de agora quando chamar um insert, por exemplo, ele vai salvar a operação no log e depois fazer as alterações no array de objetos. O resto do script é igualzinho, então vamos sair, ir pro terminal, e carregar esse novo repl. A primeira coisa que vou fazer é dar insert em mais dois usuários. Vamos inserir os desconhecidos de Paradis. Insert do Levi Ackerman, 37 anos, depois deixa eu inserir uma parente distante dele, Mikasa Ackerman, 20 anos.</p>
<p>Pronto, agora posso fazer <code>select name from users where users.city = 'Paradis'</code> e estão lá direitinho. Vamos dar Ctrl+C pra sair e abrir o Repl de novo. E olha só, ele executa a função de replay e reinsere os dois. Se eu fizer o mesmo select, eles estão lá direitinho. E com isso eu implementei um tipo estúpido de persistência desses dados. Imagina que isso fosse um servidor de banco normal, como um Posgres, e bem na hora que tá salvando essas linhas em disco, dá uma pane, acaba a energia ou o sistema crasheia. O banco ia ficar num estado inconsistente, mas como antes de tentar gravar na tabela ele salvou as operações num transaction log, no próximo restart, vai conseguir fazer um replay, como o que simulei nessa versão simples.</p>
<p>Todo banco de dados e todo sistema de arquivos moderno tem um transaction log ou o outro nome que se usa pra isso, que é um journal, que é inglês pra diário. Óbvio que diferente da minha versão estúpida, ele não vai dar replay de todo o arquivo de transactions toda vez que restarta, isso seria estúpido. Ele só dá replay nas últimas transações que não concluíram até o fim. E isso leva à última característica de um banco de dados que quero discutir hoje, que é: como se salva esses dados numa tabela de verdade num HD?</p>
<p>Bom, já mostrei que meu banco estúpido sempre carrega os dados de um arquivo como o &ldquo;users-database&rdquo;. E nesse caso estou persistindo os dados em formato texto, em javascript. Acho que todo mundo aqui já salvou dados assim de outra forma, como um arquivo JSON. Bastaria chamar a função <code>JSON.stringify</code> e salvar a string num arquivo, certo? Por que a gente não faz só isso? Toda vez que insere ou modifica uma linha naquele array de usuários, basta chamar o <code>JSON.stringify</code> e salvar tudo no arquivo. Fácil, não?</p>
<p>E isso estaria errado. Vamos entender o raciocínio pra vocês nunca cometerem esse erro. O que faz uma função como <code>JSON.stringify</code>? Ele executa uma operação que chamamos de serialização ou marshalling. Daí quando lemos de um JSON e recriamos o array de objetos em memória, chamamos de desserialização ou unmarshalling. Esse é o tipo de operação que tem literalmente em toda biblioteca de APIs que você já tenha usado, como APIs pra integrar com meios de pagamento, ou APIs pra fazer bots de instagram ou twitter. Toda API te devolve um XML ou um JSON e você converte pra objetos da sua linguagem pra poder manipular os dados. Daí prepara um payload qualquer e serializa de volta em XML ou JSON pra mandar pra API.</p>
<p>Essa operação necessariamente duplica os dados em memória, pra começar. Você já tem o array de objetos em memória, e gera uma versão em JSON, em texto, que também tá em memória. Ambos vão ocupar o dobro ou mais de espaço. Os dados no array estão em binário e toda vez que transforma binário numa representação legível em texto, isso ocupa mais memória. Vamos ver quanto. Eu fiz um scriptzinho besta que usa a biblioteca &ldquo;object-sizeof&rdquo; que instalei via npm. E lá embaixo faço uma versão serializada do array de users usando o <code>JSON.stringify</code>.</p>
<p>Olha só. A string com a conversão em JSON do array tá ocupando 4.3 kilobytes na memória. Já a versão original binária em memória do array de objetos de users ocupa uns 3 kilobytes. Portanto a versão JSON é mais de 40% maior. Imagina se fosse um array gigante, com 1 gigabyte. Significa que o JSON ocuparia algo na ordem de 1.4 gigabytes. 400 Mega a mais por causa da conversão em texto e do formato JSON. Representações em texto de estruturas de dados fazem o trade-off de ocupar mais espaço pra gerar um arquivo que é mais fácil de um ser humano como nós, programadores, conseguirmos manipular num editor de textos. Por isso é um bom formato pra arquivos curtos, que não vão crescer o tempo todo. Mas em termos puramente de performance, trafegar JSON é um desperdício de espaço, banda e processamento.</p>
<p>Exatamente por isso que empresas como o Google preferem usar outro protocolo pra fazer comunicação entre serviços, como o ProtoBuf, que é um protocolo binário de formato de dados. Nenhum game que você joga online trafega JSON pra atualizar a posição do seu personagem no jogo, sempre é um protocolo binário. Mas pra APIs de coisas como mandar mensagens automatizadas no Whatsapp ou Twitter, eles oferecem um protocolo baseado em JSON, porque a maioria das pessoas nunca ia conseguir montar um pacote binário. E também porque costuma ser poucos dados trafegados. Num jogo precisa estar trafegando dezenas ou centenas de pacotes de dados por segundo, por isso todo byte conta. Numa API de mensagens ou pagamentos, você manda poucos pacotes de dados, por isso, mesmo que seja 40% mais pesado, não vai fazer tanta diferença assim pra você.</p>
<p>Da mesma forma, num banco de dados, a gente primeiro não quer perder 40% de espaço gravando num formato ineficiente como JSON ou XML ou qualquer outro protocolo baseado em texto. Mesmo desconsiderando isso, ainda tem outro problema. Uma função como JSON.stringify, converte a estrutura de dados do array inteira de uma só vez pra texto. Depois não tem como inserir ou modificar só uma linha. Como que adiciona um novo elemento no JSON? Não tem como de maneira rápida. E um banco de dados vai o tempo todo aumentando de tamanho e não dá pra ficar convertendo de binário pra texto, tabelas inteiras toda vez. É inviável. Tem que ter um jeito de ir adicionando só um insert ou update de cada vez, sem mexer no que já tava gravado, sem criar outro arquivo e sobrescrever por cima.</p>
<p>Pra gravar em disco queremos gravar em formato binário, algo próximo da tal árvore ordenada que mencionei antes e que simulei com índices usando uma árvore AVL. Você poderia pensar, ok, todo Nó de uma árvore contém a referência ou ponteiro na memória pra outros Nós. Basta gravar o nó em disco e mudar de ponteiro em memória pra endereço no disco, daí consigo navegar pelos nós no disco e consigo inserir novos nós fácil, certo?</p>
<p>Nos videos sobre sistemas de arquivos eu explico como que funciona um sistema de partição, endereçamento em sistemas de arquivos e tudo mais. Recomendo que dêem uma olhada. Mas pros nossos propósitos basta saber que na prática o sistema operacional enxerga um disco como se fosse um array gigante de dados, começando com a posição zero e incrementando pra 1, 2, 3 etc até acabar o espaço de 64-bits, que é endereços pra caramba, terabytes e terabytes. Então beleza, é parecido como memória funciona, que pra uma linguagem é como se fosse um arrayzão gigante que começa em 0 e vai endereçando até acabar a RAM, também com endereços de 64-bits. Então tá fácil, dá pra gravar um binário em RAM pra um binário em disco só convertendo as posições de cada nó quando passa de um arrayzão em memória pro arrayzão em disco.</p>
<p>Mas discos de HD tem uma diferença importante. Vamos pensar a árvore AVL. Deixa eu mostrar uma representação gráfica de como seria a árvore de índice de idades. Vou inserir aqui todas as idades de todo mundo que tá no array de usuários. Ela vai ficar assim. Conseguem enxergar que essa árvore é uma lista ordenada? Começa lendo da esquerda pra direita. Olha só. 22, 25, 27, 30, 30, 30, 30, 32, 33, 33, 35, 35, 35, 35 etc.</p>
<p>E se eu quiser achar todo mundo com idade maior que 40? Começa descendo tudo pra esquerda. 22 é menor que 40, vai pra direita, 27 é menor que 40, sobe, 25 ainda é menor, 30 é menor, sobe, 30 ainda é menor, 33 é menor, sobe 30 é menor, 35 é menor, sobe, 35 é menor. sobe, 40 é menor. Desce tudo até 40, é menor, opa 41. sobe, 42. opa. Daqui pra direita é tudo maior que 40. Então eu tenho no final 10 endereços pras posições na memória onde estão esses nós.</p>
<p>Essas posições em memória não são necessariamente consecutivas. Eu peço um espaço pro gerenciador de memória, tipo malloc, e ele me dá algum endereço que eu não me preocupo. Expliquei isso no episódio de gerenciamento de memória, dêem uma olhada depois. Mas enfim, saiba que toda vez que você inicia uma nova variável na sua linguagem, por baixo a variável é só um nome pra alguma posição na memória, um endereço, que você não sabe. Uma das utilidades de uma linguagem de programação é esconder esses endereços de você pra não ficar complicado. Em Assembly você teria que lidar direto com o endereço. Em disco é a mesma coisa, só que em disco vai ser a localização em coordenadas tridimensionais de qual dos discos, em qual trilha e em qual posição da trilha.</p>
<p>Tão vendo esse CD aqui, um HD é como se fossem vários CDs um em cima do outro. Em cima e embaixo de cada disco desse tem um cabeçote com um leitor, que vai navegar nos diversos cilindros ou trilhas desse disco. Agora imagina que aqueles 10 endereços que retornou pras idades maior que 40 seja cada um num disco diferente, em trilhas diferentes, em posições aleatórias. Concorda que pra ler tudo, o HD vai ter um trabalhão? Especialmente porque os discos não estão parados, eles ficam girando sem parar dentro do HD e o cabeçote tem que ir lendo até bater no endereço que foi pedido, daí ele muda de cilindro, e procura outro endereço. É muito trabalhoso pegar endereços aleatórios num disco, é muito lento. HDs funcionam melhor quando queremos pegar pedaços mais longos de dados de uma só vez numa única passada do cabeçote na trilha.</p>
<p>E essa nossa árvore AVL é super curtinha. Imagina um banco de dados de milhares ou milhões de nós. Seu HD ia fritar pra ficar procurando nós individualmente e aleatoriamente. Mas tem jeito melhor? Tem sim, na verdade tem dois jeitos, as árvores B-Tree e B+Tree que eu cheguei a mencionar no episódio de árvores e agora espero que faça mais sentido pra que eles servem. Deixa eu mostrar como ficam as mesmas idades ordenadas organizadas numa árvore B-tree.</p>
<p>Nem vou inserir todo mundo, só as idades dos 10 primeiros usuários. Olha o formato que essa árvore tá ficando. Entenderam? Cada nó pode ter mais de um valor. Em particular se eu pesquisar só quem tem mais de 40 anos, aí vou ter só esses dois grandes nós aqui na direita. E eles vão ser lidos do disco em no máximo 2 operações, em vez de ter que pesquisar e ler de 10 endereços aleatórios como no exemplo da árvore AVL. A grande sacada é justamente isso de acumular vários valores um atrás do outro num único nó grandão, num bloco ou cluster. Isso porque um HD não lê byte a byte, ele lê em setores ou blocos de uma só vez. Todo HD vai sempre ler pelo menos um bloco de 512 bytes ou até 4 kilobytes de uma só vez e colocar num buffer.</p>
<p>Se não ficou claro, lembra quando se formata um HD novo? Na tela de formatação ele pergunta sobre tamanho da unidade de alocação. Por padrão costuma ser 4 kilobytes que é o tamanho do setor. É a menor unidade que ele lê ou grava de uma só vez. Quanto maior for, melhor pra ler depois, mas desperdiça mais espaço, porque quando precisar gravar menos de 4 kilobytes, o que faltar, não pode reusar mais. Mas enfim, em um único setor do disco cabe nossa estrutura de usuários inteira, que a gente viu que ocupava menos de 4 kilobytes lembra? Então num HD nossa tabelinha de usuários cabe inteiro num único setor e consegue ser lido do HD de uma só vez.</p>
<p>O trabalho pra ler um único byte não compensa pro HD, como o disco tá girando sem parar, na hora que a cabeça começa a ler, ele já lê de uma só vez vários bytes. Ler um byte ou ler 4 kilobytes, é quase o mesmo trabalho. Então se eu organizar a árvore pra já conseguir organizar em clusters, fica mais fácil de ler depois. Na hora que o HD for ler esse valor 41 do exemplo, já vai ler de uma vez o 42, 43 e 47. Como já organizei na árvore pra serem valores consecutivos, ou seja, um atrás do outro sem intervalo, ele vai ler os 4 valores corretos numa única passagem.</p>
<p>Nesse exemplo eu fiz nós que vão ter no máximo uns 7 valores, mas numa árvore B-tree de verdade vai ter dezenas ou centenas de valores um atrás do outro em cada nó. E é assim que o HD vai gravar e vai ler de uma só vez. A diferença de uma árvore B-tree e B+tree é que no B+tree só os últimos nós da árvore que vão conter dados, os nós acima só vão conter metados, os ponteiros pros nós de baixo.</p>
<p>Atualizar a árvore é significativamente mais complicado do que a rotação que já mostrei de uma árvore simples Red Black ou AVL, mas o layout pro HD é muito mais amigável. Recomendo depois procurar num livro de algoritmos, como o do Cormen, os detalhes sobre uma B+Tree. No Cormen inclusive ele explica exatamente sobre essa vantagem e no meu repositório no GitHub desse episódio adicionei outro repositório como sub módulo que encontrei alguém que implementou em C a B+Tree do Cormen pra gravar a árvore em disco, pra quem tiver curiosidade.</p>
<p>E de fato, se eu procurar por &ldquo;btree&rdquo; no repositório do código fonte do banco sqlite3, vamos encontrar várias menções a árvore B-tree que ele usa pra gravar as tabelas, os índices, e tudo mais. E se sair procurando em outros bancos como MySQL, Postgres, até Redis e outros, você vai encontrar uma variação dessa árvore sendo usada.</p>
<p>Em resumo o importante é entender que um bom banco de dados precisa levar em consideração a estratégia e custos de ler e gravar dados em disco, em blocos, vários bytes de uma só vez. Um programador iniciante que só lida com variáveis em memória, costuma pensar só um byte de cada vez, mas é bom saber que dependendo do que tá fazendo, é mais eficiente pegar mais dados do que precisa e descartar o que não precisa. Não é intuitivo mas é justamente por isso que ninguém deve sair fazendo um banco de dados do zero sem ter conhecimento pra isso. Existem vários papers e anos de pesquisa pra cada aspecto de um banco de dados modernos. Lembrem-se que a gente vem fazendo isso faz mais de 60 anos.</p>
<p>A intenção do episódio de hoje não foi incentivar ninguém a escrever o próprio banco, mas sim entender alguns dos principais componentes que um banco de dados de verdade precisa ter. Muita gente tenta racionalizar um banco com variáveis e arrays em memória, e espero que tenha ficado mais claro como é bem mais do que isso. E também porque chamei o episódio onde explico árvores de &ldquo;O Começo de Tudo&rdquo;. Compiladores precisam de árvores. Bancos de dados precisam de árvores. Tudo precisa de árvores, e se você não consegue enxergar listas ordenadas como árvores, está perdendo o entendimento de como tudo funciona em computação.</p>
<p>Aliás, como último exercício de tudo que fiz até agora no meu banco estúpido, faltou mostrar mais uma coisa. Como transformar esse meu console de linha de comando de SQL em um sistema cliente/servidor, como um banco de verdade. E esse nem vou explicar o código, porque é um servidor simples de TCP. Pra quem aprendeu redes na faculdade isso aqui é o primeiro dia de aula. Mas olha só. Num terminal vou executar o servidor com <code>./stupid-server.mjs</code> e pronto, isso vai pendurar o meu servidor na porta 4000 da minha máquina.</p>
<p>Em outro terminal, vou executar <code>./stupid-client.mjs</code> que vai tentar se conectar na porta 4000. E no terminal do servidor, mostra que alguém se conectou. Esse cliente não tem nenhuma lógica do banco, não importa nenhum código do interpretador de SQL nem o array de users, mas eu posso mandar comandos em SQL. Esses comandos vão trafegar pela rede e chegar no servidor. O código do servidor é parecido com o REPL, e no fim devolve o resultado de volta pro cliente.</p>
<p>E pra provar que é um servidor mesmo, posso abrir outro Terminal separado e abrir mais um &ldquo;stupid-client&rdquo;. O servidor mostra que mais alguém se conectou. Posso mandar mais SQL por aqui, e ele vai responder. E esse é o esqueleto básico de como um servidor de banco de dados funciona. Depois estudem o código no repositório do GitHub pra ver como eu fiz isso, mas se entenderam até aqui, essa foi a parte mais fácil, eu literalmente fiz esse cliente e servidor em uns 15 minutos.</p>
<p>Fazer um banco de dados de verdade exige conhecimento bem mais avançado do que isso em ciências da computação e vai exigir centenas, milhares de horas homem de desenvolvimento, testes e ajustes. Essa minha versão estúpida eu justamente chamo de estúpido porque fiz só as coisas mais fáceis, o suficiente pra fazer esse episódio. Eu basicamente peguei o feriado e pensei &ldquo;hum, que tipo de banco SQL será que consigo fazer em uns 2 ou 3 dias de trabalho&rdquo;, e foi isso aqui que mostrei pra vocês. Em particular, o interpretador de SQL, eu nunca tinha usado Antlr4 antes, então foi a parte que gastei mais tempo pra fazer, devo ter gasto pouco mais de 1 dia pra fazer o parser e interpretador. Portanto, tendo conhecimentos básicos de algoritmos e as coisas que já vinha explicando no canal, dá pra fazer esse exercício do zero em uns 3 dias.</p>
<p>Eu me coloquei um limite de tempo porque não dava pra ficar eternamento mexendo no código, uma hora eu precisava gravar este episódio. Mas se alguém se interessar puramente pelo exercício, o próximo passo que não fiz foi justamente mover o array de objetos do &ldquo;users-database&rdquo; pra uma árvore B+tree e gravar em disco, incluindo os índices. Só que aí precisaria também implementar um cache, pra não precisar ir toda hora no disco, e precisaria implementar um sisteminha simples de lock, caso na versão cliente e servidor, vários clientes tentem modificar os dados ao mesmo tempo.</p>
<p>Além disso era bom fazer um limite de conexões. Javascript não tem multi-thread real, então teriamos um limite do que é possível fazer. E nesse caso sintam-se a vontade de reimplementar tudo numa linguagem diferente como Java ou C# ou qualquer outra coisa assim. Só que se fizerem numa linguagem que não é dinâmica, aí vão ter mais trabalho por não terem a trapaça que eu fiz usando &ldquo;eval&rdquo; pra executar as condições de where. Enfim, dá pra ficar brincando bastante em cima disso ainda.</p>
<p>Pra só usar um banco de dados precisa saber em detalhes como ele foi feito? Claro que não. Justamente o que mostrei aqui já é um modelo mínimo suficiente pra ter na cabeça pra não parecer que é uma caixa preta mágica. Usem isso pra ajudar a explicar os principais conceitos técnicos de como um banco de dados funciona pra colegas e amigos iniciantes. Quero saber se isso ajudou a mudar um pouco o que vocês imaginavam que era um banco de dados. Num outro episódio vou falar mais especificamente de bancos de verdade como um Postgres, Redis ou Dynamo. Mas por hoje acho que já ficou comprido o suficiente. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video pros seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>bancos-de-dados</category><category>algoritmos-e-estruturas-de-dados</category><category>javascript</category><category>akitando</category></item><item><title>Examinando o Vazamento do Twitch</title><link>https://akitaonrails.github.io/2021/10/21/examinando-o-vazamento-do-twitch/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/10/21/examinando-o-vazamento-do-twitch/</guid><pubDate>Thu, 21 Oct 2021 11:18:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/IBWdTuc6vDg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;No começo de Outubro de 2021 vazou um &amp;ldquo;arquivão&amp;rdquo; de 120 gigas de coisas do Twitch que incluia MUITO código-fonte. Hoje vou dar minha opinião sobre esse vazamento e dar uma olhada no que tinha nesses códigos.&lt;/p&gt;
&lt;p&gt;Avisando: eu NÃO apoio vazamentos ilegais. Se alguém postar o link pra esses arquivos, será banido.&lt;/p&gt;
&lt;h3&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;/h3&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;01:04 - Avisos Legais&lt;/li&gt;
&lt;li&gt;02:33 - Streamers não entenderam&lt;/li&gt;
&lt;li&gt;03:47 - Twitch + Amazon&lt;/li&gt;
&lt;li&gt;04:10 - O que é o vazamento?&lt;/li&gt;
&lt;li&gt;05:41 - Scriptando a avaliação&lt;/li&gt;
&lt;li&gt;06:33 - Limpando repositórios&lt;/li&gt;
&lt;li&gt;07:03 - Indexando os arquivos&lt;/li&gt;
&lt;li&gt;08:04 - Vendorização e dependências&lt;/li&gt;
&lt;li&gt;13:12 - Limpando vendorização&lt;/li&gt;
&lt;li&gt;15:13 - Depois da Limpeza&lt;/li&gt;
&lt;li&gt;16:14 - Do Justin.tv ao Twitch&lt;/li&gt;
&lt;li&gt;22:04 - Protobufs&lt;/li&gt;
&lt;li&gt;23:50 - Infraestrutura&lt;/li&gt;
&lt;li&gt;24:54 - Projetos ativos hoje&lt;/li&gt;
&lt;li&gt;26:42 - Como startups evoluem&lt;/li&gt;
&lt;li&gt;28:11 - Quando usar Microsserviços?&lt;/li&gt;
&lt;li&gt;30:25 - Ranking de Linguagens&lt;/li&gt;
&lt;li&gt;32:21 - Conclusão&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/IBWdTuc6vDg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No começo de Outubro de 2021 vazou um &ldquo;arquivão&rdquo; de 120 gigas de coisas do Twitch que incluia MUITO código-fonte. Hoje vou dar minha opinião sobre esse vazamento e dar uma olhada no que tinha nesses códigos.</p>
<p>Avisando: eu NÃO apoio vazamentos ilegais. Se alguém postar o link pra esses arquivos, será banido.</p>
<h3>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></h3><ul>
<li>00:00 - Intro</li>
<li>01:04 - Avisos Legais</li>
<li>02:33 - Streamers não entenderam</li>
<li>03:47 - Twitch + Amazon</li>
<li>04:10 - O que é o vazamento?</li>
<li>05:41 - Scriptando a avaliação</li>
<li>06:33 - Limpando repositórios</li>
<li>07:03 - Indexando os arquivos</li>
<li>08:04 - Vendorização e dependências</li>
<li>13:12 - Limpando vendorização</li>
<li>15:13 - Depois da Limpeza</li>
<li>16:14 - Do Justin.tv ao Twitch</li>
<li>22:04 - Protobufs</li>
<li>23:50 - Infraestrutura</li>
<li>24:54 - Projetos ativos hoje</li>
<li>26:42 - Como startups evoluem</li>
<li>28:11 - Quando usar Microsserviços?</li>
<li>30:25 - Ranking de Linguagens</li>
<li>32:21 - Conclusão</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>Eu normalmente não faço videos sobre assuntos da moda porque esse tipo de video tende a envelhecer muito rápido. Mas um episódio que aconteceu alguns dias atrás chamou minha atenção e acho que todo mundo ficou sabendo. O vazamento de 120 gigabytes de dados de ninguém menos que o Twitch.</p>
<p>A primeira semana de Outubro de 2021 vai ficar marcado na história porque no começo dessa semana, alega-se que por problemas de DNS o Facebook e seus outros apps como Instagram e Whatsapp ficaram fora do ar por metade do dia, coincidentemente um dia antes de um vazamento de outra natureza que expôs suas táticas de negócios. Mas esse assunto não me interessa tanto pra discutir no canal, deixo isso pros twiteiros.</p>
<p>E logo na sequência veio o vazamento do Twitch. E eu quero falar sobre esse último episódio porque acho fascinante pra quem nunca trabalhou numa tech startup grande ter uma noção de como é o software por trás. Talvez quebrar algumas concepções erradas que muitos iniciantes ainda tem.</p>
<p>(&hellip;)</p>
<p>Obviamente tenho que começar com um disclaimer gigante aqui: eu não apoio atividades ilegais como vazar dados de qualquer empresa ou pessoa. Eu não admiro nem um pouco quem fez isso, muito pelo contrário, eu condeno como um criminoso comum. Nem mais, nem menos. Se for pego e condenado só vou pensar que não foi mais que merecido.</p>
<p>E não adianta vir nos comentários perguntar como se pega esses arquivos. Se alguém compartilhar links, qualquer um, eu vou banir na mesma hora, então já fiquem avisados. E nem fiquem me enchendo o saco por DM, vou só ignorar. Quem tá interessado, se vira.</p>
<p>Possuir os arquivos é uma zona cinzenta. Dependendo da jurisdição, das circunstâncias, pode não ser bom. Por isso eu não pretendo manter esses arquivos, depois do video vou dar o bom e velho <code>rm -Rf</code>. Usar o código é totalmente fora de cogitação. É propriedade intelectual de terceiros. Você nunca pode usar código que você mesmo fez se foi dentro de um contrato com outra empresa. Se não gosta dos termos, não assine o contrato e nem o pagamento. Se você tem dúvidas com relação à lei, consulte um advogado.</p>
<p>Eu conheço as leis, e pra garantir eu consultei meu advogado pra ter certeza. Se alguém tem interesse comece estudando a Lei de Software, Lei 9.609/1998. Mas a parte que me interessa é que não constitui ofensa aos direitos do titular de programa de computador a citação parcial do código-fonte para fins didáticos, desde que identificados o programa e o titular dos direitos respectivos. E também nada do que vou dizer neste video deve ser interpretado como tentativa de difamação ao Twitch.</p>
<p>Eu brinquei no Twitter sobre o que vocês achariam se alguém hipotético que tem esse código fizesse um code review ao vivo num streaming. Obviamente eu tava brincando. Talvez não seja totalmente ilegal mostrar trechos, mas eu considero anti-ético. Qualquer um que tentou fazer isso e foi banido do Twitch ou outras plataformas, mereceu. Isso é molecagem, coisa de criança de 5 anos mesmo cheirando a leite.</p>
<p>Além disso, eu duvido que qualquer um que ficou biscoitando tentando fazer live entendeu qualquer coisa útil dessa montanha de código. Só quem já trabalhou num ambiente assim e lidou com projetos desse tamanho sabe o que procurar e como. E já antecipo que apesar de não ter tido tempo de ver 100%, não tem nada de mais nesse código. Nenhum grande segredo. Nenhuma mágica especial. É muito parecido com dezenas de outros projetos que eu já vi ao longo dos anos.</p>
<p>Se você é iniciante, este video vai ser mais uma lista de coisas que vocês precisam estudar. Se você já é experiente, o video vai ser mais uma confirmação do que vocês já tinha deduzido. Não é muito difícil de deduzir porque todo mundo sabe que ela foi comprada pela Amazon, portanto tem todo o interesse de usar tudo que a Amazon Web Services, a AWS oferece, e sim, de fato ela usa o máximo possível de tudo do AWS, em particular o Kinesis porque é streaming.</p>
<p>Assim como todo mundo ela também usa o S3 pra servir arquivos, o Redshift que é uma base de dados colunar usada pra datastore de big data, Route53 pro DNS, SNS pra notificações, SQS pra fila, DynamoDB pra dados que precisam acessar rápido, Redis pra cache rápido, Postgres como banco de dados estruturada, CloudWatch pra métricas e por aí vai. Nada de novo até aqui.</p>
<p>Antes de continuar vale a pena eu falar um pouco sobre a natureza desse código e o que de fato tem nesse arquivão. Como já expliquei, isso foi chamado de &ldquo;parte um&rdquo;. Talvez tenha uma parte 2, talvez não. Independent, a gente teria que assumir que o que vazou não foi 100%, porque quem vazou não se identificou e não sabemos que nível de conhecimento e de acessos tinha. Por isso é seguro afirmar que esse vazamento é com certeza incompleto. Mas mesmo assim dá pra tirar alguns insights.</p>
<p>Se você não tá acostumado, pode pensar, &ldquo;caraca, é incompleto e mesmo assim tem 120 gigas de material?&rdquo; Na verdade não. O que vazou foram 159 arquivos zipados que dão 120 giga. Se descompactar tudo dá mais de 180 gigas. E não é mais porque misturado tem muito arquivo binário, como imagens, que não compacta bem. Se fosse só texto, a taxa de compactação seria de quase 10 pra 1, mas aqui é menos de 2 pra 1. Pra mergulhar nisso tudo, precisa de um bom SSD porque vasculhar tudo manualmente é impossível. Estamos falando de milhares de diretórios e quase na ordem de centenas de milhares de arquivos.</p>
<p>Sinceramente, eu não tenho paciência pra ficar olhando um a um cada diretório. Um time inteiro precisaria de meses pra auditar linha a linha na mão. Eu me limitei a não mais uns 3 dias nisso. Então eu tive que usar a estratégia de jack, o estripador. Ir por partes. Abrir tudo isso de uma só vez num Visual Studio Code da vida e tentar procurar palavras é uma estupidez. Cada procura demora minutos ou mais. De novo, são literalmente milhões de arquivos. Mais exatamente 3.5 milhões de arquivos.</p>
<p>Em vez disso eu fiz um script que usa a ferramenta <code>find</code> que tem em qualquer Linux e montei um banco de dados em SQLite3. Só pra guardar coisas básicas: o caminho completo ou path, a extensão do arquivo, quantos bytes ele tem de tamanho, coisas assim. Meu script ingênuo levou mais de 30 horas pra indexar tudo e gerou um SQlite de quase 2 gigabytes. Mas com isso agora eu posso fazer queries SQL que responsem em milissegundos pra achar o que eu quero.</p>
<p>Um script pra isso pode ser feito no que você quiser, em Bash mesmo do seu shell, em Python, no caso eu fiz rapidinho em Ruby. Pra fazer esse tipo de coisa é ótimo usar linguagens como Python ou Ruby. Inclusive, uma boa parte do Python que tem nesse código vazado é exatamente isso: scripts pra automatizar tarefas mundanas como limpar arquivos, conectar em serviços de logs ou analytics pra limpar os dados e mandar pra um banco de dados ou outro armazenamento como Spark.</p>
<p>Na verdade antes de indexar tudo eu fiz uma primeira limpeza. Eu notei que muitos projetos ainda tinham o diretório <code>.git</code>, que se você assistiu meus videos sobre Git, sabe que é um basicamente um banco de dados de commits que contém o histórico das mudanças desse projeto. E esse diretório ocupa bastante espaço. Então eu fiz primeiro um script que move todos os <code>.git</code> pra outro lugar. E como eu esperava, isso removeu mais de 60 gigas. Então em vez de 180 gigas descompactado, na verdade o que temos pra trabalhar são 126 gigas.</p>
<p>Uma vez feito isso, daí eu rodei o script pra indexar num sqlite3 e isso que totalizou os 3.5 milhões de arquivos, já removido todos os arquivos dos diretórios <code>.git</code>, mas tem ainda um monte de coisa que não é código fonte. Inclusive eu tenho uma suspeita que seja lá quem foi o perpetrador que saiu copiando as coisas, eu acho que é ou ex-funcionário ou ele entrou nas máquinas de desenvolvimento de funcionários, porque não são clones de git limpos, eles tem um monte de arquivos temporários, como builds de android, builds de go, coisas de javascript depois que rodou <code>npm install</code> porque tem coisas como diretórios <code>node_modules</code>, diretórios de dependências, um monte de binários que sobra depois de baixar bibliotecas open source e compilar.</p>
<p>Aliás, além de bibliotecas de dependências baixadas, também tinha um monte de bibliotecas open source vendorizadas manualmente. Isso é uma má prática e você deve evitar ao máximo fazer isso. Aqui vale uma tangente. Se você nunca ouviu falar de vendorização, ouça o tio Akita: não vendorize coisas de terceiros no seu projeto. E o que é vendorizar?</p>
<p>Digamos que você esteja fazendo um script que vai encriptar arquivos no seu sistema. Pelo menos você não é um burro completo e não vai tentar criar seu próprio algoritmo caseiro de criptografia, o que seria uma estupidez sem tamanho se você não for estritamente um pesquisador renomado e com anos de experiência na área. Se não entendeu isso, assista meus episódios sobre criptografia, mas em resumo, 100% das vezes que um programador comum tenta criar criptografia do zero, é 100% de certeza que é um lixo cheio de buracos de segurança.</p>
<p>Daí você resolve baixar o código fonte do OpenSSL ou alternativas como o BoringSSL, que são bibliotecas reconhecidas, com diversas funcionalidades de criptografia, geração de par de chaves e tudo mais. Daí você evitou uma catástrofe de usar uma criptografia caseira escolhendo uma das bibliotecas mais usadas do mundo, portanto ordens de grandeza mais seguro do que o que você faria sozinho. Mas acabou criando outra catástrofe esperando pra acontecer: você copiou o código do OpenSSL dentro do seu projeto e embutiu ele no seu sistema. Essa é um péssima decisão.</p>
<p>Digamos que a versão do OpenSSL que você embutiu foi a 1.0. Ah, que mau pode fazer? Pelo menos assim eu sei que tudo funciona, eu não dependo de ter que baixar a biblioteca quando for buildar nem quando instalar num servidor, facilitou minha vida. Errado. Vira e mexe descobrem bugs de segurança, agora mesmo em agosto foi descoberto mais um, que foi corrigido nas versões 1.1.1 e 1.0.2.</p>
<p>Só que você não é um especialista em segurança, você terminou aquele código e já esqueceu dele. E agora sobrou um software em produção rodando com OpenSSL 1.0 com buracos de segurança que todo mundo já conhece. A versão mais recente já é a 3.0. E como você é esperto pra caramba, saiu embutindo o código do openssl em todas as dezenas de projetos que precisava de alguma coisa de criptografia. Agora tem dezenas de projetos, cada um rodando uma versão diferente de openssl e nenhum sendo atualizado.</p>
<p>Isso é pior ainda quando você usa uma biblioteca &ldquo;wrapper&rdquo; como a cryptography de Python que por baixo dos panos é só uma casca pro OpenSSL nativo. E você nem sabia disso de openssl, só vendorizou a biblioteca cryptography dentro do projeto e não pensou duas vezes que podia vir uma bomba relógio junto. Projetos mal feitos de Python vira e mexe tem esse problema. Em vez de usar um gerenciador de pacotes como um PIP da vida, tem gente que ainda baixa e deixa estático no projeto.</p>
<p>E de fato, eu achei dezenas de lugares com a biblioteca cryptography, openssl, além de várias outras como url3lib, setuptools e mais. Aliás, em vários lugares eu achei as instalações inteiras de python2.7, python3.6 e 3.7 mas eu acho que nesse caso foi diretórios de build de pacotes de linux ou algo assim. Acho que não tava embutido nos projetos, e por isso eu disse que o vazamento foi não direto dos servidores de repositórios, mas sim da máquina de desenvolvimento de algum programador.</p>
<p>Enfim, a tangente foi mais pra ilustrar que a gente usa um troço chamado gerenciador de dependências por uma razão: não desperdiçar espaço baixando bibliotecas externas e também pra termos controle pra atualizar essas bibliotecas no futuro. Fazendo direitinho, ou seja, declarando a biblioteca e qual versão seu projeto precisa em arquivos como <code>package.json</code>, num projeto de Node.js, num <code>Gemfile</code> num projeto de Ruby, num <code>requirements.txt</code> num projeto de Python e assim por diante, depois temos como criar ferramentas de auditoria que conseguem ver se algum projeto tá usando uma versão muito antiga de alguma coisa que pode ser um problema de segurança. A ferramenta dependabot do GitHub faz exatamente isso.</p>
<p>Algumas comunidades como Go e Python tem problemas históricos de gerenciar versões de bibliotecas. Em python nem todo mundo usa coisas como PIP, muitos dependem de pacotes da distribuição de Linux que usam, o que pode gerar várias dores de cabeça se usar em distros diferentes. O Go antigamente era uma porcaria pra gerenciar versões, só a partir da 1.11 que isso melhorou um pouco, porque antes você declarava a biblioteca e ele baixava e vendorizada direto do github, mas não marcava a versão, então era um saco de gerenciar dependências. Esse foi um erro grave que os autores cometeram nas primeiras versões e isso gerou diversas ferramentas de terceiros com jeitos diferentes de resolver esse problema e muita falta de consenso por muito tempo.</p>
<p>Falando em falta de consenso, Javascript até poucos anos atrás também era uma dor de cabeça. Quem lembra de coisas como Bower? Os eternos bikeshedding sobre formato de pacotes? A lentidão na evolução do NPM antigo, tanto que o Facebook criou a própria dela, o Yarn, e isso forçou o NPM a andar mais rápido. E uma das coisas que mais me deixa irritado é desenvolvedor que não coloca o diretório <code>node_modules</code> no arquivo <code>.gitignore</code> como deveria e aí quando baixo o projeto vem uma tonelada de arquivos inúteis. Pessoal, bota a mão na consciência, não commita <code>node_modules</code>, é pra isso que existe o <code>npm</code> ou <code>yarn</code>. Aprende a usar.</p>
<p>Isso tudo foi só pra argumentar porque eu disse que esse código vazado talvez seja da máquina de desenvolvimento de alguém, porque tá cheio de diretórios de coisas vendorizadas, ou seja, depois que alguém rodou um <code>npm install</code> da vida e baixou as bibliotecas. Portanto, eu tinha que primeiro sair apagando essas bibliotecas do caminho. E tinha um monte de coisas.</p>
<p>No começo eu fiz um script que vasculhava os arquivos de LICENSE. Daí se dentro tinha palavras como &ldquo;Apache License&rdquo; ou &ldquo;BSD License&rdquo; ou &ldquo;GNU&rdquo; ou coisas assim, eu sabia que eram bibliotecas open source. O problema é que isso você acha no repositório oficial num GitHub da vida, mas depois que eles empacotam pra distribuir num servidor de NPM ou Maven da vida, o script de build costuma excluir arquivos que não são essenciais, incluindo os de licença, então a maioria das coisas vendorizadas não tinha esse arquivo. Então isso dificultou um pouco na minha limpeza.</p>
<p>Daí não teve muito jeito, eu fui listando arquivos de terminadas extensões como .C ou .CPP que é C++ e isso me levou a bibliotecas fáceis de reconhecer como ffmeg, libxml, zlib, tcmalloc, libpng, boringssl e muito mais. Se você mexe como desenvolvimento faz tempo, tem centenas de nomes assim que imediatamente você sabe que é open source. Tudo isso eu apaguei do meu banco de dados. No final meu script ficou com uma lista de padrões que eu ia pesquisando no caminho completo de cada arquivo e marcando pra apagar. Olha só como ficou.</p>
<p>Pra achar esses padrões, foram algumas horas, só vasculhando, fazendo queries no meu índice, e apagando tudo que eu achei que fosse open source, coisas vendorizadas, artefatos temporários de builds automatizadas. E isso faz diferença, por exemplo, tinha código fonte dos apps de iOS, Xbox, Android e dentro tinha diretórios como <code>android-dev-tools</code>, diretório dos SDKs, e quem já desenvolveu Android sabe que só um diretório de SDK ocupa por volta de 1 giga de espaço.</p>
<p>Eu acho que limpei pelo menos a grande maioria, mas muita coisa ainda pode ter sobrado. Pode ser que eu tenha apagado uma coisa ou outra que não era open source por acidente, mas isso deve ser suficiente pra dar uma idéia geral. Recapitulando, o total do vazamento foram 120 gigas compactados que dão uns 180 descompactados. Daí eu ignorei diretórios como <code>.git</code> e depois de uma primeira limpeza de bibliotecas open source, cheguei a um total de mais de 3.5 milhões de arquivos que somam uns 110 gigabytes.</p>
<p>Daí limpando o que eu achei que era open source, builds, coisas como backups de banco de dados, imagens, e tudo mais, no final sobraram ainda mais de 600 mil arquivos totalizando uns 11 gigabytes. Ou seja, deu pra jogar quase 90% fora pra conseguir separar o que é realmente proprietário do que era open source e coisas temporárias. Como já disse, a limpeza manual que eu fiz não foi 100% preciso, mas acho que cheguei próximo o suficiente pra conseguir tirar as informações que eu queria.</p>
<p>Se você não entendeu isso de sqlite3 ainda, é uma tabela com o caminho completo de cada arquivo, diretórios, subdiretórios, nome do arquivo e extensão, além do tamanho de cada arquivo. fazendo isso eu consigo fazer, por exemplo, <code>select count(*), sum(size) from files where extname = '.go'</code> ou em português, pesquise na tabela <code>files</code> a quantidade de arquivos e o tamanho total deles em bytes onde os arquivos tem extensão <code>.go</code>. E com isso eu consigo saber quantos arquivos de Go existem e quanto bytes ocupam. E posso fazer isso pra todas as linguagens que achei.</p>
<p>Sim, linguagens no plural. Se você é iniciante, isso pode ser um choque. Sim: toda grande tech startup usa mais de uma linguagem. Sem nem ver o código fonte, não é difícil de imaginar quais linguagens se usa e por quais razões. Por exemplo, eu já sabia que o Twitch era originalmente feito em Ruby on Rails, quando ela ainda se chamava Justin.TV. Aliás, no código hoje ainda tem muitos pacotes com namespace justin.tv.</p>
<p>À medida que a plataforma começa a dar certo você começa a expandir pra outras coisas. Por exemplo, o Twitch consegue exportar videos pro YouTube. Pra fazer isso você usa ferramentas open source como <code>ffmpeg</code>, o software mais importante de encoding e decoding de video, que todo mundo usa e, adivinha, o Twitch também usa. Pra processar áudio de voz, tem a biblioteca <code>speex</code> e assim vai, tem dezenas de ferramentas pré-prontas e não só o Twitch mas qualquer um vai usar todas elas. E depois de um certo tamanho você quer entender como esse código funciona, e eles são feitos em C++.</p>
<p>Se sua startup sobrevive e cresce, agora você tem milhões de requisições, precisa subir dezenas de servidores, e vai precisar otimizar performance. Uma das primeiras coisa que a gente faz em linguagens como Ruby e Python é se preocupar com o gerenciador de memória, que deriva de C, o malloc padrão. Em vez dele a gente tenta outros gerenciadores como jcmalloc do Facebook ou tcmalloc do Google. Eu falei disso no episódio de gerenciamento de memória. E de fato, vi rastros de tcmalloc no projeto.</p>
<p>Pra diminuir o tempo de cada requisição web, tem duas coisas que a gente faz logo de cara. Primeiro joga tudo que tá gerando gargalo em jobs em background. No mundo Rails a gente usa bibliotecas como Sidekiq ou Resque. Eles mantém uma fila separada num banco como Redis, ou mesmo no Postgres - o que eu não recomendo, a não ser que conecte numa instância de banco separada da instância principal, ou conecta num serviço de filas como o SQS da Amazon. Daí a requisição web só tem o trabalho de gravar na fila do banco e ir embora muito rápido, e deixar o trabalho mais demorado pra um worker em background, que vai pegar o job da fila e processar depois. Isso é padrão, todo mundo faz isso. Eu falei um pouco disso no episódio sobre Concorrência e Paralelismo.</p>
<p>A segunda coisa é separar queries demoradas, pesquisas que custam mais caro que estão sendo feitas no seu banco de dados tradicional como Postgres e jogar os dados mais acessados, já pré-calculados e consolidados, num banco de dados mais rápido NoSQL como Redis ou DynamoDB. E adivinhem, é o que o Twitch está fazendo. Eu falei mais disso no episódio sobre NoSQL. E não, jogar tudo num NoSQL não é a solução. Alguns bancos são otimizados pra escrever rápido, mas as pesquisas são lentas. Como um Cassandra. Outros são otimizados pra leituras rápidas, mas a escrita é lenta, como um MongoDB. Outros ainda são otimizados pra não perder dados, mas a leitura e escrita podem ser lentos, como um Postgres. Cada caso precisa de um banco diferente ou uma combinação deles, o que é mais comum.</p>
<p>Mas só isso não é o suficiente quando você chega num certo patamar de audiência, milhões de pessoas penduradas o dia inteiro assistindo ASMR e moleques jogando Fortnite. Apesar de ser altamente produtivo, uma plataforma dessas não tem como ser 100% em Rails, e agora você começa a gerar microserviços em outra coisa, como Go. Mas nesse estágio, agora você tem tamanho, experiência e dinheiro pra começar a criar ferramentas proprietárias pra lidar com coisas como WebSockets e WebRTC pra conseguir manter seus chats no ar.</p>
<p>Mas não é só isso. Com Rails você pode continuar fazendo coisas como painéis de administração, suporte, configuração e funcionalidades do site que vão ser estáticas e podem ser servidas por cache. Nesse estágio você começa a ter volume grande de dados, big data, e precisa lidar com isso. Então começa a criar ferramentas pra consumir esses dados. Logs pra auditoria, logs pra ads, logs pra analytics, e precisa tratar, consolidar e processar esses dados em coisas como Redshift, Spark, monitorar a infra com coisas como Ganglia, notificações de Rollbar, criar dashboards que mostram esses dados com coisas como Grafana. Novamente, tudo que todo mundo nesse tamanho usa, e o Twitch não é diferente.</p>
<p>À medida que você vai adicionando mais peças, mais serviços externos, mais microserviços internos, vai aparecer um volume grande de um tipo comum de código: wrappers, ou clientes de APIs. Uma boa parte do código dos projetos é só um codigozinho que chama uma API e trata o resultado de alguma forma. E sendo o Twitch parte da Amazon, não é surpresa que a maior parte é código pra integrar com Cloud Watch, com o SNS, SQS, enfim tudo que a AWS oferece. E de fato, muito do que eu falei que precisei apagar pra fazer a contagem eram diretórios como <code>aws-sdk</code>.</p>
<p>E ao longo do tempo da startup, vão surgindo campanhas temporárias. Uma campanha patrocinada, colaboração com marcas, promoções e tudo mais. Código que só vai servir por alguns meses e depois sai do ar. E de fato, tem vários projetos desse tipo que claramente estão arquivados e não são mais usados. Esbarrei em alguns wordpress, com um tanto de plugins. Mas não sei se era código ativo. PHP é uma das coisas menos usadas no Twitch. Depois da limpeza só achei uns 2 mil arquivos que não dava 11 megabytes, do total de 11 gigas. E felizmente muitos deles foram feitos como projetos em repositórios separados dos principais, o que ajuda a não poluir demais o código do dia a dia.</p>
<p>Pra que seja possível separar as coisas em micro projetos diferentes, uma outra parte do código vai ser pra expôr seus serviços internos como APIs, tanto APIs HTTP, web services, que normalmente respondem JSON da vida e aí fica mais fácil criar sites separados de campanhas. Dá pra facilmente integrar no front-end um sistema de autenticação, puxar dados do perfil do usuário e tudo mais. Isso é bem comum também e aqui não é diferente. Mas o que é diferente é um cuidado que pouca gente toma e só faz sentido em sistemas grandes: a comunicação entre micro serviços.</p>
<p>Só quando a startup vai chegando mais perto do tamanho de um Twitch, cada milissegundo, cada kilobyte conta, porque são milhões de operações por segundo na plataforma inteira. Sistemas menores não devem se preocupar com isso, mas neste caso eles usam muito Protobuf. Em resumo, um microserviço é basicamente uma aplicação web pequena que expõe APIs e se integra com outros micro serviços chamando as APIs deles. Sendo uma aplicação web, o protocolo de comunicação é HTTP e o pacote de resposta costuma ser JSON ou XML.</p>
<p>JSON é ótimo pra um ser humano entender, por isso toda grande plataforma expõe APIs que respondem JSON ou similar pra facilitar pro pequeno desenvolvedor integrar sem muito trabalho. Mas é um tradeoff porque protocolos em texto desperdiçam muito espaço. Internamente na plataforma, o protocolo precisa ser rápido, mas sem perder totalmente as características de ser conveniente de implementar e integrar. E por isso o Google inventou o Protobuf, que é uma forma fácil de criar protocolos binários de RPC, ou Remote Procedure Call.</p>
<p>Uma estrutura de dados convertida em texto desperdiça muito espaço, mesmo se compactar com gzip, o que adiciona tempo de processamento pra ficar compactando e descompactando. Um protocolo binário é muito mais eficiente porque você não fica convertendo números em string, daí ter que reconverter de volta em número. Em binário ainda dá pra montar vetores de bits e muito mais. Existe um padrão chamado gRPC que muita gente usa com Protobuf, mas o Twitch inventou o dele, que é open source e se chama Twirp. Vale a pena dar uma estudada, mas o ponto é que muita coisa usa esse protocolo binário internamente em vez de APIs HTTP. Eu sei disso porque na limpeza esbarrei com várias cópias da biblioteca de protobuf e twirp vendorizadas.</p>
<p>Como tem muito microserviço, começa a ficar complicado pro desenvolvedor rodar e testar cada um. Por isso normalmente todo projetinho desses acompanha instruções num arquivo README da vida, o que é uma boa prática e se você não faz, deveria se acostumar a fazer. E também costuma vir junto pelo menos um arquivo <code>Dockerfile</code> pra facilitar subir um container local pra rodar. Também é boa prática escrever arquivos como INSTALL ou BUILD ou até mesmo um <code>Makefile</code> de uma vez pra automatizar o build dos pacotes. Achei muito disso em vários dos diretórios que vasculhei.</p>
<p>Feito tudo isso, o problema agora é subir esse tanto de coisa em produção. E aqui eu fiquei um pouco na dúvida. Não achei absolutamente nada de Kubernetes. Sendo na Amazon o normal seria ter configurações de Terraform pra subir tudo no EKS que é o serviço de Kubernetes da Amazon. Mas nada. O que eu achei foi um monte de arquivos de configuração de Terraform (main, variables, output), mas parece que o provider é conectando direto nos serviços da AWS pra configurar coisas como RDS, EC2, Beanstalk e tudo mais.</p>
<p>Pode ser que eu não tenha olhado direito, ou faltou isso no vazamento, talvez uma das coisas que devem sair na parte 2. Eu cheguei a cogitar que talvez o vazamento saiu agora mas ele pegou as coisas faz tempo e só resolveu vazar muito tempo depois, mas não. Eu fiz outro script pra vascular todos os repositórios Git, os diretórios <code>.git</code> locais. Nem todo diretório tinha um <code>.git</code> mas rodando meu script eu encontrei quase 5 mil e 700 repositórios. Daí fiz um script pra rodar <code>git log</code> em cada um e conseguir a data do último commit. De novo, fiz meu script jogar tudo num outro SQLite3.</p>
<p>Quando meu script terminou, eu fiz um select agrupando o ano da data desses últimos commits e de fato, quase 2000 deles tem commits recentes de 2021, os mais recentes são de 4 de outubro deste ano, poucos dias antes do vazamento. Então são repositórios ativos neste momento mesmo. Tem repositórios que não tem commits novos faz tempo, mas mesmo assim quase 890 tem commits em 2020, 844 em 2019 e assim por diante. Mas mais da metade dos repositórios tem commits entre 2021 e 2020, ou seja, recentes. Aliás, fazendo essa query eu consigo saber quais projetos estão ativos neste momento e quais são projetos antigos que não se usa mais.</p>
<p>De qualquer forma, parece que pode ser só Terraform direto ligando na AWS sem usar Kubernetes no meio, controlando EC2 na mão mesmo. E não, eu não gosto de Kubernetes, na verdade acho horrível pra qualquer um que não seja do tamanho de um Twitch pra cima. Eu sempre penso que o Google inventou Kubernetes pra resolver o problema de datacenters do tamanho dos deles. Projetos pequenos e mesmo médios deveriam ficar longe de Kubernetes. A grande maioria dos projetos devem ficar num Heroku da vida, e já tá mais que ótimo. A maioria das pessoas inexperientes ignora os custos elevados de se gerenciar e manter uma infra toda em Kubernetes.</p>
<p>O que me leva ao ponto que falei lá no começo, uma grande tech startup, até mesmo média, vai usar mais de uma linguagem: a linguagem certa pra coisa certa. Normalmente vai ser ou Rails ou Node.js ou Django ou Laravel pra aplicações mais visuais, que usuários interagem. Isso porque no estágio inicial de uma startup, frameworks desse tipo são super produtivos, tem um ecossistema rico com soluções pré-prontas pra você não ficar reinventando a roda, e muita documentação e guias pra orientar o desenvolvimento. Ninguém deveria estar fazendo do zero autenticação, login, esqueci minha senha do zero, isso é pré-pronto em todo framework decente.</p>
<p>Depois disso começa a entrar coisas como Go ou Elixir, pra pedaços que precisam obrigatoriamente de baixíssima latência ou suportar milhares de pessoas penduradas em conexões de longa duração como WebSockets ou WebRTC, como é o caso de um Twitch, Discord, Messengers e coisas assim.</p>
<p>O front-end obviamente é sempre em Javascript. No caso como o Twitch foi feito em Rails lá atrás, ainda tem muita coisa usando o framework Ember. Não só isso, eu achei um monte de jquery vendorizado. Como nos últimos 15 anos o mundo javascript foi inventando um monte de bibliotecas e frameworks, qualquer startup com 10 anos ou mais vai ter uma torre de babel misturando jquery, ember, angular, react e tudo mais. Não fiquei fuçando muito a parte de Javascript, mas parece que é muito diferente disso. É difícil de ter uma contagem do que é javascript que eles fizeram e o que é coisa baixada misturada.</p>
<p>Muita gente tem dúvidas sobre organização de microserviços, quando deveria fazer, como deveria organizar. E isso não tem resposta exata. Micro serviços aparecem em duas ocasiões principais: quando você tem um endpoint específico que precisa de características muito especiais de baixa latência e alto throughput. E o segundo caso é quando você precisa dividir suas equipes internas pra facilitar o gerenciamento, daí divide o sistema em microserviços, um pra cada equipe, que é o caso clássico da Lei de Conway, que observou que com o tempo, a estrutura de um software tende a replicar a estrutura de comunicação da empresa.</p>
<p>Com algumas ressalvas, eu diria que quanto menos micro serviços você tem sempre é melhor. Muita gente gosta de ficar falando mau de monolitos, mas existem monolitos e monolitos. Outra tech startup gigante feita em Rails e que faz questão de se orgulhar de ser um monolito é o Shopify. Claro que também tem micro serviços, mas eles querem dizer que não saem dividindo o software em milhões de serviços à toa. A vantagem de um monolito é que a complexidade da infra tende a ser mais baixa, mas a complexidade do software pode dificultar a produtividade. O problema é quando o código é sujo, macarrônico, onde passou um monte de programador porco que largou um monte de dívida técnica, com quase nenhum teste automatizado, nenhum build automatizado, ou seja, um inferno de dar manutenção.</p>
<p>Um monolito bem cuidado pode durar muito tempo e costuma ser sinal de uma empresa que tem boa comunicação entre suas equipes e mantém boas práticas de código limpo. Eu não sei quantos programadores existem no Twitch hoje, mas eles tem muito mais microserviços do que eu gostaria de ver. Talvez tenham sido mais uma vítima da Lei de Conway. Mas sendo justo, uma parte do código que eu vi tem testes automatizados. E pelo menos a parte de infra parece decente, com scripts que automatizam muita coisa.</p>
<p>Falando nisso, só de bater o olho aleatoriamente, eu não vi código muito sujo além do normal de qualquer projeto. Nada excessivamente macarrônico, funções com dezenas de linhas, nomes de variáveis difíceis de identificar, números mágicos, complexidade ciclomática e coisas assim. Inclusive eles tem um guia de boas práticas de como escrever o código, o que costuma ser uma boa coisa pra ser criar internamente, pra todo mundo saber o que é esperado do código.</p>
<p>Enfim, vamos fazer um pequeno ranking só de brincadeira. Como eu disse antes, eu limpei o código o máximo que dava. Joguei fora tudo que eu achei que era open source. Tudo que claramente era código duplicado entre projetos e fazia muito volume e no final achei o seguinte. Dos 11 giga que sobrou, tem quase 58 mil arquivos de Go que totalizam uns 300 megabytes.</p>
<p>O segundo colocado, que não é surpresa pelo tanto de front-end que uma plataforma dessas costuma ter é javascript. Mas como eu disse antes, eu acho que ainda tem muita coisa vendorizada que eu não consegui limpar. São mais de 18 mil arquivos que totalizam 68 megas. Tecnicamente HTML seria o segundo lugar com quase 21 mil arquivos ocupando 450 megas, mas tem centenas de diretórios de documentação de bibliotecas com HTML no meio e coisas assim, então esse número tá bem inflado ainda.</p>
<p>Achei curioso que tem bastante arquivo de C#, e aqui eu fiquei ainda em dúvida se eu não limpei bibliotecas externas o suficiente mas a razão de ter muito C# é porque o Twitch tem apps nativos pra tudo que é plataforma como Windows e XBox. Tinha até o executável do NuGet espalhado em vários diretórios, muitas DLLs que eu imagino que seja do SDK e outras dos builds automatizados, mas no final da limpeza sobrou mais de 9 mil arquivos de C# ocupando uns 51 megas.</p>
<p>Eu falei que eles usam muito Terraform? Só de arquivos .tf de configuração tem quase 22 mil arquivos que consomem 37 megabytes. Arquivos de configuração passaram na frente de arquivos de Ruby que tem só uns 16 mil arquivos ocupando 32 megas e depois dele vem Python com pouco mais de 17 mil arquivos ocupando 81 megas. Python era uma das coisas que mais tinha biblioteca estática baixada e descompactada no meio dos projetos. De novo, eu limpei o que deu, mas provavelmente uma boa parte disso é sujeira que eu não achei tudo. Ruby é de fato mais aplicações Rails mesmo. Mas Python a grande maioria são scripts, incluindo funções pra AWS Lambda pra ficar processando dados.</p>
<p>A conclusão é que foi uma pequena startup que começou com Rails, fez muito sucesso e foi adicionando scripts em Python, microserviços e ferramentas em Go, migrando de JQuery pra Ember pra React. E pra deploys começaram  com Elastic Beanstalk e Cloud Formation e foram migrando pra Terraform, integrando com tudo que tem na AWS, evitando reinventar a roda. Em resumo é o que posso dizer sem correr o risco de dar spoilers de confidencialidade do código fonte.</p>
<p>Pra você que é iniciante e achou esse volume de código grande, isso não é grande. Lembrando que pela data dos commits dos projetos, menos da metade tá realmente ativo hoje e muita coisa é projeto que foi ficando obsoleto e abandonado pelo caminho. Metade não recebem novos commits desde 2019. E isso é outra coisa normal: muito código vai ficando obsoleto, sendo substituído por projetos novos. Lembram como eu falei em videos anteriores que um software nunca tá 100% acabado? É isso que eu quero dizer. 2 repositórios viram 10, 10 viram 100, 100 viram 1000 e depois de 5 a 10 anos você acaba com 5 mil repositórios ocupando 100 gigas, sem contar os bancos de dados e tudo mais.</p>
<p>Eu não tenho muito a criticar no que eu vi. A única coisa foi que eles precisam melhorar o gerenciamento de pacotes e dependências. Mesmo tendo muita coisa automatizada, ainda deu pra achar muita coisa realmente estática. Isso são bombas relógio que se ninguém fizer auditoria uma hora vai estourar buracos de segurança e bugs inesperados por conta de bibliotecas estáticas velhas que ninguém sabia que ainda existiam. Vai saber se foi por conta de uma dessas coisas que deu o vazamento de agora.</p>
<p>A idéia do video de hoje não era expor o Twitch. Centenas de engenheiros trabalharam sério nisso por 10 anos. Esse trabalho merece respeito, são nossos colegas da nossa área. Sair expondo o trabalho dos outros não é profissional. Meu objetivo foi totalmente educacional pra dar uma noção de dimensão pra quem nunca viu. Mesmo porque o que todo mundo teve acesso nesse vazamento é no mínimo incompleto.</p>
<p>Eu espero que vocês tenham tido alguns insights com esse caso e que sirva pra dar perspectiva nos projetos que estão trabalhando hoje. Ninguém tem que tentar fazer nada igual a um Twitch da vida, nem uma fração de 1% dos projetos que existem hoje vai chegar a ter o mesmo nível de audiência massiva que eles tem. Cada software deve ser feito pra atender as necessidades de hoje e ir evoluindo de acordo com seu sucesso. Se ficaram com dúvidas mandem nos comentários abaixo e avisando de novo que links indevidos sendo divulgados serão sumariamente banidos. Não deixem de assinar o canal e compartilhar o video pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>seguranca</category><category>bancos-de-dados</category><category>arquitetura-de-software</category><category>akitando</category></item><item><title>[Akitando] #95 - Árvores: O Começo de TUDO | Estruturas de Dados e Algoritmos</title><link>https://akitaonrails.github.io/2021/04/06/akitando-95-arvores-o-comeco-de-tudo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/04/06/akitando-95-arvores-o-comeco-de-tudo/</guid><pubDate>Tue, 06 Apr 2021 10:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/9GdesxWtOgs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Este é o final da minha Trilogia de Estruturas de Dados e Algoritmos e finalmente vou conseguir falar sobre o mais importante na matéria: árvores! Vamos de BSTs a AVLs, passando por Red Black Trees e muito mais! Se você nunca estudou sobre isso, ou não tinha entendido quando estudou, esta é sua chance!&lt;/p&gt;
&lt;p&gt;Conteúdo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;02:27 - Distribuição Gaussiana&lt;/li&gt;
&lt;li&gt;05:53 - Hashing&lt;/li&gt;
&lt;li&gt;09:43 - Todo mundo erra&lt;/li&gt;
&lt;li&gt;10:18 - Recapitulando&lt;/li&gt;
&lt;li&gt;12:34 - Pilhas e Filas&lt;/li&gt;
&lt;li&gt;12:57 - Grafos&lt;/li&gt;
&lt;li&gt;13:25 - Entendendo Redes&lt;/li&gt;
&lt;li&gt;15:05 - Entendendo Árvores&lt;/li&gt;
&lt;li&gt;16:34 - Heap/Memória&lt;/li&gt;
&lt;li&gt;18:21 - Construindo Árvores&lt;/li&gt;
&lt;li&gt;19:31 - Vantagem da Partição (log)&lt;/li&gt;
&lt;li&gt;20:18 - Árvore de Procura Binária&lt;/li&gt;
&lt;li&gt;22:24 - Visualizando Inserção na Árvore&lt;/li&gt;
&lt;li&gt;26:19 - Imprimindo a Árvore Ordenada&lt;/li&gt;
&lt;li&gt;30:24 - Procura Binária&lt;/li&gt;
&lt;li&gt;32:01 - Complexidade de Recursos&lt;/li&gt;
&lt;li&gt;34:52 - Pior Caso&lt;/li&gt;
&lt;li&gt;35:47 - Red Black Trees&lt;/li&gt;
&lt;li&gt;38:33 - Cor e Rotação&lt;/li&gt;
&lt;li&gt;42:52 - AVL Trees&lt;/li&gt;
&lt;li&gt;44:51 - Red Black no Mundo Real&lt;/li&gt;
&lt;li&gt;47:06 - B-Tree e B+ Tree&lt;/li&gt;
&lt;li&gt;48:47 - Bancos de Dados&lt;/li&gt;
&lt;li&gt;52:46 - Intuição em Algoritmos&lt;/li&gt;
&lt;li&gt;54:13 - Por que estudar tudo isso?&lt;/li&gt;
&lt;li&gt;57:04 - Conclusão&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Meus Vídeos Mencionados:&lt;/p&gt;</description><content:encoded><![CDATA[

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

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Este é o final da minha Trilogia de Estruturas de Dados e Algoritmos e finalmente vou conseguir falar sobre o mais importante na matéria: árvores! Vamos de BSTs a AVLs, passando por Red Black Trees e muito mais! Se você nunca estudou sobre isso, ou não tinha entendido quando estudou, esta é sua chance!</p>
<p>Conteúdo:</p>
<ul>
<li>00:00 - Intro</li>
<li>02:27 - Distribuição Gaussiana</li>
<li>05:53 - Hashing</li>
<li>09:43 - Todo mundo erra</li>
<li>10:18 - Recapitulando</li>
<li>12:34 - Pilhas e Filas</li>
<li>12:57 - Grafos</li>
<li>13:25 - Entendendo Redes</li>
<li>15:05 - Entendendo Árvores</li>
<li>16:34 - Heap/Memória</li>
<li>18:21 - Construindo Árvores</li>
<li>19:31 - Vantagem da Partição (log)</li>
<li>20:18 - Árvore de Procura Binária</li>
<li>22:24 - Visualizando Inserção na Árvore</li>
<li>26:19 - Imprimindo a Árvore Ordenada</li>
<li>30:24 - Procura Binária</li>
<li>32:01 - Complexidade de Recursos</li>
<li>34:52 - Pior Caso</li>
<li>35:47 - Red Black Trees</li>
<li>38:33 - Cor e Rotação</li>
<li>42:52 - AVL Trees</li>
<li>44:51 - Red Black no Mundo Real</li>
<li>47:06 - B-Tree e B+ Tree</li>
<li>48:47 - Bancos de Dados</li>
<li>52:46 - Intuição em Algoritmos</li>
<li>54:13 - Por que estudar tudo isso?</li>
<li>57:04 - Conclusão</li>
</ul>
<p>Meus Vídeos Mencionados:</p>
<ul>
<li>Monetizar? Bloquear ADs? O que Fazer? (<a href="https://www.youtube.com/watch?v=dOe9X6Q_-nU"target="_blank" rel="noopener">https://www.youtube.com/watch?v=dOe9X6Q_-nU</a>)</li>
<li>Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 1/2 (<a href="https://www.youtube.com/watch?v=CcU5Kc_FN_4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=CcU5Kc_FN_4</a>)</li>
<li>O Guia +Hardcore de Introdução à COMPUTAÇÃO (<a href="https://www.youtube.com/watch?v=8G80nuEyDN4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=8G80nuEyDN4</a>)</li>
<li>O Computador de Turing e Von Neumann | Por que calculadoras não são computadores? (<a href="https://www.youtube.com/watch?v=G4MvFT8TGII"target="_blank" rel="noopener">https://www.youtube.com/watch?v=G4MvFT8TGII</a>)</li>
<li>Turing Complete, Emuladores e o Chip ARM M1 (<a href="https://www.youtube.com/watch?v=kz3649U2sJY"target="_blank" rel="noopener">https://www.youtube.com/watch?v=kz3649U2sJY</a>)</li>
<li>Qual a REAL diferença entre Arquivos Binário e Texto?? 🤔 (<a href="https://www.youtube.com/watch?v=oSCVb4Ts-G4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=oSCVb4Ts-G4</a>)</li>
<li>Hello World Como Você Nunca Viu! | Entendendo C (<a href="https://www.youtube.com/watch?v=Gp2m8ZuXoPg"target="_blank" rel="noopener">https://www.youtube.com/watch?v=Gp2m8ZuXoPg</a>)</li>
<li>O que vem DEPOIS do Hello World | Consertando meu C (<a href="https://www.youtube.com/watch?v=YyWMN_0g3BQ"target="_blank" rel="noopener">https://www.youtube.com/watch?v=YyWMN_0g3BQ</a>)</li>
</ul>
<p>Links:</p>
<ul>
<li>Red-black Trees (rbtree) in Linux (<a href="https://www.kernel.org/doc/Documentation/rbtree.txt"target="_blank" rel="noopener">https://www.kernel.org/doc/Documentation/rbtree.txt</a>)</li>
<li>Red-black Trees Animated (<a href="https://www.cs.usfca.edu/~galles/visualization/RedBlack.html"target="_blank" rel="noopener">https://www.cs.usfca.edu/~galles/visualization/RedBlack.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>No final eu acabei me empolgando no assunto de algoritmos e estruturas de dados. Eu não queria continuar porque dá bastante trabalho fazer esse tipo de episódio, mas já que cheguei até aqui, me senti na obrigação falar da estrutura de dados que eu considero a mais importante da matéria: árvores ou trees.</p>
<p>Os vídeos acabaram trazendo muita gente nova que acho que não entendeu a proposta do canal. Eu não tô fazendo um substituto pra cursos oficiais de faculdades, e eu nem tenho intenções de parecer que seja. Só tô discutindo assuntos que acho que são importantes que iniciantes procurem estudar por conta própria. E pra isso eles precisam pelo menos saber que esses assuntos existem e porque são importantes.</p>
<p>Pra quem não sabe, o canal não tem fins lucrativos, eu não tenho patreons, nem patrocínios de nenhum tipo, nem vendo nada. E tudo que é arrecadado com ads eu faço doação de 200%, quer dizer, eu pego o que arrecado e dobro do meu bolso pra outros projetos de educação sem fins lucrativos. Vejam meu video acima explicando e não esqueçam de mandar mais sugestões de projetos pro e-mail contato @ codeminer42.com.</p>
<p>Como eu faço os vídeos nas minhas horas vagas e como desde o fim do ano passado meu volume de trabalho aumentou drasticamente, tem sobrado bem menos tempo pra me dedicar. Infelizmente não é porque tô jogando muito videogame. Meu pobre PS5 tá pegando pó desde que comprei. E eu sou meio retardado das idéias e decidi justo fazer videos de 1 hora de duração, trabalhosos de editar, e obviamente cometi erros pra lá e pra cá. Desculpem por isso, mas lembrem que eu relato os principais nas erratas na descrição e até mesmo no video seguinte. Por outro lado acho bacana que vocês estão prestando atenção e notando esses erros.</p>
<p>E só pra variar, o comecinho do episódio de hoje vai ser pra eu falar das erratas do episódio anterior. Apesar de pra alguns parecer coisa pequena, é importante eu explicar o correto, porque se você entender completamente esta minissérie de 3 episódios, talvez comece a enxergar programação e código de uma forma diferente. E mais do que isso: que cometer erros faz parte do processo. Então vamos lá!</p>
<p>(&hellip;)</p>
<p>Um erro besta, mas importante, é quando eu falo sobre a função de hashing pro nosso exemplo de Hashtable. Sou o primeiro a dizer que as pessoas não devem achar que tudo é uma distribuição gaussiana ou normal no mundo, porque não são. E eu mesmo cometi o erro de fazer isso. Vamos entender rapidamente porque isso é importante e aproveitar pra fazer umas tangentes.</p>
<p>Eu não vou explicar estatística porque eu não me acho a pessoa mais adequada. Mas todo mundo já deve ter visto a distribuição gaussiana ou normal. Todo tipo de evento independente pode ser gaussiano, por exemplo a altura média da população. Faz de conta que se medirmos a altura de 100 pessoas, 80 vão estar nos 1 metro e sessenta, 12 vão estar perto de 1 metro e meio ou 1 metro e oitenta, e 8 entre 1 metro e quarenta e 1 metro e noventa. Se colocar isso num gráfico, mesmo medindo poucas pessoas, a gente começa a ver o tal formato de sino ou bell shape, que caracteriza uma distribuição normal.</p>
<p>A média é onde costuma estar a maioria das medições, no meio. E quanto mais pra direita ou pra esquerda vai indo, as medições caem rápido. Essas distâncias a gente chama de sigmas. Se não me engano, 65 porcento das medições vão estar em sigma 1, 95 porcento em sigma 2, e 99 ponto sete porcento em sigma três. Quem estudou coisas como processos de controle de qualidade conhece Six Sigma, ou sigma 6 que é o famoso 99 ponto 99966 ou 3.4 defeitos por milhão.</p>
<p>Quando a gente fala 99.7% parece muito bom, porque nas nossas concepções mais casuais, parece &ldquo;perto de 100 porcento o suficiente&rdquo;, mas não é. Um contrato de SLA Sigma 3 de 99.7% significa 1 dia inteiro fora do ar num ano, por exemplo. Em uma indústria que produz milhões de itens por mês, significa 3 MIL defeitos por milhão de itens, por mês. Infelizmente, perfeição, ou seja, zero defeitos é quase impossível no mundo real por períodos prolongados de tempo. Eventualmente vai ter erros humanos, falhas mecânicas. Six Sigma costuma ser a meta.</p>
<p>A última coisa a se lembrar sobre distribuições normais e médias é que elas só valem pra eventos independentes entre si. Quando você mensura coisas onde um não afeta o outro, como jogar dados, peças fabricadas em massa, altura de uma população. Mas um lugar onde todo mundo usa errado é salários, porque o salário de um influencia o dos demais, não é um evento independente. É um número dinâmico que varia por diversos fatores. Outras distribuições estatísticas vão se encaixar melhor como a Lei de Potência ou Power Law, mas não a gaussiana.</p>
<p>Enfim, essa tangente não tem nada a ver com a minha errata, foi só algumas coisas que eu já  queria compartilhar mesmo. Mas a errata é que na explicação sobre funções de hashing eu disse que o ideal seria uma função que conseguisse devolver números que caracterizam uma distribuição gaussiana. Mas tá errado, pois significa que na média ele ia devolver o mesmo valor, com alguns números diferentes próximos da média. Por exemplo, se fosse uma função que devolve números entre 1 e 10, ele poderia só ficar devolvendo o número 5 por exemplo, que é exatamente o oposto do que a gente quer.</p>
<p>Na verdade a distribuição pra esse tipo de função deve ser uniforme onde, aí sim na média, ele devolve a mesma quantidade pra cada número do range. E por isso minha confusão antes. No exemplo, se for uma função que devolve números de 1 a 10, e eu rodar essa função cinquenta vezes, cada número de 1 a 10 deveria aparecer na média 5 vezes, e assim balancear o uso do meu hashtable.</p>
<p>E mesmo assim não deveria devolver cinco números 1s, depois cinco números 2s etc de forma fácil de prever, determinístico, e aí eu disse que deveria ser o mais próximo de aleatório quanto possível, se mantendo numa distribuição uniforme. E eu sei que isso que eu acabei de falar, por si só é assunto pra outro episódio, então pesquisem sobre números pseudo-aleatórios pra mais detalhes depois.</p>
<p>Continuando, eu usei o exemplo de função de hashing djb2 e disse que não é necessariamente a melhor, mas não tá correto também, na realidade a djb2 é uma boa função se você não souber nenhuma outra. Como disse antes, o importante é os resultados caírem o mais perto possível de uma distribuição uniforme e evitar colisões, ou seja, evitar devolver muito do mesmo resultado.</p>
<p>Dentre as funções de hashing mais conhecidas, um exemplo didático é o Lose Lose, um dos piores, muita colisão. Tem o Super Fast Hash, que como o nome diz, é rápido, mas também muita colisão. E tem o CRC32 que é bom mas precisa de um lookup table de 1 kilobyte, então consome mais recursos que outros algoritmos, o que pode fazer diferença num ambiente limitado, como embarcado, mas é bom pra todo o resto. Não tenho certeza de cabeça, mas acho que é usado em coisas como protocolos de rede, por exemplo.</p>
<p>O que eu venho explicando desde os episódios sobre Turing como tudo são números. Endereços de memória são números. Strings é uma cadeia de bytes, tudo é um numerozão binário. E hashing é uma forma de conseguir números menores que representam um número maior, como Strings. Então um texto da wikipedia é um linguição de bits gigantes, um numerozão enorme. E com hashing podemos etiquetar esse textão com um número menor. No caso de um SHA-256 da vida, seria com uma etiqueta numérica de 256 bits. É como um número de série de um item. O ideal é ser um número único, mas não é garantia e por isso temos colisões onde dois itens diferentes, dois textões diferentes de wikipedia podem chegar no mesmo hash.</p>
<p>Quem já programa poderia pensar, mas porque não usamos coisas como SHA-512? Eles são usados pra segurança, não são melhores? Eles tem mais ou menos a mesma função, devolver um número que representa uma cadeia de bits, um String, como a chave do nosso hash. E esse número costuma ser o mais único quanto possível. Nunca vai ser 100% sempre único, mas quanto maior o valor do resultado, estatisticamente menores as chances disso acontecer.</p>
<p>Porém a categoria de funções de hashing como SHA-512 foram desenhados pra ter características de segurança, se usados em conjunto com coisas como salts, como eu expliquei no episódio de criptografia. São mais avançados e consomem mais recursos e mais processamento pra gerar esse número. Portanto, não são necessariamente eficientes como geradores de chaves num hashtable se não há requerimentos de segurança junto.</p>
<p>Em particular o SHA-512 é rápido em máquinas de 64-bits porque as funções de cálculo vem implementado em hardware no CPU hoje em dia. De qualquer forma não quer dizer que não poderia ser usado, só que não vai ser o mais eficiente pra hashtables genéricos. E voltando pra errata, eu falei no video que o cálculo do djb2 faz bitwise shift de 5 bits pra esquerda, que é um cálculo rápido e é equivalente a multiplicar por 3 em decimal. O certo é multiplicar por 32.</p>
<p>Eu ainda cometi dois erros sobre complexidade. O primeiro foi quando falei que NP é tempo não polinomial, mas o correto, que até falo depois no vídeo, é que NP é Non Deterministic Polynomial Time ou Tempo polinomial não determinístico. Outro erro foi quando falei que complexidade exponencial é Big O de 2 elevado a N mas na realidade é N elevado a N. E falando nisso eu disse que fatorial é a complexidade mais cara, mas na realidade é a exponencial, porque por exemplo, 15 fatorial é 15 vezes 14 vezes 13 vezes 12 etc até 1. Mas 15 elevado a 15, exponencial, é 15 vezes 15 vezes 15 etc 15 vezes.</p>
<p>Exemplo de trecho que tirei direto da cabeça e não revisei direito. Eu disse que um exemplo de complexidade fatorial é a implementação do cálculo de fatorial recursivo. Tá errado e nem lembro porque eu falei isso, mas na realidade é O linear e não fatorial. O exemplo correto pra fatorial é o Travelling Salesman, que eu traduzi direto do inglês pra Vendedor Viajante mas no Brasil povo tá acostumado a falar em problema do caixeiro viajante. Na real tanto faz, se você procurar em português vai achar como caixeiro mesmo, mas eu recomendo procurar como travelling salesman que vai achar artigos muito melhores sobre o problema.</p>
<p>Tudo isso dito, vamos ver se neste último episódio da série eu cometo menos erros. Sempre leiam os comentários, tem um povo que tá prestando atenção e corretamente corrigindo. Falando nisso eu fico surpreso que tem gente que acha que pessoas como eu não erram. Óbvio que erram e não é pouca coisa, no máximo eu diria que pessoas experientes no geral reconhecem o erro mais rápido e corrigem mais rápido. Pessoal inexperiente, por alguma razão, insiste no erro por muito mais tempo do que deveriam. No final todo mundo erra, então não fiquem frustrados se vocês erram, o problema é errar a mesma coisa o tempo todo.</p>
<p>Pra você não se perder vamos recapitular. Ano passado eu adaptei dois vídeos de dois canais que eu gosto, do Ben Eater e do Gaming Mechanics Explained. Primeiro pra mostrar no nível do transistor, num protoboard, como bits são armazenados numa memória e como instruções de &ldquo;baixo nível&rdquo; são construídas com transistores, a verdadeira linguagem de máquina. Tudo começa com instruções simples como soma, construído em hardware, passando elétrons por transistores, formando portas lógicas. E com isso você chega no Assembly.</p>
<p>Depois, de propósito, resolvi contar a história de Turing e Von Neumann pra explicar como instruções e dados populam a mesma linguiçona de bits e o que isso significa, os primórdios da computação moderna. Na sequência eu quis falar sobre tipos de arquivos, que todo mundo usa e ninguém entende que arquivos texto e arquivos executáveis é tudo a mesma coisa, tudo um linguição de bits. Todo programa, todo arquivo, é nada mais que um numerozão gigante.</p>
<p>A ordem que esses bits aparecem nessa fita determina seu formato. Cada bit tem um endereço, que é a posição na fita. E quando você tem sequências especiais de bits em determinados endereços vai começar a diferenciar entre uma imagem jpeg e um executável de Linux, por exemplo. Aliás, quando eu falei de memória virtual nos dois últimos episódios, a divisão de segmentos como stack, heap, ROData, Text é específica de Linux e executáveis ELF. Windows e Mac o layout dos segmentos é diferente, ou seja, o range de endereços de cada coisa vai ser diferente. Mesma coisa entre arquiteturas de 32 ou 64-bits.</p>
<p>Enfim, daí chegamos na trilogia de agora, onde comecei com diferentes Hello World pra mostrar como essa fita é usada e como a memória começa a ser organizada em coisas como stack e heap e finalmente no último episódio mostrei como existem diferentes tipos de listas: arrays, listas ligadas, hashtables. Não cheguei a mencionar, mas você pode implementar Stacks que são pilhas e Queues que são filas usando qualquer uma dessas estruturas. Números inteiros, arrays de chars, e coisas assim são tipos primitivos, coisas “concretas”. Pilhas ou filas são conceitos, que podem ser implementados de vários jeitos, e por isso chamamos de tipos abstratos.</p>
<p>Pilhas podem ser arrays ou listas ligadas onde você só tem acesso ao último elemento, no caso de pilha, ou seja, só operações de push pra empurrar um elemento pro fim da lista e pop pra tirar o elemento do começo da lista. Filas você também tem duas operações principais, uma enqueue pra colocar no fim da lista e uma dequeue pra tirar do começo da lista. São casos de estruturas que chamamos de lineares.</p>
<p>Então também existem estruturas não-lineares, e aqui entra o assunto de Grafos. Eu não lembro se era correto dizer assim, mas um caso particular de Grafo unidirecional é uma Árvore. Voltando. Grafos você vê todos os dias. Toda rede, seja rede local ou de internet, é um grafo. Um conjunto de nós ou Nodes, como computadores, ligados por Edges que são tipo o cabo ethernet pro roteador ou mesmo a conexão wifi que liga seu smartphone no roteador.</p>
<p>Uma rede social é um grafo, onde os Nodes são as pessoas. Os Edges são os relacionamentos entre elas. E você pode ter todo mundo ligado com todo mundo ou diversos grupos ligados por algumas poucas pessoas em comum entre os grupos. Se quiser entender redes sociais, você começa entendendo Grafos.</p>
<p>Mas grafos é um assunto muito grande pra explicar em pouco tempo. Por hoje, entendam que existe essa matéria pra estudar. Praticamente tudo que você chama de Rede pode ser descrito com grafos. Por exemplo, o algoritmo de como encontrar o caminho mais curto entre dois Nodes num Grafo, que é o Shortest Path do Dijkstra. Poderia ser o caminho mais curto entre duas localizações num Google Maps ou o caminho mais curto no relacionamento de pessoas num LinkedIn.</p>
<p>E na estatística existem vários estudos sobre as particularidades de distribuição de diferentes grafos ou redes. Se quiser se aprofundar, recomendo livros de rede do Steven Strogatz e Duncan Watts, que inclusive tem um modelo que leva o nome deles, o modelo Watts-Strogatz. Entendendo redes de forma estatística você chega nos temas de small worlds ou mundos pequenos, os famosos seis graus de separação.</p>
<p>Acho que na faculdade o povo ensina até o modelo de Erdös e Rényi. O tema de mundos pequenos explodiu por conta do aparecimento das redes sociais massivas no começo do século XXI. Porque agora temos dados reais massivos pra analisar, o tal Big Data, que foi material de pesquisa pra um Duncan Watts quando trabalhou no Yahoo e hoje na Microsoft. Daí você chega nos modelos de Watts-Strogatz e Barabási-Albert. Se você acha que entende redes sociais e nunca ouviu falar deles, ainda não sabe o que são redes sociais.</p>
<p>Isso tudo dito, deixa eu dar uns passos pra trás. Depois de aprender arrays, listas ligadas e hashtables acho que é importante fechar com árvores que, por si só, é um assunto bem grande, podemos ir pra B-trees que sendo honesto eu não lembro mais tudo de cabeça. B-trees é o que tá por trás de índices de bancos de dados, por exemplo. Mas falando genericamente de árvores, você vai ver essa estrutura em tudo que é lugar.</p>
<p>Exemplos simples? Basta ver seu Windows Explorer, Mac Finder ou GNOME Nautilus. Seu file system, o sistema de arquivos é inteiro baseado em árvores. Diretórios e arquivos são Nodes na árvore, a raíz pode ser o seu C:. Veja um documento HTML, qualquer página da Web. Sua representação é o famoso DOM ou Document Object Model, uma árvore onde cada Node, ou entidade, são elementos como parágrafos, forms, imagens e tudo mais. E falando em objetos, veja um Java da vida cuja biblioteca é toda herdada a partir da classe Object. Toda herança de classes e interfaces é representada numa árvore.</p>
<p>E sem árvores você não consegue seguir pras próximas matérias na ciência da computação como sistemas operacionais ou compiladores. Todo seu código fonte, seja numa linguagem compilada como C ou interpretada como Python ou Javascript passa por etapas como análise léxica, depois análise sintática, que gera coisas como um AST ou abstract syntax tree, que é a representação do seu código texto no formato de uma árvore. E mesmo pra entender sistemas operacionais você vai precisar de árvores.</p>
<p>Quando falei de memória virtual vimos dois exemplos de segmentos importantes. Primeiro a stack, que controla o estado da execução do programa a cada instrução e que é implementado como uma estrutura de dados linear de Pilha. Já o segmento Heap pode ser gerenciado com uma árvore. Na realidade o Heap usa uma estrutura de dados baseada em árvore chamada Heap. Um Heap pode ser uma implementação de um tipo de dados abstrato chamado Fila de Prioridade ou Priority Queue. São mais tipos abstratos como a Pilha ou Fila, que podem ser implementadas com as estruturas de dados primitivas que expliquei antes.</p>
<p>Se for uma binary heap, então é implementado usando uma binary tree, ou árvore binária. A distinção é importante porque nos exemplos de file system ou DOM de HTML as árvores não são binárias, porque cada Node não tem só dois elementos, eles podem ter múltiplos filhos ou até múltiplos pais, então são grafos mas não árvores binárias. Provavelmente B-trees, que vou explicar o que são mais pro final.</p>
<p>A estrutura de Heap é a usada no algoritmo de ordenação Heapsort. Lembram quando falei de Quicksort no episódio anterior? Tem outras nos livros de algoritmos como mergesort, insertion sort; e heapsort costuma aparecer junto. Mas o importante é saber que sua memória tá organizada numa árvore binária.</p>
<p>Uma árvore tem como característica começar de um Node raíz ou Root. Root pra quem não sabe, é tradução pra raíz, o primeiro Node. O primeiro usuário num Linux chama Root, por isso. Quando você digita caminhos de diretórios a primeira barra ao contrário, backslash, a gente chama de root do file system. Num Windows o “C:\” só é a raíz do seu primeiro HD que o Windows sempre chama de C (e pra quem não sabia é porque A e B eram reservados pra drives de disquetes). Mas enfim, você vai ver esse nome root ou raíz em vários lugares.</p>
<p>Lembram da Lista Ligada? É uma struct que tem um valor qualquer que queremos guardar e um ponteiro pra outro Node, o previous ou next no nosso exemplo. Numa árvore, cada Node vai apontar pra mais de um Node. Numa árvore binária, em particular, aponta pra 2 outros Nodes que comumente chamamos de <code>left</code> e <code>right</code> ou esquerda e direita.</p>
<p>Uma árvore pode ser balanceada ou não-balanceada. Numa árvore binária, não balanceada, onde vamos incluindo elementos, digamos, tudo pra direita da árvore, vai se comportar basicamente como uma lista ligada com complexidade O linear pra procura. Vamos dar um exemplo besta de árvore assim.</p>
<p>Pense numa lista de números quaisquer como vinte e sete, quatorze, trinta e cinto, dez, dezenove, trinta e um e quarenta e dois. Agora vamos imaginar uma Struct Node com ponteiros left e right pra outros Nodes. E vamos desenhar como ficaria na memória a versão ineficiente, onde só vou preencher o ponteiro right de cada Node. Veja como elas ficam sequenciais como se fosse uma lista ligada.</p>
<p>É o pior caso. Mas a coisa só fica interessante quando falamos em árvores binárias balanceadas ou pelo menos semi-balanceadas. E nesse caso o tempo de procura vai ser proporcional ao height, ou altura da árvore em vez de ser proporcional ao total de elementos inseridos.</p>
<p>De exemplo, podemos desenhar a mesma sequência mais bonitinho, de uma forma balanceada pra ter ambos left e right preenchidos. Veja como fica mais bonito, com uma altura tamanho 2, sem contar a raiz, em vez de ter altura 7 que era a altura da lista ligada. Mas até aqui não ganhamos muita coisa a não ser reorganizar a estrutura. Se eu quiser procurar onde tá a chave 31 ainda ia precisar sair comparando quase todos os Nodes e a procura seria O linear do mesmo jeito que na lista ligada. Vamos ver como melhorar isso.</p>
<p>Eu falei antes que um dos problemas de um array é quando queremos inserir um elemento no meio dele. Digamos, inserir na posição 5 de um array de 10 elementos. Tem duas formas. A primeira é copiar os elementos da posição 6 até 10 uma posição mais pra frente - se ainda tiver espaços vazios) e inserir o novo elemento na posição 6. Outra forma é se for uma lista ordenada, daí inserimos o novo elemento no fim do array e mandamos ordenar ele usando um Timsort ou Quicksort da vida.</p>
<p>Qualquer uma das formas pode ter uma complexidade maior que O linear, provavelmente loglinear que é a complexidade de um algoritmo de ordenação como Quicksort. Mas, e se fosse possível inserir um elemento numa lista pré-ordenada em complexidade menor que linear, talvez logaritmica?</p>
<p>Apesar do nome árvore, e apesar da imagem na cabeça ser algo como diretórios e arquivos numa hierarquia, pense em árvores binárias como se fosse uma lista mesmo. Aqui vem o negócio que se fala sempre sobre abstrações. O objetivo é o seguinte: ter uma lista ordenada que, no geral, é mais barato de procurar e mais barato de inserir ou apagar elementos do que um array ou lista ligada.</p>
<p>Árvores aparecem porque, como expliquei no video anterior, se temos um problema que podemos ir particionando, tipo quebrando uma lista ao meio, depois quebrando essa metade ao meio, isso é característica de algo logarítmico. Pense numa árvore visualmente como se fosse uma lista particionada. A raíz poderia ser o elemento mais do meio da lista e cada um de seus filhos tem metade da lista e assim sucessivamente até o fim da lista.</p>
<p>Mas só particionar não tem muita vantagem se a lista estiver bagunçada, tudo fora de ordem. O grande truque é ter essa árvore com os itens ordenados. Porém, o que significa Nodes ordenados numa árvore que não parece ter uma única direção? Quem é o primeiro elemento? Quem é o segundo? Vamos pegar essa sequência de 14 números aleatórios e organizar numa árvore binária ordenada. Veja como o número 0 tá mais pra esquerda, o 34 virou a raíz e mais pra direita tem números maiores como 99.</p>
<p>O único código que vou mostrar linha a linha hoje é da binary search tree ou árvore de procura binária. Vamos começar criando a struct Node, parecido com o da lista ligada e do hashtable. É muito simples, tem um campo pro número que vamos guardar. Daí os ponteiros de left e right pros Nodes filhos. Agora vamos direto fazer uma função main que vai popular essa árvore. Note que eu declarei a variável root só com o tipo Node em vez de <code>struct Node</code> como vim fazendo antes. Isso porque eu declarei ele como um novo tipo com <code>typedef</code> então <code>Node</code> é a mesma coisa que <code>struct Node</code>. Fica mais curto de escrever e agora vocês já viram do outro jeito pra ver a vantagem de usar assim.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

typedef struct Node {
    int data;

    struct Node* left;
    struct Node* right;
} Node;

Node* insert(Node *root, int data) {
    Node *temp = (Node*) malloc(sizeof(Node));

    temp-&gt;data = data;
    temp-&gt;left = NULL;
    temp-&gt;right = NULL;

    if (root == NULL) {
        root = temp;

    } else {
        Node *current = root;
        Node *parent = NULL;

        while(1) {
            parent = current;

            if (data &lt; parent-&gt;data) {
                current = current-&gt;left;

                if (current == NULL) {
                    parent-&gt;left = temp;
                    return root;
                }
            } else {
                current = current-&gt;right;

                if (current == NULL) {
                    parent-&gt;right = temp;
                    return root;
                }
            }
        }
    }
    return root;
}

Node* search(Node *root, int data) {
    Node *current = root;
    printf(&#34;Visiting elements: &#34;);

    while(current) {
        printf(&#34;%d &#34;, current-&gt;data);

        if (data &lt; current-&gt;data) {
            current = current-&gt;left;
        } else if (data &gt; current-&gt;data) {
            current = current-&gt;right;
        } else {
            return current;
        }
    }
}

void pre_order_traversal(struct Node* root) {
    if(root) {
        printf(&#34;%d &#34;, root-&gt;data);
        pre_order_traversal(root-&gt;left);
        pre_order_traversal(root-&gt;right);
    }
}

void inorder_traversal(struct Node* root) {
    if(root) {
        inorder_traversal(root-&gt;left);
        printf(&#34;%d &#34;, root-&gt;data);
        inorder_traversal(root-&gt;right);
    }
}

void post_order_traversal(struct Node* root) {
    if(root) {
        post_order_traversal(root-&gt;left);
        post_order_traversal(root-&gt;right);
        printf(&#34;%d &#34;, root-&gt;data);
    }
}

int _print_t(Node *tree, int is_left, int offset, int depth, char s[20][255])
{
    char b[20];
    int width = 5;

    if (!tree) return 0;

    sprintf(b, &#34;(%03d)&#34;, tree-&gt;data);

    int left  = _print_t(tree-&gt;left,  1, offset,                depth &#43; 1, s);
    int right = _print_t(tree-&gt;right, 0, offset &#43; left &#43; width, depth &#43; 1, s);

    for (int i = 0; i &lt; width; i&#43;&#43;)
        s[2 * depth][offset &#43; left &#43; i] = b[i];

    if (depth &amp;&amp; is_left) {

        for (int i = 0; i &lt; width &#43; right; i&#43;&#43;)
            s[2 * depth - 1][offset &#43; left &#43; width/2 &#43; i] = &#39;-&#39;;

        s[2 * depth - 1][offset &#43; left &#43; width/2] = &#39;&#43;&#39;;
        s[2 * depth - 1][offset &#43; left &#43; width &#43; right &#43; width/2] = &#39;&#43;&#39;;

    } else if (depth &amp;&amp; !is_left) {

        for (int i = 0; i &lt; left &#43; width; i&#43;&#43;)
            s[2 * depth - 1][offset - width/2 &#43; i] = &#39;-&#39;;

        s[2 * depth - 1][offset &#43; left &#43; width/2] = &#39;&#43;&#39;;
        s[2 * depth - 1][offset - width/2 - 1] = &#39;&#43;&#39;;
    }

    return left &#43; width &#43; right;
}

void print_t(Node *tree)
{
    char s[20][255];
    for (int i = 0; i &lt; 20; i&#43;&#43;)
        sprintf(s[i], &#34;%80s&#34;, &#34; &#34;);

    _print_t(tree, 0, 0, 0, s);

    for (int i = 0; i &lt; 20; i&#43;&#43;)
        printf(&#34;%s\n&#34;, s[i]);
}

int main() {
    int i;
    int array[] = { 34, 84, 15, 0, 2, 99, 79, 9, 88, 89, 18, 31, 39 };
    Node *root = NULL;

    for(i = 0; i &lt; 17; i&#43;&#43;) {
        root = insert(root, array[i]);
        printf(&#34;Inseriu: %d\n&#34;, array[i]);
    }

    Node *temp = search(root, 30);

    if(temp) {
        printf(&#34;[%d] Element found.\n&#34;, temp-&gt;data);
    } else {
        printf(&#34;[x] Element (%d) not found.\n&#34;, i);
    }

    temp = search(root, 88);

    if(temp) {
        printf(&#34;[%d] Element found.\n&#34;, temp-&gt;data);
    } else {
        printf(&#34;[x] Element (%d) not found.\n&#34;, i);
    }

    printf(&#34;\nPreorder traversal: &#34;);
    pre_order_traversal(root);

    printf(&#34;\nInorder traversal: &#34;);
    inorder_traversal(root);

    printf(&#34;\nPost order traversal: &#34;);
    post_order_traversal(root);

    printf(&#34;\n&#34;);
    print_t(root);

    return 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>Começamos com um array de 14 elementos, números aleatórios. E um Node chamado root pra ser a raíz. Fazemos um loop pra inserir todos os elementos desse array na árvore. Então já sabemos que vamos precisar de uma função chamada <code>insert</code>. Só pra ficar mais fácil de explicar e não precisar falar de ponteiro de ponteiros, a primeira variável vai ser o endereço pra raíz, que começa nulo e a função devolve o endereço da raíz de volta quando popular. Tem jeito melhor de fazer isso, mas pro exemplo isso é suficiente.</p>
<p>Chegou a hora de tentar procurar um elemento dentro da árvore criando outra função, a <code>search</code>. Mesma coisa dos outros episódios, vamos passar o endereço da raíz e o número que tamos procurando. Agora vamos voltar lá pro começo do arquivo e criamos essas funções, começando pela <code>insert</code> que vai receber um ponteiro pra raíz e o número que queremos inserir e retorna de volta o endereço da raíz.</p>
<p>Começamos criando uma instância de Node no heap com malloc e associando o endereço na variável <code>temp</code> de temporário. Daí preenchemos a estrutura com o número que passamos no campo <code>data</code> e inicializamos os ponteiros de left e right pra ser nulo. Aliás, importante, isso foi uma coisa que esqueci mesmo nos exemplos de lista ligada e hashtable. Se não inicializar os ponteiros com NULL, explicitamente, nada garante que no espaço alocado não havia bits de outra coisa lá e os campos podem começar já preenchidos com lixo, apontando pra algum lugar aleatório bizarro. Das duas uma, ou eu inicializo na mão como fiz agora gravando NULL ou em vez de <code>malloc</code> posso usar <code>calloc</code> pra que já faz essa limpeza.</p>
<p>Voltando, o começo é simples. Se a raiz, ou root, for nula como é o caso na primeira vez, ele passa a apontar pro mesmo lugar que a variável <code>temp</code>. Daí termina o <code>if</code> e dá return do novo root. Caso contrário, vamos fazer uma cláusula <code>else</code> e começamos criando um novo ponteiro temporário chamado <code>current</code> que aponta pro Node do root e outro ponteiro chamado <code>parent</code> de pai ou mãe, tanto faz, que começa nulo.</p>
<p>Começamos fazendo um loop infinito com <code>while(1)</code>. Se a gente não fizer nada dentro desse loop pra sair, vai ficar repetindo dentro do loop pra sempre. Dentro começamos fazendo a variável <code>parent</code> apontar pra <code>current</code> que, na primeira passada do loop é o primeiro Node que já tá inserido. O truque começa aqui.</p>
<p>Vamos checar dois casos, se o número que queremos inserir agora for menor que o número do parent. Se não for, cai no <code>else</code> que é o caso oposto, se o número que estamos inserindo for maior ou igual ao do Node parent. No nosso exemplo, a raíz é o número 34. O próximo número da lista é 84. Então ele cai no bloco de else.</p>
<p>Nesse bloco fazemos a variável current apontar pra direita do Node atual, do 34. E agora podemos ter dois casos. Se o Node current for nulo ou se já tiver outro Node. Nesse caso do 2o item ele ainda é nulo, então fazemos o ponteiro pra direita do node parent apontar pro temporário e saímos do loop dando return da raíz de novo.</p>
<p>Vamos completar o código com o próximo item da lista que é o 15. Vai ser um novo insert, passamos o root e o número 15, daí criamos um novo Node temp pra acomodar esse valor 15. Root não é nulo, então entramos no primeiro bloco <code>else</code>. Variável current aponta pro root, parent pra nulo. Entramos no <code>while</code> infinito. Agora fazemos a variável parent ser igual ao current porque vamos usar pra navegar pela árvore; e o parent sempre vai tá apontando pro Node imediatamente acima dela. Fica mais fácil de entender tendo esse diagrama na cabeça.</p>
<p>Agora checamos que 15 é menor que o valor no Node parent, que é raíz 34, então o current vai ser o espaço pra esquerda do Node atual. Por acaso, esse espaço ainda tá vazio, então vai passar a ser o Node 15 que acabamos de criar. Retornamos o root de novo e quebramos o loop infinito. E neste estágio temos uma lista com o 34 no topo, 15 na esquerda e 84 na direita. O código de inserção tá completo, vamos testar mais uma vez com o próximo número da lista, o zero.</p>
<p>Começamos no topo da função insert, passando a raiz e número 0. Criamos o node temporário que vai conter o zero, root não é nulo então seguimos criando o ponteiro current apontando pro Node 34 e parent nulo. Entra no loop infinito e começamos com o parent igual ao current que é a raiz.</p>
<p>Como zero é menor que o parent, que é o 34 ainda, fazemos o current ser o Node à sua esquerda, que é o 15. Sendo o current não nulo, então sai desse bloco e chega ao fim do while. Como o loop é infinito repetimos. Agora o parent vai ser o current 15. De novo, zero é menor que o atual parent que é 15 então fazemos o current ser o Node left à esquerda. Agora não tem ninguém lá, é nulo, e é onde vamos colocar o node zero. Pronto, atribuímos o left do parent 15 pra ser o node temporário zero e damos return pra sair do loop infinito.</p>
<p>Vou acelerar aqui do lado pra inserir os elementos que faltam, e a árvore vai ficando dessa forma. Obviamente não fica bonitinho assim na memória, mas cada Node tem ponteiros apontando pra até dois outros Nodes e podemos rearranjar num diagrama pra ficar visualmente assim. O importante é o seguinte: começa lendo do Node mais à esquerda e vai indo pra direita. Começa pelo zero, depois dois, nove, daí quinze, dezoito, trinta e um e chegamos na raíz trinta e quatro, continua pro trinta e nove, setenta e nove e vai indo até o noventa e nove. Notaram como a lista tá ordenada?</p>
<p>Antes de mostrar como fica a função search, acho que vale mostrar como pegar de volta a lista ordenada e mostrar na tela. Temos 3 formas de navegar por essa lista, pre order, post order e in order. Pre order começa da raiz e vai pra esquerda primeiro, depois pra direita. Indo de cima pra baixo. Post order começa do primeiro node mais à direita, no caso o nove e vai subindo, de baixo pra cima.</p>
<p>In order, como o nome diz é na ordem. E qualquer um desses casos é simples usando recursão. Primeiro vamos colocar na main a chamada pra função inorder_traversal passando o node raiz. Precisamos criar essa função. Ela começa checando se o node passado é nulo, se for, não faz nada e retorna. Caso contrário chama ela mesma passando o Node à esquerda.</p>
<p>Com os nossos valores de exemplo, se a gente pensar como ficaria a Stack do programa, vamos começar chamando inorder pro Node 34. Ela não é nula. Então chama ela mesma de novo passando o Node 15. Acumula no topo da Stack porque não retornamos ainda. Agora o node continua não sendo nulo, então chama ela de novo com o próximo node à esquerda, que é o do zero. Empilha na Stack, de novo. E quando chamarmos outra vez, o Node zero não é nulo, então passamos o node à esquerda dela que, agora é nulo. Empilhamos.</p>
<p>Agora a função vai receber nessa variável root um nulo, então o <code>if</code> vai ser verdadeiro, não faz nada e retorna. Desempilha da Stack. Voltamos pra chamada anterior que vai continuar do Node 0 e dar <code>printf</code> na tela. Vai imprimir zero e agora vamos chamar a função de novo passando quem tá à direita do Node 0, e tem o Node 2. Empilhamos.</p>
<p>Mesma coisa, prestem atenção, O node não é nulo, agora chamamos a função passando o ponteiro pro Node à esquerda do 2, por acaso é nulo. Empilhamos. Já sabemos que o <code>if</code> vai checar a variável como nulo, não faz nada e desempilha. Agora dá <code>printf</code> pro 2 e chama a função passando quem tá à direita do dois. Por acaso é o Node 9. Empilha.</p>
<p>Mesma coisa, node não é nulo, chamamos a função pro node à esquerda do nove que é nulo, já sabemos que vai empilhar, entrar e desempilhar porque é nulo, então vamos pular e na sequência vai dar <code>printf</code> do 9. Agora tenta chamar a função pro Node à direita do 9, que é nulo. De novo, vai empilhar, checar que é nulo, não faz nada e desempilha. E acaba a função pro 9, retorna e desempilha.</p>
<p>A função do 2 também termina, desempilha. A chamada pro zero também acaba e desempilha. Voltamos pro Node 15. Agora continua dando <code>printf</code> pra ele, imprime 15 na sequência do 9 e chama a função pro node à direita do 15, que vai ser o 18. Empilha. Node não é nulo então chama a função pro node à esquerda, que é nulo, podemos pular porque sabemos que vai empilhar e desempilhar. Continua dando <code>printf</code> pro 18. Agora chama a função pro Node 31 à direita.</p>
<p>Pausa, agora olha só, já imprimiu o zero, dois, nove, quinze, dezoito e o próximo vai ser 31, tudo na ordem crescente, ordenadinho e se continuar fazendo a mesma coisa vai imprimir até o 99 na ordem. Se você nunca viu isso antes deve ter dado um nó na cabeça. Mas é justamente isso que precisa acontecer, porque essa estrutura e esse jeito de operar não é intuitivo se você nunca estudou estruturas de dados. Por intuição você vai parar nas listas lineares e nunca vai entender o que vem depois, que são grafos.</p>
<p>Ao final, só de ter inserido um monte de números aleatórios na árvore binária conseguimos pegar os elementos já ordenados. O mesmo resultado poderia ser obtido inserindo os números num array e passando um Quicksort da vida neles. Mas entenda a diferença. Se for considerar puramente só inserção de novos valores, é mais rápido colocar num array, porque é complexidade O constante. Já na árvore vai ser O logaritmico. Ou seja, a inserção na árvore é mais lenta.</p>
<p>Mas a comparação é injusta. A complexidade de inserir elementos numa árvore de procura binária é O logaritmico porque ele já insere os elementos ordenados. Já no array é O constante pra inserir no fim porque não estamos ordenando. Se também quisermos o array ordenado a cada inserção, precisaria rodar um quicksort no mínimo uma vez no final, daí vai ficar mais caro que O loglinear.</p>
<p>Quando o array estiver todo ordenado, podemos pesquisar usando procura binária. Eu não vou mostrar código, mas é assim. O jeito ingênuo de procurar num array é fazer um loop e ir comparando elemento a elemento da esquerda pra direita até achar o 88. Você ia precisar de 10 operações de comparação pra chegar lá. Mas tem um jeito mais rápido que só funciona se a lista já tiver sido ordenada antes.</p>
<p>Você pega o tamanho do array e divide por dois, no caso vai ser 7. Daí pega o endereço do primeiro elemento e soma pelo tamanho de cada elemento multiplicado por sete, que é o elemento 39, mais ou menos no meio da lista. Agora compara, 88 é menor que 39? Não é, então já sabemos que não tá na metade à esquerda do 39 e não precisamos olhar nenhum deles. Só de fazer isso já eliminamos metade da lista que não precisamos nos preocupar.</p>
<p>Recursivamente fazemos a mesma coisa, à direita do 39 temos 6 elementos sobrando, dividido por 2 são 3, somado à posição 7 chegamos na posição 10, que é o meio da metade da direita. E vai ser o elemento 88 por acaso. Comparamos com o valor procurado 88 e já chegamos nele. Ou seja, com 2 operações de endereço pra chegar no meio do meio e 2 operações de comparação chegamos no 88. 4 operações no total, contra os 10 do jeito ingênuo, menos da metade do processamento. E isso porque essa lista é muito pequena. Quanto maior a lista maior vai ser a economia.</p>
<p>Por isso tem vantagem trabalhar com listas ordenadas. Porém, como disse antes, custa O n vezes log de n, loglinear pra ordenar um array. E dependendo do algoritmo, pode custar espaço na memória pra fazer essa ordenação. Um mergesort por exemplo, acho que era um que custava mais caro em espaço extra pra conseguir ordenar.</p>
<p>Lembram da animação que mostrei de mergesort no episódio anterior? Vamos ver de novo. Estão vendo que ele vai guardando valores aqui embaixo? Isso é o espaço temporário que o mergesort gasta pra conseguir ordenar a lista em cima. Então, não é só velocidade de processamento pra se preocupar como também espaço temporário de memória que se gasta. Agora pensa na árvore. Ela não custa muito espaço adicional pra ordenar, e custa literalmente O logaritmico, tudo bem que é pra cada elemento, mas a complexidade é uma ordem de grandeza mais rápida que qualquer ordenação de array.</p>
<p>E uma vez tendo a árvore ordenada, a procura é parecida com o que acabamos de fazer com procura binária de array. Vamos ver. Voltando ao exemplo, precisamos implementar a função <code>search</code>, que vai começar recebendo como argumento o Node raiz da árvore e o valor que estamos procurando, no caso o 88.</p>
<p>Criamos uma variável temporária chamada <code>current</code> apontando pro <code>root</code>. De novo criamos um loop com <code>while</code> que vai quebrar se o node current for nulo. E isso só vai acontecer se a gente navegar pela árvore e nenhum dos Nodes tiver o valor que procurado. Agora é a mesma coisa que na busca binária de array. A inserção garante que nossa árvore binária já tá ordenada como acabei de mostrar.</p>
<p>Vamos comparar o número 88 com o valor do current que é a raíz 34. Como não é menor, pula pro <code>else if</code>. Como é maior faz o <code>current</code> ser o Node 84. Repete o loop. 88 ainda não é menor que 84, pula pro <code>else if</code> de novo e faz current ser o node à direita, right, que é o 99. Repete o loop. Agora sim, 88 é menor que 99 então vamos fazer o current pegar o Node à esquerda de 99, o ponteiro left. Repete o loop. Agora o Node que achamos não é menor, não é maior, então finalmente cai no último <code>else</code> e pronto, achamos o 88.</p>
<p>Em quantidade de comparações é equiparável à busca binária de array ordenado, ordem de grandeza Big O logaritmico porque ambos os casos estamos particionando. Entenda a relação. Inserir elementos no fim de um array é rápido, O constante, caso tenha espaços vazios sobrando no final. Se não tiver, precisa criar um novo array maior e copiar os elementos da anterior pra ela. Complexidade O linear. Se quiser que a lista esteja ordenada pra conseguir procurar via busca binária, vai custar O loglinear de um quicksort da vida. No hashtable não se ordena porque a posição depende do hashing da chave. A procura vai ser O linear também.</p>
<p>Então a pesquisa numa árvore binária vai ser melhor do que num array, lista ligada ou hashtable. Tudo depende de pra que você vai usar a lista. Pra guardar os campos que vem num formulário simples de cadastro, qualquer lista serve, são poucos elementos. Mesmo na força bruta ingênua, a diferença é pouca. Agora, pra gerenciar coisas como um cache de banco de dados, você vai escolher alguma coisa melhor que um hashtable ou uma árvore binária.</p>
<p>Não chegamos no fim da história. Essa nossa árvore binária rudimentar é só a base. O caso ruim pra árvore binária, da forma como implementamos, é tentar adicionar uma lista de números consecutivos. Por exemplo, vamos começar de novo a partir de 99 e tentar inserir cem. Como é maior que 99, vai pra direita. Agora cento e um. Como é maior que 99, vai pro 100. Sendo maior que 100, vai pra direita. Cento e dois, mesma coisa, quando chegar no cento e um, vai pra direita. E se continuarmos adicionando, olha como ficou nossa árvore. Parece um galho seco esperando pra cair, ficou basicamente como uma lista ligada, o pior caso.</p>
<p>Pior caso porque, sendo praticamente uma lista ligada, a complexidade pra procurar deixou de ser O logarítmico e voltou a ser O linear. Ou seja, pra achar o último elemento, vai precisar comparar com todos os outros anteriores. Uma árvore assim é desbalanceada. A gente precisa balancear e distribuir em outros galhos dessa árvore.</p>
<p>O que vimos até aqui é chamado de árvore de procura binária ou binary search trees, cujo acrônimo é BST. O que costuma ensinar depois é como balancear essas BSTs e uma das formas de fazer isso é o que se chama de RBTrees ou Red Black Trees. Antes que alguém saia correndo pros comentários, vou falar sobre AVLs já já, calma aí.</p>
<p>Uma das razões de eu querer pular esse assunto é porque realmente não estou com vontade de explicar linha a linha de como se faz inserções numa árvore Red Black. É muito detalhezinho, vai dar um puta trampo pra editar e indo linha a linha num video vai ser maçante pra caramba. Então, recomendo que vocês procurem o código em qualquer linguagem e testem linha a linha na máquina de vocês. Em vez disso, vou fazer diferente aqui.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;

#define RED   &#39;R&#39;
#define BLACK &#39;B&#39;

#define COUNT 10

typedef struct Node {
    int          key;
    char         color;
    struct Node *left;
    struct Node *right;
    struct Node *parent;
} Node;

// Based on CLRS algorithm, use T_Nil as a sentinel to simplify code
struct Node  T_Nil_Node;
Node* T_Nil = &amp;T_Nil_Node;

Node* Root = NULL;

// A utility function to create a new BST node
Node* newNode(int key)
{
    Node *temp   = (Node*) malloc(sizeof(Node));
    temp-&gt;key    = key;
    temp-&gt;color  = RED;
    temp-&gt;left   = NULL;
    temp-&gt;right  = NULL;
    temp-&gt;parent = NULL;

    return temp;
}

void rotateLeft( Node** T, Node* x)
{
    Node *y  = x-&gt;right;    // set y
    x-&gt;right = y-&gt;left;     // turn y&#39;s left subtree into x&#39;s right subtree{
    if (y-&gt;left != T_Nil)
        y-&gt;left-&gt;parent = x;
    y-&gt;parent = x-&gt;parent;  // link x&#39;s parent to y
    if (x-&gt;parent == T_Nil)
        *T = y;
    else if (x == x-&gt;parent-&gt;left)
        x-&gt;parent-&gt;left = y;
    else
        x-&gt;parent-&gt;right = y;
    y-&gt;left   = x;            // put x on y&#39;s left
    x-&gt;parent = y;
}

void rotateRight(Node** T, Node* y)
{
    Node *x  = y-&gt;left;     // set x
    y-&gt;left  = x-&gt;right;    // turn x&#39;s right subtree into y&#39;s left subtree{
    if (x-&gt;right != T_Nil)
        x-&gt;right-&gt;parent = y;
    x-&gt;parent = y-&gt;parent;  // link y&#39;s parent to x
    if (y-&gt;parent == T_Nil)
        *T = x;
    else if (y == y-&gt;parent-&gt;right)
        y-&gt;parent-&gt;right = x;
    else
        y-&gt;parent-&gt;left  = x;
    x-&gt;right  = y;         // put y on x&#39;s right
    y-&gt;parent = x;
}

void redBlackInsertFixup(Node** Root, Node* New)
{
    Node* temp;
    while (New-&gt;parent-&gt;color == RED)
    {
        if (New-&gt;parent == New-&gt;parent-&gt;parent-&gt;left)
        {
            temp = New-&gt;parent-&gt;parent-&gt;right;
            if (temp-&gt;color == RED)
            {
                temp-&gt;color = BLACK;
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                New = New-&gt;parent-&gt;parent;
            }
            else {
                if (New == New-&gt;parent-&gt;right)
                {
                    New = New-&gt;parent;
                    rotateLeft(Root, New);
                }
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                rotateRight(Root, New-&gt;parent-&gt;parent);
            }
        }
        else
        {
            temp = New-&gt;parent-&gt;parent-&gt;left;
            if (temp-&gt;color == RED)
            {
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                New = New-&gt;parent-&gt;parent;
            }
            else {
                if (New == New-&gt;parent-&gt;left)
                {
                    New = New-&gt;parent;
                    rotateRight(Root, New);
                }
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                rotateLeft(Root, New-&gt;parent-&gt;parent);
            }
        }
    }
    Root[0]-&gt;color = BLACK;
}
void redBlackInsert(Node** T, int key)
{
    Node* z =  newNode(key);
    Node* y =  T_Nil;
    Node* x = *T;

    // Find where to Insert new node Z into the binary search tree
    while (x != T_Nil) {
        y = x;
        if (z-&gt;key &lt; x-&gt;key)
            x = x-&gt;left;
        else
            x = x-&gt;right;
    }

    z-&gt;parent = y;
    if (y == T_Nil)
        *T = z;
    else if (z-&gt;key &lt; y-&gt;key)
        y-&gt;left  = z;
    else
        y-&gt;right = z;

    // Init z as a red leaf
    z-&gt;left  = T_Nil;
    z-&gt;right = T_Nil;
    z-&gt;color = RED;

    // Ensure the Red-Black property is maintained
    redBlackInsertFixup(T, z);
}

#define MAX(a,b) (((a)&gt;(b))?(a):(b))

int height(Node* Root)
{
    int h = 0;

    if (Root != NULL) {
        if (Root == T_Nil)
            h = 1;
        else
        {
            int leftHeight  = height(Root-&gt;left);
            int rightHeight = height(Root-&gt;right);
            h = MAX(leftHeight, rightHeight) &#43; 1;
        }
    }

    return h;
}

void printTree(Node* root)
{
    if (root-&gt;left != T_Nil)
        printTree(root-&gt;left);
    printf(&#34;%d &#34;, root-&gt;key);
    if (root-&gt;right != T_Nil)
        printTree(root-&gt;right);
}

void padding ( char ch, int n ){
  int i;
  
  for ( i = 0; i &lt; n; i&#43;&#43; )
    putchar ( ch );
}

void structure ( struct Node *root, int level ){
  int i;
  
  if ( root == NULL ) {
    padding ( &#39;\t&#39;, level );
    puts ( &#34;~&#34; );
  } else {
    structure ( root-&gt;right, level &#43; 1 );
    padding ( &#39;\t&#39;, level );
    if(root-&gt;color) {
        printf ( &#34;%d,%c\n&#34;, root-&gt;key, root-&gt;color );
    } else {
        printf ( &#34;%d,N\n&#34;, root-&gt;key );
    }
    structure ( root-&gt;left, level &#43; 1 );
  }
}

int main(int argc, char* argv[])
{
    Node* Root = T_Nil;
    int list[14] = { 34, 84, 15, 0, 2, 99, 79, 9, 88, 89, 18, 31, 39 };
    //int list[10] = {1, 3, 2, 5, 4, 7, 6, 9, 8, 10};
    for (int i = 0; i &lt; 14; i&#43;&#43;)
    {
        //printf(&#34;%d &#34;, list[i]);
        redBlackInsert(&amp;Root, list[i]);
        printf(&#34;Inserindo %d\n&#34;, list[i]);
        structure(Root, 0);
    }
    printTree(Root);

    structure(Root, 0);
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que muda numa árvore binária normal e numa Red Black? Pra começar o Node da árvore vai conter um campo extra de um bit, que pode ser zero ou um. Tanto faz se você chama zero de Red e um de Black ou vice versa, mas uma regra inviolável é que o Node raiz é sempre preto.</p>
<p>Por padrão, todo Node novo que formos criando vai começar sendo da cor vermelha. Mas depois de inserido na árvore precisamos checar a cor do pai e dos filhos, porque a regra é que um Node vermelho só pode ter pais e filhos pretos. Se depois de inserir não estiver assim, precisamos repintar os Nodes pra encaixar na regra. Parece estranho mas você vai entender.</p>
<p>Além disso, todo Node no fim da árvore, que você pode chamar de folhas ou leafs, e terminaria com os ponteiros left e right sendo nulo, vão apontar pra um Node de serviço que muitos nomeariam como Null Node ou TNil. Você pode chamar como quiser, mas é um Node que serve só pros ponteiros left e right dos últimos Nodes não serem nulo e seguir a regra das cores. Como é tudo ponteiro, todo mundo no final vai só apontar pra esse Node mas no diagrama, pra ficar mais bonito, vai parecer que cada um tá apontando pra um Node extra. Só lembrem que é tudo o mesmo Node TNil no final.</p>
<p>Uma das razões de ter esses Nodes no final é a regra que se contarmos todos os nodes entre a raíz e esse Nulo deve sempre ter o mesmo número de Node pretos. Na prática isso só vai acontecer se a árvore tiver a altura balanceada. Pra entender isso de balanço considere nossa árvore binária de exemplo. Conseguem ver como alguns galhos pendem mais pra um lado só? Os nodes 0 ou 15 pendem tudo pra direita. Do node 84 pende tudo pra esquerda.</p>
<p>Pra árvore ficar balanceada, precisamos fazer uma manutenção, movendo ponteiros de lugar durante a inserção. Lembra aquela ideia de fazer swap de ponteiro que eu falei no episódio anterior? É parecido aqui. Swaps ou trocas, são operações rápidas. Só muda uns dois ou três ponteiros de lugar, independente da quantidade total de Nodes, então é uma operação de complexidade O 1, constante.</p>
<p>Essa operação em árvores não é exatamente um swap, mas sim uma rotação. E isso tudo dito, vamos dar um exemplo visual com a mesma lista de números do exemplo, mas agora inserindo com o algoritmo de inserção de uma árvore Red Black. O 34 é a raíz, por isso ela é preta por padrão e seus ponteiros de left e right vão apontar praquele Node Nulo TNil. O próximo número é 84, que por padrão entra vermelho e igual na árvore binária vai pra direita porque é maior que 34. O próximo número 15 é menor que 34 então vai pra esquerda e também é vermelho por padrão.</p>
<p>Até aqui tudo bem, mas agora vamos inserir o número 0. Pelas mesmas regras, é menor que 34, menor que 15 então vai indo pra esquerda. E inserimos o Node zero vermelho. Mas a regra é que um node vermelho não pode ter um pai vermelho como o Node 15 em cima dele. Aqui começa o trabalho de zeladoria recolorindo o Node 15 e o Node 84 pra preto. Agora a regra foi satisfeita.</p>
<p>Sobre o caminho mais curto que falei, olha só. Quantos nodes pretos tem entre o 34 e o Nil embaixo do 0? 34 é preto, 1. 15 é preto, 2. 0 é vermelho, continua 2, e nil é preto, 3. Conseguem ver que qualquer outro caminho continua sendo 3 nodes pretos? Então as regras continuam satisfeitas. Vamos continuar tentando inserir o número 2.</p>
<p>E aqui a coisa ficou diferente. Olha o que aconteceu. Tentamos inserir o número 2. Pela regra da árvore binária, é menor que 34 então vai pra esquerda. É maior que zero então vai pra direita. Agora a árvore ficou desbalanceada. Por que? Pra começar temos dois Node vermelhos um atrás do outro. E o irmão do Node 0, que é o ponteiro pra direita do Node 15 tá vazio. Isso não pode acontecer, a ideia é que os nodes mais pra cima tenham left e right preenchidos, evitando ponteiros nulos antes de chegar nas folhas. Precisamos fazer a tal rotação.</p>
<p>Primeiro rotacionamos pra esquerda, subimos o Node 2 na árvore e descemos o Node 0 pra esquerda. Depois rotacionamos pra direita. Subimos o Node 2 pro mesmo andar da 15 e descemos o 15 pra direita. Finalmente repintamos o Node 2 e o Node 15 e pronto, voltamos a cumprir todas as regras do jogo. Nenhum Node vermelho tem pai ou filhos vermelhos e todos os caminhos do Node 34 até os nulos continua tendo o mesmo número de Node pretos, no caso 3. Vamos continuar.</p>
<p>Os números noventa e nove, setenta e nove e oitenta e oito vão sendo inseridos do mesmo jeito que na árvore binária. Mas agora vamos ver o 89. Mesma coisa, é maior que 34, vai pra direita. É maior que 84 vai pra direita. É menor que 89, vai pra esquerda e, sendo maior que 88, vai pra sua direita. Alerta! Temos dois nodes vermelhos um atrás do outro e o irmão do 88, ou seja o ponteiro pra direita do 89 tá vazio. Hora de rotacionar.</p>
<p>Começa rotacionando pra esquerda, daí o 89 passa a ser o pai de 88 e filho de 99. Rotacionamos pra direita e 99 passa a ser filho de 89 e irmão do 88. Vamos repintar o 88 e 89 e agora estamos cumprindo todas as regras de novo. Conseguem ver que se compararmos com a árvore binária de antes, agora parece visualmente mais balanceada? Ou seja, todos os Nodes antes do andar mais de baixo tem ambos os ponteiros left e right preenchidos, sem buracos sobrando.</p>
<p>Vamos continuar os outros números até o fim da lista sem parar mais. Continuamos com dezoito, trinta e um e trinta e nove. Daí escondemos os Nodes nulos no final pra ficar mais fácil de comparar. Olhem como a árvore Red Black e a árvore binária ficaram com um layout bem diferente. É assim que uma árvore binária balanceada se parece usando o método de Red Black.</p>
<p>Em termos de espaço extra sendo usado, é barato. Só um bit a mais pra cada Node, fora os ponteiros pro Node nulo no final de cada folha. E mesmo esse espaço tem como economizar e vou falar disso já já. O tempo de inserção é definitivamente mais caro, mas ainda assim na ordem de grandeza de O logaritmico.</p>
<p>Ou seja, em comparação com a árvore de procura binária, tá um pouco mais lento, mas a ordem de grandeza de complexidade não ficou muito mais caro. Procurar por itens nessa árvore é a mesma coisa da árvore de procura binária. Uma busca binária. E pra apagar vai ser um pouco mais lento também. Assim como na inserção precisa manter a árvore balanceada, então toda vez precisa checar se precisa rotacionar e recolorir.</p>
<p>Como eu prometi, ainda temos árvores AVL pra comentar, que chama assim por causa dos autores Adelson-Velsky e EM Landis, de 1962. Em resumo, diferente do sistema de bit de cor do Red Black, cada Node num AVL tem um número de balanço que pode ser menos um, zero ou um, pra árvore ser balanceada. Se for mais que menos dois ou dois, a árvore tá desbalanceada e precisa rotacionar e recalcular o balanço.</p>
<p>Embora ambos AVL e Red Black tenham operações com complexidade na ordem de grandeza O logaritmico, a procura numa árvore AVL é um pouco mais rápida, porque a Red Black ainda pode acabar menos balanceada que a AVL. A AVL é mais agressiva em manter a árvore estritamente balanceada. A inserção da AVL acaba sendo um pouco mais cara que a Red Black pra atingir isso. É sempre um trade-off. Se você fizer mais inserções do que procuras, um Red Black é melhor. Se o tempo de inserção não for importante, mas pra você o tempo de procura for muito importante, então talvez AVLs sejam melhores.</p>
<p>Mais do que isso, diferente dos Nodes de Red Black que são só 1 bit, os nodes de AVL precisam de dois bits extras, então vai ocupar um pouco mais espaço na memória. Além disso, os Blacks tem outra vantagem dependendo da implementação. Lembram quando falei sobre números inteiros com sinal e sem sinal? Se a gente souber que os números que vão ser armazenados na árvore sempre é positivo, podemos usar o primeiro bit, que normalmente seria pro sinal pra ser a cor do Node, daí economizamos esse bit a mais na estrutura de Node.</p>
<p>E só pra piorar o caso do AVL, eu disse antes que as rotações de Red Black são simples, complexidade O 1 constante. Mas as inserções de AVL podem ter mais rotações mais complicadas, daí o tempo varia mais. Por tudo isso, na dúvida, a escolha padrão acaba sendo Red Black, e AVL em casos especiais que você sabe que precisa das propriedades específicas dele.</p>
<p>Vou deixar linkado na descrição abaixo um documento do site da kernel do Linux que explica mais sobre como árvores Red Black são usados no Kernel. Parafraseando, os schedulers de I/O deadline e CFQ usam rbtrees pra rastrear requisições. RBTree você lembra que são Red Black Trees. O pacote de CD e DVD faz o mesmo. O código de timer de alta resolução usa rbtrees. O filesystem ext3 rastreia diretórios em uma rbtree. VMAs ou áreas de memória virtual usam rbtrees.</p>
<p>Seu Node.js e todo servidor com I/O assíncrono, por baixo usa o epoll do Linux. Rastreamento de descritores de arquivos, chaves criptográficas e scheduler de pacotes de rede usam rbtrees. Só pra comparar, lembram nosso código de exemplo de procura numa árvore binária? Vamos ver como essa documentação do site da kernel sugere fazer. E olha só, é bem parecido né? As mesmas comparações pra esquerda, pra direita ou quando encontra o Node. Meu exemplo foi rudimentar mas a estrutura básica é a mesma nos casos mais complexos.</p>
<p>Não sei porque as linguagens mais populares interpretadas não tem árvores Red Black por padrão. Eu diria que elas deveriam, porque assim como outras estruturas de dados, seriam melhor implementadas em C ou C++ do que em alto nível como Javascript ou Python. A classe <code>std::map</code> do C++ implementa árvores Red Black, assim como as classes TreeSet ou TreeMap do Java. Se for uma linguagem compilada como Go ou Rust, você pode baixar do GitHub alguma uma implementação que funciona em boa velocidade porque são compiladas. Mas tirando Java e C++ não sei de nenhuma outra que já vem embutida. Se alguém souber mande nos comentários.</p>
<p>Mas lembrem-se, árvores Red Black e AVL tem como objetivo chegar numa árvore de procura binária, como no nosso exemplo anterior, mas balanceadas. Os algoritmos de inserção que usam o sistema de cores ou balanço e rotação servem pra nos dar uma árvore balanceada que vai oferecer o menor tempo médio de procura binária. Mas o que vimos até agora continua sendo uma árvore de procura binária, com Nodes que só tem um valor e dois ponteiros, um pra esquerda e outro pra direita.</p>
<p>Depois disso ainda existe a B-Tree que é uma estrutura de árvore que também é auto-balanceada, ou seja, durante a inserção e remoção de Nodes ele faz a zeladoria de se rebalancear. Mas em vez de Nodes que só tem dois filhos, num B-Tree os Nodes podem ser X filhos maiores que 2. E mais coisas que não vou detalhar aqui. A definição vem do livro do Knuth e todo mundo usa a mesma, então pesquisem sobre B-Trees como lição de casa. Visualmente é algo como nessa imagem do lado. Ainda uma árvore, idealmente balanceada, mas com mais filhos por Nodes.</p>
<p>E indo mais longe chegamos na B PLUS Trees, que você pode pensar como B-Trees mas onde os Nodes não contém os valores em si. Em vez disso, aponta pra folhas, e esse nodes folha é que vão ter os valores que você quer armazenar. Os Nodes pais vão ter só as chaves que identificam esses valores. E é essa árvore que você vai ver em tudo que usa armazenamento orientado a blocos.</p>
<p>Eu tô pensando em fazer um episódio falando sobre blocos de armazenamento. É por isso que se você já brincou com AWS ou Google Cloud ou Azure, já deve ter visto o termo “block storage” pra identificar HDs virtuais. Por agora só entenda que é isso que faz B+ Trees serem ideais pra coisas como filesystems e bancos de dados. Tudo que você imaginar que armazena dados em disco provavelmente é uma B+ Tree ou derivação. Exemplo. Os filesystem de Linux como ReiserFS, EXT4. O NTFS do Windows. O APFS dos Macs. A maioria dos bancos de dados relacionais como DB2, SQL Server, Oracle, SQLite e por aí vai. É tudo B+ Trees.</p>
<p>Falando em bancos de dados. Pra entender como os dados são armazenados em disco, você precisa entender B+ Trees. Se você já usou qualquer MySQL ou Postgres da vida vai lembrar que tem a boa prática de sempre criar índices pra melhorar o tempo das suas pesquisas. E se você leu stackoverflow o suficiente também vai lembrar que povo fala pra não sair criando índice pra tudo que é lado, como se não houvesse amanhã, senão vai acabar com a performance. E aí você fica sem saber o que fazer, cria ou não cria índices então?</p>
<p>Se você entendeu a ideia de pegar uma lista e colocar numa árvore de procura binária balanceada e a idéia de hashing da hashtable do episódio anterior, meio que já entendeu uma parte da charada. O que são registros de um banco? Pense como se fosse uma struct de C, como as que usamos em todos os exemplos até agora. Agora pense em gravar os bits de instâncias de structs no disco em vez da memória. Uma tabela de banco de dados grande pode ter milhares ou milhões de registros. Seria bem lento sair procurando registro a registro como numa lista ligada. Mesmo se for uma árvore.</p>
<p>Diferente de ler da memória, pense que num disco mecânico você precisa sair caçando bits em trilhas num disco. E ficar caçando endereços assim ia ser bem demorado. Mas se não tem nenhum índice, é isso que vai acontecer mesmo. Pelo menos fazendo uma busca binária, você vai eliminando uma boa parte de registros que não precisa vasculhar, mas em milhões de registros, mesmo com busca binária vai ser devagar. É o que muitos já devem ter visto nos seus bancos de dados favoritos quanto a pesquisa cai num table scan.</p>
<p>Quando você procura por chave primária ou por um campo indexado é bem mais rápido. Pense num índice como uma segunda árvore, uma B-tree que é a tal árvore onde cada Node pode ter vários filhos, e é auto-balanceada, ou seja, inserções e deleções re-balanceiam a árvore via operações como rotação. Ela fica mais compacta porque é como se fosse uma cópia da sua tabela só que com os campos indexados.</p>
<p>Imagina num exemplo simples de árvore de procura binária, como esse na imagem aqui do lado. Se eu fizer uma pesquisa pra achar todos os salários maiores que 10 mil, depois de eliminar a metade esquerda da árvore. Agora temos os IDs ou endereços de onde esses registros estão na outra árvore no disco, e podemos buscar só elas.</p>
<p>E porque não é bom criar um montão de índices? Pense em inserir uma tabela como inserir numa árvore Red Black. Pra manter a árvore balanceada, pra acelerar pesquisas, você precisa repintar e rotacionar toda hora. Cada índice da sua tabela é como se fosse uma árvore dessas. Quanto mais índices tiver, mais caro fica inserir em várias delas, porque você precisa inserir na tabela propriamente dita e depois atualizar cada um dos índices que você criou. Índices são bons pra acelerar as pesquisas, mas o custo é aumentar o preço de operações de inserts, updates e deletes.</p>
<p>De qualquer forma o objetivo de hoje não é detalhar como bancos de dados funcionam, mesmo porque eu mesmo não sei tudo de cabeça Mas só de saber que seu sistema de arquivos, seu banco de dados usam uma implementação de B+ Trees e que os índices que tornam suas pesquisas mais rápidas e suas inserções mais lentas, provavelmente é uma implementação de B-Trees, já vai ajudar você a procurar mais material pra estudar.</p>
<p>Uma última coisa é não confundir B-Trees com Binary Trees ou árvores binárias. Esse B é de Balanced Trees, árvores balanceadas. E balancear árvores tem um custo, as tais operações de rotação por exemplo. Mas no fim do dia, embora você pense visualmente numa árvore como uma hierarquia, na verdade é uma lista ordenada, como um array ou uma lista ligada, mas estruturada de forma diferente. De forma não linear.</p>
<p>A complexidade de cada tipo de algoritmo em cada tipo de estrutura de dados tem complexidades diferentes de processamento e uso de recursos. Alguns são rápidos mas consomem mais memória. Outros são mais devagar na inserção mas mais rápidos na procura. Casos excepcionais podem fazer um algoritmo O logaritmico cair pra O linear ou até mesmo O loglinear.</p>
<p>Outras coisas que você deve prestar atenção. Se você partir do zero, de C, sozinho, é possível que compreenda um pouco sobre as complexidades de manipular e procurar elementos num array. Com um pouco de prática, vai naturalmente chegar numa lista ligada. Acho difícil chegar num Quicksort sozinho, mas se por acaso esbarrar em algum texto sobre particionamento e logaritmos, talvez chegue em algo parecido pra ordenação e busca binária. John Von Neumann chegou no mergesort.</p>
<p>Se você se esforçar bastante, talvez consiga evoluir da lista ligada e array pra hashtable e árvore de procura binária desbalanceada que vimos hoje. Mas eu acho muito improvável você sozinho, sem a ajuda de nenhum livro, chegar na idéia de árvore balanceada e mecanismos como Red Black, AVL ou mesmo B-Trees. Pra isso você precisa de mais literatura. Especialmente porque conceitos como colorização e rotação não são intuitivos, mesmo se você já tiver a idéia de swap de variáveis na cabeça.</p>
<p>A ideia de colorização não é usada só em árvores Red Black. Um dos mecanismos de Garbage Collectors se chama TriColor Marking, onde a fase de marcação de objetos categoriza quem pode ou quem não pode ser descartado usando um sistema de três cores, preto, branco e cinza. Se você entendeu a ideia do vermelho e preto, já tem uma noção de TriColor marking. Talvez eu fale sobre isso se for explicar como funcionam os garbage collectors em alto nível , mas por hoje fica a dica.</p>
<p>Eu espero que pelo menos tenha conseguido fazer vocês que são iniciantes enxergarem um pouco mais de como as coisas funcionam por baixo do seu Javascript e Python. Mesmo que não usem diretamente no trabalho, reservem um tempinho pra estudar o básico de C. Especialmente porque se vocês se empolgarem, C e C++ vão ser úteis caso um dia queiram usar uma biblioteca em C dentro do Javascript. E sim, isso é possível e é como muitas bibliotecas são feitas de verdade.</p>
<p>O nosso linguição de bits sozinho é só isso, bits um atrás do outro. Mas quando definimos estruturas de dados, começamos a dar forma a pedaços desses bits. Delimitamos essas estruturas pelo tamanho de cada primitiva, como inteiros de 32 ou 64 bits, e os endereços em que elas se localizam no linguição. Os bits sozinhos não significam nada. Só quando nós programadores definimos o que cada pedaço é que ela começa a ter sentido. Por exemplo, uma sequência de inteiros um atrás do outro pode ser um array só de números mesmo ou de letras, formando um String, quem decide qual é qual sou eu, programador.</p>
<p>Um pacote de bits que tem codificado um número que é outra posição no linguição, é um Node. E esses Nodes podem ser organizados como listas ligadas, hashtables ou árvores. Daí vem o problema de navegar por esses pacotinhos de bits. O jeito simples é sequencialmente como num array, mas logo a gente vê que isso é limitado. Daí saímos pulando de endereço pra endereço de Nodes. Mas isso é lento e complexo. Aí temos que inventar formas de minimizar esse trabalho e chegamos em algoritmos como de ordenação e procura.</p>
<p>E à medida que evoluímos o conceito, uma árvore desbalanceada se torna balanceada. Uma procura que levava tempo linear vira logaritmico. E uma estrutura que era simples, vai ficando mais sofisticada. Saímos de heaps de memória até índices de bancos de dados com a mesma categoria de estruturas e algoritmos. O importante é: mesmo se você treinasse por anos a fio, sem estudar a fundação, nunca vai chegar no que vimos nesses últimos 3 episódios sozinho. E ia continuar fazendo errado sem saber porque. “Meu programa tá lento, deve ser meu CPU que é velho”. E esquece que anos atrás se fazia programas que rodam mais rápido que o seu em CPU ainda mais velho. Então o problema não é o hardware não ser o suficiente, mas sim você não saber usar o máximo que ele pode oferecer e se limitar a se conformar com “ah, pelo menos roda”.</p>
<p>Tudo que falei até aqui é o básico. Cada assunto que falei tem bem mais detalhes por trás. E mesmo quando você tiver estudado tudo, ainda assim não vai fazer programas bons. Mas ao longo do seu treino e estudos, as peças vão começar a se encaixar mais rápido e você vai começar a dar saltos maiores do que quem começou junto com você mas preferiu pular essa teoria toda. Peguem o livro do Cormen, Tanenbaum e estudem. É chato pra caramba, mas quanto mais cedo fizer isso, melhor vai ser depois.</p>
<p>E com isso, finalmente cheguei no final deste assunto. Como eu disse no começo, assistam esta série múltiplas vezes. Se você é iniciante, não vai entender vendo só uma vez e nem tem tudo aquia. Precisa pesquisar mais sobre os assuntos que falei, fazer você mesmo esses códigos como exercício e só assim começar a apreciar como as coisas funcionam. Se acharem algum erro no video não deixem de mandar nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e cliquem no sininho pra não perder os próximos episódios. Compartilhem o video com seus amigos pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>ciencia-da-computacao</category><category>bancos-de-dados</category><category>akitando</category></item><item><title>[Akitando] #49 - Devo usar NOSQL? | "ENDGAME" para Iniciantes em Programação | Série "Começando aos 40"</title><link>https://akitaonrails.github.io/2019/04/24/akitando-49-devo-usar-nosql-endgame-para-iniciantes-em-programacao-serie-comecando-aos-40/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/04/24/akitando-49-devo-usar-nosql-endgame-para-iniciantes-em-programacao-serie-comecando-aos-40/</guid><pubDate>Wed, 24 Apr 2019 17:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/EdOkYEE1J_Y"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente chegamos ao décimo-quarto e último episódio da série &amp;ldquo;Começando aos 40&amp;rdquo;. E eu quis coincidir o término da série com o dia da pré-estreia de Avengers: Endgame, porque eu sou geek assim mesmo :-)&lt;/p&gt;
&lt;p&gt;No episódio de hoje vou dar um pequeno overview sobre o que eu considero como alguns dos principais aspectos de bancos de dados relacionais, NoSQL, e as melhores escolhas pra sua aplicação.&lt;/p&gt;</description><content:encoded><![CDATA[

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

<p>Finalmente chegamos ao décimo-quarto e último episódio da série &ldquo;Começando aos 40&rdquo;. E eu quis coincidir o término da série com o dia da pré-estreia de Avengers: Endgame, porque eu sou geek assim mesmo :-)</p>
<p>No episódio de hoje vou dar um pequeno overview sobre o que eu considero como alguns dos principais aspectos de bancos de dados relacionais, NoSQL, e as melhores escolhas pra sua aplicação.</p>
<p>Vou aproveitar pra complementar a discussão de máquinas virtuais, containers, IaaS, PaaS adicionando minhas opiniões sobre FaaS ou Function as a Service.</p>
<p>E no final quero deixar um pensamento sobre esse mundo cloud e o impacto quanto ao mundo de código livre pra todos pensarem.</p>
<p>Se você ainda não assistiu todos os episódios, não esqueçam que tem a playlist com todos os episódios pra maratonar: <a href="https://www.youtube.com/watch?v=O76ZfAIEukE&amp;list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki"target="_blank" rel="noopener">https://www.youtube.com/watch?v=O76ZfAIEukE&list=PLdsnXVqbHDUc7htGFobbZoNen3r_wm3ki</a></p>
<p>Desta vez quero tirar uma folga de uma ou duas semanas antes de retornar então é o melhor momento pra ver todos os vídeos do canal que ainda não assistiu.</p>
<p>Ajudem o canal compartilhando pra todo mundo!</p>
<p>Pular direto pra FaaS: 20:56</p>
<p>Pular direto pra Conclusão: 26:46</p>
<p>Links:</p>
<ul>
<li>
<p>Was MongoDB Ever the Right Choice? (<a href="https://www.simplethread.com/was-mongodb-ever-the-right-choice/"target="_blank" rel="noopener">https://www.simplethread.com/was-mongodb-ever-the-right-choice/</a>)</p>
</li>
<li>
<p>Redis Labs changes its open-source license - again (<a href="https://techcrunch.com/2019/02/21/redis-labs-changes-its-open-source-license-again/"target="_blank" rel="noopener">https://techcrunch.com/2019/02/21/redis-labs-changes-its-open-source-license-again/</a>)</p>
</li>
<li>
<p>NoSQL Databases: An Overview (<a href="https://www.thoughtworks.com/insights/blog/nosql-databases-overview"target="_blank" rel="noopener">https://www.thoughtworks.com/insights/blog/nosql-databases-overview</a>)</p>
</li>
<li>
<p>A SHORT HISTORY OF DATABASES: FROM RDBMS TO NOSQL &amp; BEYOND (<a href="https://www.3pillarglobal.com/insights/short-history-databases-rdbms-nosql-beyond"target="_blank" rel="noopener">https://www.3pillarglobal.com/insights/short-history-databases-rdbms-nosql-beyond</a>)</p>
</li>
<li>
<p>Redis has a license to kill: Open-source database maker takes some code proprietary (<a href="https://www.theregister.co.uk/2018/08/23/redis_database_license_change/"target="_blank" rel="noopener">https://www.theregister.co.uk/2018/08/23/redis_database_license_change/</a>)</p>
</li>
<li>
<p>MySQL Performance: MyISAM vs InnoDB (<a href="https://www.liquidweb.com/kb/mysql-performance-myisam-vs-innodb/"target="_blank" rel="noopener">https://www.liquidweb.com/kb/mysql-performance-myisam-vs-innodb/</a>)</p>
</li>
<li>
<p>Why Amazon DynamoDB isn’t for everyone (<a href="https://read.acloud.guru/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you-aefc52ea9476"target="_blank" rel="noopener">https://read.acloud.guru/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you-aefc52ea9476</a>)</p>
</li>
<li>
<p>SQLite vs MySQL vs PostgreSQL: A Comparison Of Relational Database Management Systems (<a href="https://www.digitalocean.com/community/tutorials/sqlite-vs-mysql-vs-postgresql-a-comparison-of-relational-database-management-systems"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/sqlite-vs-mysql-vs-postgresql-a-comparison-of-relational-database-management-systems</a>)</p>
</li>
<li>
<p>When Should I Use Amazon Aurora and When Should I use RDS MySQL? (<a href="https://www.percona.com/blog/2018/07/17/when-should-i-use-amazon-aurora-and-when-should-i-use-rds-mysql/"target="_blank" rel="noopener">https://www.percona.com/blog/2018/07/17/when-should-i-use-amazon-aurora-and-when-should-i-use-rds-mysql/</a>)</p>
</li>
<li>
<p>Why Uber Engineering Switched from Postgres to MySQL (<a href="https://eng.uber.com/mysql-migration/"target="_blank" rel="noopener">https://eng.uber.com/mysql-migration/</a>)</p>
</li>
<li>
<p>MySQL Might Be Right for Uber, but Not for You (<a href="https://dzone.com/articles/on-ubers-choice-of-databases"target="_blank" rel="noopener">https://dzone.com/articles/on-ubers-choice-of-databases</a>)</p>
</li>
<li>
<p>Showdown: MySQL 8 vs PostgreSQL 10 (<a href="http://blog.dumper.io/showdown-mysql-8-vs-postgresql-10/"target="_blank" rel="noopener">http://blog.dumper.io/showdown-mysql-8-vs-postgresql-10/</a>)</p>
</li>
<li>
<p>Apache Cassandra Architecture Fundamentals (<a href="https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/"target="_blank" rel="noopener">https://blog.yugabyte.com/apache-cassandra-architecture-how-it-works-lightweight-transactions/</a>)</p>
</li>
<li>
<p>Highly available Redis Architecture (<a href="https://medium.com/kokster/highly-available-redis-architecture-613c89f887b4"target="_blank" rel="noopener">https://medium.com/kokster/highly-available-redis-architecture-613c89f887b4</a>)</p>
</li>
<li>
<p>Paper Review: Dynamo: Amazon’s Highly Available Key-value Store (<a href="https://sookocheff.com/post/databases/dynamo/"target="_blank" rel="noopener">https://sookocheff.com/post/databases/dynamo/</a>)</p>
</li>
<li>
<p>Using the HTC G1, 10 years later: 2008&rsquo;s smartphone is effectively a dumbphone in 2018 (<a href="https://www.androidpolice.com/2018/12/24/using-htc-g1-10-years-later-2008s-smartphone-effectively-dumbphone-2018/"target="_blank" rel="noopener">https://www.androidpolice.com/2018/12/24/using-htc-g1-10-years-later-2008s-smartphone-effectively-dumbphone-2018/</a>)</p>
</li>
</ul>
<p>=== Script</p>
<p>Olá, Fabio Akita</p>
<p>Este é o terceiro episódio do tema de devops pra encerrarmos o assunto e ao mesmo tempo vamos finalmente encerrar a série &ldquo;Começando aos 40&rdquo; neste décimo quarto episódio.</p>
<p>No episódio da semana passada eu disse que iria explicar sobre bancos de dados. Eu tentei escrever esse script diversas vezes de diversas formas nos últimos dias, mas é um assunto muito mais complexo do que caberia num único episódio. Eu não vou entrar em detalhes de como eles são implementados, os algoritmos e arquitetura. Eu queria falar sobre clustered indexes, bloom filters, write ahead logs, mvcc, consistent hashing e muito mais. Em vez disso quero só compartilhar algumas opiniões que talvez nem todos concordem, mas que deve ser o mais útil pra maioria das pessoas.</p>
<p>Então quero aproveitar pra falar muito rapidamente de uma coisa que não mencionei no episódio anterior quando falei sobre máquinas virtuais e containers, o tal de Function as a Service ou FaaS que muitos ficam hypando hoje em dia. Finalmente vou fechar com um pensamento de como tudo isso que eu expliquei nos últimos episódios afeta o mundo de código livre. Hoje é dia do Endgame, fiquem comigo!</p>
<p>(&hellip;)</p>
<p>Como primeiro assunto, vamos falar rapidamente sobre bancos de dados e arquitetura. Se você estiver mais interessado em ir direto pra parte de Function as a Service vá pra esse tempo indicado aqui embaixo. E se quiser ir direto pra conclusão, vá pra este outro tempo aqui embaixo.</p>
<p>Se você é iniciante eu não recomendo tentar instalar e configurar manualmente nenhum banco de dados sozinho. Nem os bancos relacionais como MySQL ou Postgres e nem os NoSQL como Redis, MongoDB ou Cassandra. Não adianta só seguir um tutorial e fazer rodar. Garantir que você não vai perder dados importantes é muito mais do que meramente fazer um servidor rodar. Seguir um tutorial é absolutamente trivial.</p>
<p>Todos os bancos tem dezenas de configurações diferentes que devem ser planejadas exclusivamente pro uso da aplicação. Não existe uma única configuração que serve universalmente pra tudo. E não, NoSQLs não são mais fáceis de configurar ou dar manutenção do que os bancos relacionais. Se você tem essa noção de algum lugar, tire isso da sua cabeça. Eu diria que uma infraestrutura NoSQL de verdade dá tanto trabalho ou mais do que bancos relacionais. Especialmente se realmente formos eliminar pontos únicos de falha e tivermos requerimento de estratégias de recuperação de desastres com zero downtime, ou seja, o banco sempre estar disponível mesmo que um dos servidores do cluster sofra algum problema.</p>
<p>Se você for programador aprenda SQL e o modelo relacional corretamente. Você precisa saber como fazer queries decentes. A pior parte de uma aplicação é quando o desenvolvedor acredita que a biblioteca de mapeamento objeto-relacional que ele está usando, seja Hibernate no Java, SQLAlchemy no Python, ActiveRecord no Ruby, Eloquent ORM no PHP, nenhum deles vai fazer queries eficientes pra você. E queries ineficientes é um dos principais ofensores em desperdício de memória e performance. Não adianta trocar de banco se seu código é uma porcaria, nenhum banco vai fazer mágica.</p>
<p>Muita gente acha que usar um MongoDB é bala de prata pra combinar facilidade de uso e performance, especialmente pra iniciantes. Se você não assistiu meu episódio sobre o livro The Mythical Man Month, aqui vai um resumo: não existe bala de prata!</p>
<p>Pra começar, se você subir uma instância de MongoDB e deixar ele sozinho, você já começou errado. MongoDB não tem garantias de 100% de durabilidade. O melhor caso de uso do Mongo é que o dataset caiba inteiro em RAM. Todas as operações são feitas em RAM, é o que garante sua performance. Mas isso significa que se a máquina der pane antes da RAM ter chance de dar flush pro disco, você vai perder dados. Você pode configurar pra que ele garanta mais durabilidade. Por exemplo, hoje quando você manda gravar alguma coisa no Mongo ele devolve ok antes de ter realmente gravado. O ok do Mongo é mais tipo &ldquo;bele, recebi a ordem pra gravar, quando der eu gravo&rdquo;. Diferente de um banco relacional que só dá ok se garantidamente o dado foi gravado. Você pode configurar o Mongo pra fazer o mesmo, devolver ok só se garantidamente uma outra instância de replica master recebeu o dado. Mas aí você perde a performance que era justamente o que te fez escolher Mongo em primeiro lugar.</p>
<p>Aliás, uma instalação mínima de Mongo precisa de pelo menos duas instâncias em replica set. Eu disse que o sweet spot é o dataset caber em RAM, mas você não tem RAM infinita, se você pretende armazenar ordens de grandeza mais dados do que cabe em RAM, talvez precise fazer shards. Mas configuração de shard precisa ser planejada do começo. Mudar de uma configuração sem shard pra ter shard não é só ligar ou desligar uma chave, você vai ter que refazer toda a infraestrutura do zero e mover todos os dados de lugar se escolher errado. Some replicação e sharding pra configurar e você criou um emaranhado de gato pra resolver. Portanto não, um MongoDB não é mais simples do que qualquer outro banco se você realmente vai usar da forma certa. Instalar na sua máquina só pra brincar, qualquer banco é simples, não só o Mongo.</p>
<p>Um NoSQL wide column store como Cassandra, apesar de ter similaridades com bancos relacionais só na superfície, especialmente nos schemas e no CQL que é similar a SQL, não tem absolutamente nada a ver. Eles são como key-value stores multi dimensionais com column families e foram feitos pra serem bancos altamente distribuídos, com múltiplos nós num cluster, de preferência em múltiplas regiões diferentes. Cassandra não foi feito pra usar numa única instância, como banco de um blog. O sweet spot dele é funcionar em cluster, com múltiplos nós, e com muita escrita.</p>
<p>Aliás, a maioria dos NoSQL é alguma derivação de uma estrutura de chave e valor, os key-value stores, como um Hash ou um Dicionário que qualquer linguagem de programação tem. O próprio Mongo pra mim é um key-value onde o value é um documento JSON, por isso chamamos de Document store. O sweet spot deles é localizar um valor tendo a chave em mãos. Diferente de um banco relacional, a maioria não é nem um banco transacional e nem um banco analítico, ou seja, que você pode sair fazendo queries como em SQL com qualquer critério e a qualquer momento. Pesquisas em bancos NoSQL precisam ser planejadas, indexadas com antecedência. NoSQLs tendem a ser bancos que você quer que funcionem em cluster, com shards, ou seja, nenhum servidor tem todos os dados que você precisa ao mesmo tempo, e se sua pesquisa for mal planejada, ela vai ter que ir vasculhando em todos os nós da rede, e isso custa processamento e custa tempo, muito tempo.</p>
<p>E um aviso muito importante que eu já dei em outros episódios: você não é o Facebook, nem o Google, nem o Netflix, nem o Uber. As experiências de engenharia dentro do Uber só tem valor pra outro produto com o mesmo nível de escala do Uber. Aceite a seguinte realidade: os 99,999% de nós jamais vai chegar a uma fração da escala do Uber. Portanto as decisões mais críticas de infraestrutura que eles relatam e que dão excelentes blogs posts e palestras são quase como ficção científica pra gente: é legal de ver, mas não nos impacta de nenhum jeito.</p>
<p>Um exemplo disso foi um blog post incendiário de um engenheiro do Uber de 2016 intitulado: &ldquo;Por que a engenharia do Uber migrou de Postgres pra MySQL&rdquo; acusando o Posgres de ter uma &ldquo;arquitetura ineficiente pra escritas&rdquo;. Se você ler só isso vai sair que nem um idiota dizendo &ldquo;puuts, temos que migrar nossos Postgres pra MySQL porque ele tem defeitos de escrita&rdquo;. Tem idéia de qual imbecil isso soa? Pra começo de conversa, o teor do artigo se refere a limitações do Postgres em casos extremos de grande volume de updates ou atualizações. E novamente, na escala do Uber. Tanto MySQL quanto Postgres são excelentes escolhas pra todos os casos que você precisa de um banco relacional, e que por acaso é a grande maioria dos casos. Programador é muito crédulo, fica acreditando em conto do vigário toda hora, é impressionante. Parem com isso!</p>
<p>Na prática a maioria de nós vai sempre acabar usando uma combinação de um banco relacional com um cache, usando algo como um Redis. Nessa configuração o Redis pode até ser um pouco mais relaxado porque o certo é fazer com que os dados sejam consultados no Redis e o que não estiver lá você carrega do banco relacional e grava no Redis à medida que for precisando. Portanto o Redis pode ser reconstruído do zero mesmo se der pane e precisar derrubar tudo. E você usa o Redis pra manter coisas pesadas de calcular e gravar num banco relacional como agregações, coisas com contadores, médias e outras métricas que agregam múltiplas linhas do banco.</p>
<p>Num banco relacional, o que mais custa performance é escrever, mais do que ler. A escrita precisa ter o tal ACID garantido: atomicidade, consistência, isolamento e durabilidade. Dados escritos precisam garantidamente estar no disco de tal forma que uma pane inesperada não faça você perder dados. Múltiplas conexões escrevendo ao mesmo tempo não podem um pisar no pé do outro, você precisa de um esquema de concorrência como multiversion concurrency control ou MVCC que a maioria dos bancos usa hoje em dia. E você precisa de transaction logs pra conseguir dar rollback ou voltar ao estado original dos dados se uma transação de múltiplas operações dá pau na metade.</p>
<p>Numa aplicação com grande pressão de escrita, digamos um ecommerce em dia de Black Friday, fechando várias compras por segundo. Você deve programar sua aplicação pra usar filas. Toda nova compra entra na fila e vai sendo escrito no banco à medida que der. Você nunca pode só aumentar a quantidade de servidores de aplicação web e deixar conectar todo mundo no mesmo banco ao mesmo tempo, isso só vai aumentar a contenção e no final ninguém consegue escreve. Então é melhor todo mundo entrar numa fila e só a fila gerenciar o banco. As leituras usam caches de Redis e isso tira a carga de pesquisa no banco. O grande gargalo numa aplicação web é o banco de dados, porque mesmo com replicação e sharding, você sempre vai ter contenção por causa das garantias ACID.</p>
<p>Além disso, num banco relacional você aprende na faculdade a idéia de normalização, onde separa os fatos em tabelas e faz referências entre as tabelas, de forma a diminuir ao máximo a redundância dos dados entre tabelas. Do ponto de vista de modelagem, assim é mais bonito. Mas uma verdade é que do ponto de vista de performance, quanto mais você normalizar, pior pode ser a performance. Tanto bancos relacionais quanto NoSQL privilegiam tabelas denormalizadas. Você faz trade-off de redundância e mais uso de disco pra ter mais performance. Uma coisa cara no processamento de pesquisas é justamente fazer joins entre tabelas, por isso bancos como Postgres oferecem recursos como views materializadas. Views é como se fosse uma tabela lógica, que não existe, porque é o resultado dinâmico de uma query, mas uma view materializada é como se fosse uma tabela denormalizada de verdade.</p>
<p>Ou seja, numa aplicação de verdade, você talvez se veja obrigado a denormalizar algumas tabelas. Um jeito errado de usar um MongoDB ou Cassandra por exemplo, é granular as coisas demais e tentar fazer o equivalente a joins. NoSQL não foi feito pra ter joins e relacionamentos. E você vai descobrir que muitos problemas de performance que você tem com bancos relacionais é normalização demais. Pense em sharding e se você deixar uma tabela num servidor, outra tabela com dependências em outro servidor, como você vai fazer joins rápida entre tabelas em servidores diferentes? Como tudo em computação, não existe uma forma de quanto é normalizar demais. É uma arte, muita tentativa e erro em diferentes casos de uso.</p>
<p>Um exemplo bem idiota, a famosa tabela de estados. A grande maioria dos programadores faz uma tabela separada chamada estados com uma coluna de código pra colocar por exemplo SP e outra coluna de nome pra colocar São Paulo. Daí numa outra tabela de cadastro você tem um campo estado_id que é uma referência pra linha da tabela de estados. Agora toda vez que você puxar o cadastro precisa fazer um join com a tabela de estados. Eu acho isso desnecessário. Eu colocaria SP e São Paulo direto na linha da tabela de cadastros e um select simples, sem join, vai devolver tudo que eu preciso. Mas aí toda linha vai duplicar São Paulo toda vez. Foda-se. A probabilidade do estado de São Paulo mudar de nome é praticamente zero. Se um dia mudar sim, vai custar um pouco caro dar update em todas as linhas de todas as tabelas que ficou com isso duplicado, mas isso é uma possibilidade remota. Pra que eu vou normalizar dados que nunca mudam?</p>
<p>Mas isso significa que não pode haver o caso de alguém mandar gravar São Paulo com til e de outro lugar alguém mandar gravar um Sao Paulo sem acento. A letra C de consistência do ACID vem migrando do banco pra aplicação faz algum tempo e se você começar a denormalizar, vai ter que garantir essa parte da consistência na aplicação. Vamos assumir que seu código não é porco, que você colocou enums no seu código e validações antes de mandar gravar no banco. Lógico, novamente é um trade-off, vou trocar performance por mais trabalho. Como eu disse, computação é uma arte, não existe almoço de graça, se pra você performance é mais importante, vai ter mais trabalho. Se pra você conveniência é mais importante, você paga tendo menos performance. Aliás, pra ter mais segurança, mais disponibilidade, mais durabilidade, mais integridade, tudo isso você paga tendo menos performance. Se quiser aumentar a performance, alguns desses fatores devem cair, por isso fica esperto!</p>
<p>E no final o que você vai ter com mais frequência é uma solução híbrida. Independente da sua linguagem favorita ou framework web, o mais comum é você usar um banco relacional. Eu pessoalmente gosto de Postgres mas não há nenhum motivo pra não usar MySQL com InnoDb ou mesmo seus forks como as versões da Percona ou o MariaDB. Você vai acabar movendo a consistência pra aplicação, com boas validações e bons testes no seu código. Também vai acabar denormalizando algumas tabelas pra evitar joins desnecessários, sempre criar índices corretamente, mas também evitar criar índices demais porque quanto mais índices pior a performance de escrita no banco.</p>
<p>Você também quer evitar gargalos de leitura e gargalos de escrita. Pra otimizar a leitura, você quer colocar um cache na frente do banco relacional. Você vai programar pra primeiro procurar o que quer num Redis. Se não achar lá, ou seja, tiver um cache miss, daí vai no banco relacional puxar os dados e gravar no Redis pra estar lá da próxima vez; com algum período de tempo pra expirar esse dado. E opcionalmente, se não quer o risco de parar o banco caso venham escritas demais, você vai programar sua aplicação pra colocar o pedido de escrita numa fila, seja no próprio sistema de publish/subscribe do Redis ou numa fila de verdade como RabbitMQ. Significa que o tempo geral de escrita deve ser mais lento do que mandar direto pro banco, mas se por acaso vier um pico inesperado que o banco não está preparado pra receber, em vez dele dar timeout e perder as operações, é sempre melhor ficar mais lento mas que garantidamente uma hora vai executar.</p>
<p>E não, programar com um framework assíncrono, seja Node.js ou qualquer outro que hoje em dia suporte async/await também não é uma bala de prata. Ajuda um pouco, mas não resolve o problema de picos e gargalo de concorrência no banco. No dia da Black Friday o que resolve o problema são filas, estruturas que você consegue controlar. async/await são boas pra ajudar, mas são só mais uma ferramenta e nunca a única solução que vai resolver tudo.</p>
<p>E se você precisar fazer uma coisa chamada Full text Search, que é pesquisas como você faz numa Amazon, onde você encontra produtos similares e tem resultados por relevância, você tem suporte a isso tanto no MySQL quanto no Postgres, mas na idéia de tirar o máximo de processamento do banco, o certo é manter um outro serviço só pra isso, no caso um Elasticsearch que é um dos melhores pra full text search. Toda vez que você escrever no banco relacional, também manda escrever no Elastic. Novamente vamos gastar mais recursos pra ganhar performance dos dois lados. Trade-off.</p>
<p>Se você usar Heroku, significa sua aplicação web rodar em um ou mais dynos web, e você ter um ou mais workers assíncronos que rodam em dynos de workers. Daí um Heroku Postgres, configurado com um follower pra onde você vai redirecionar as queries mais pesadas. Adicione também o Heroku Redis ou outro SaaS como Redis Cloud pra cache. Eu adicionaria um Cloudinary se sua aplicação precisar armazenar e processar uploads, um SendGrid se precisar mandar e-mails, um Rollbar pra guardar logs de erro, um New Relic RPM pra monitorar a performance da aplicação e um Bonsai Elasticsearch pra funcionalidades de pesquisa avançada. Espere gastar algo em torno de 200 a 300 dólares ou uns 1000 reais por mês. Parece muito? Não é, porque se você pretende instalar e configurar e manter tudo sozinho, você precisa de um sysadmin que vai te custar 5 a 10 vezes isso por mês. E isso só compensa se você for manter pelo menos 10 aplicações iguais a essa.</p>
<p>Se você realmente for manter parte disso sozinho, você que é sysadmin vai acabar usando um IaaS como a AWS ou Google Cloud. Nesse caso vai configurar as coisas com Kubernetes, Helm, Terraform e mesmo assim o ideal vai ser manter o banco de dados gerenciado em produtos como o AWS RDS ou AWS Aurora ou Google Cloud SQL. O banco de dados é a pior parte pra se manter, acredite. O trabalho simplesmente não compensa a menos que você seja muito grande. Não estou nem dizendo tamanho do Uber, mas você precisa estar lidando com terabytes diários de dados críticos pra compensar uma solução sob medida.</p>
<p>É tudo uma questão de gerenciamento de risco, um trade-off entre pagar mais pra ter mais segurança. Quanto custa pro seu negócio ficar fora do ar por 2 dias numa Black Friday? Quanto custa pro seu negócio perder os dados financeiros de meio dia de negociações? Na verdade, quanto custa perder 1 dia dos seus dados? Se você disser que não tem nenhum problema, eu questionaria a importância da existência do seu produto.</p>
<p>Uma última dica de infraestrutura: independente se você colocar sua aplicação numa máquina virtual de um VPS ou no AWS EC2 ou em containers via Kubernetes, ou mesmo numa plataforma como serviço como o Heroku ou Openshift, em qualquer dos casos contrate a Cloudflare. Ele é um serviço que vai substituir seu DNS, toda vez que alguém digitar seu domínio num navegador, o tráfego vai todo pra Cloudflare primeiro, e ele vai tanto servir como CDN que é um cache pra coisas como as imagens e outros assets do seu site como vai servir de detecção e prevenção de ataques. Se algum hacker ou algum moleque com um script tentar invadir seu site, o Cloudflare vai ser sua primeira linha de defesa e por experiência vou dizer que eles são muito bons. Altamente recomendado. Custa caro, mas de novo, quanto custa pra você se alguém entrar no seu site e roubar os dados dos seus clientes? Ou causar um Denial of Service no seu ecommerce no pico da Black Friday?</p>
<p>Como segunda seção deste vídeo, deixa eu explicar Function as a Service ou FaaS, que são produtos recentes como o AWS Lambda ou Microsoft Azure Functions ou Google Cloud Functions. Eles são basicamente containers. Se você já usou coisas containers de Docker ou dynos de workers do Heroku, a idéia é similar. A implementação varia entre os diferentes fornecedores então cuidado porque não é um padrão! Você configura um evento ou um trigger, que pode ser várias coisas, uma chamada HTTP, um pub/sub de algum serviço de fila como o AWS SQS, ou o Firebase no caso do Google Cloud. Quando esse evento vier, ele vai subir um container pequeno que vai executar essa sua tal lambda ou função que está englobada num frameworkzinho específico pra plataforma. Dizer lambda ou functions é mais marketing do que qualquer outra coisa, se você ainda não tinha notado, pra aproveitar esse hype em cima de linguagens funcionais, mas não tem nenhuma relação.</p>
<p>Os containers já vem pré-configurados com imagens pra diversos frameworks como Node.js, ou Ruby, ou Go, ou Python, ou Java, ou C#. A idéia não é rodar um framework web monolítico como Ruby on Rails, ou ASP.NET MVC, ou Play Framework. Repetindo: sim, como é um container comum, que roda uma linguagem conhecida, que inclusive permite você instalar dependências externas, então tecnicamente você pode rodar o quiser nesses containers. Mas só porque você pode, não quer dizer que você deve. Sempre existe a ferramenta certa pro problema certo e na minha opinião FaaS não foram feitos pra hospedar sites grandes inteiros.</p>
<p>Máquinas virtuais de um IaaS como AWS EC2 ou mesmo num VPS tendem a ter um SLA alto. Eles não garantem que a máquina vai ficar de pé pra sempre, mas a intenção é ficar de pé por um longo período de tempo. Semanas ou meses. Num PaaS como Heroku, os containers que ele chama de dynos também tendem a ficar de pé por longos períodos, dias ou semanas. Mas não é uma garantia, por isso ele força você a não depender de gravar coisas no disco local, porque os dynos são containers voláteis. Você deve programar de tal forma que se um dyno restartar, a aplicação reinicie sem problemas e tudo que você precisa está em banco de dados ou caches externos.</p>
<p>Um container de função tende a ser menor e a ficar de pé por períodos ainda menores, horas, no máximo dias. A intenção é que você não se preocupe de manualmente ter que subir esses containers. Eles vão sendo iniciados a partir desses triggers ou eventos. A função deveria receber os dados do evento, processar o que precisar o mais rápido possível, e assumir que ele pode desligar depois disso. Na prática o AWS ou Google vai manter o container de pé por mais algum tempo caso outros eventos estejam esperando na fila. Mas se ficar sem fazer nada por muito tempo ele deveria desligar pra não desperdiçar recursos.</p>
<p>Como é possível receber um trigger de tipo HTTP ou seja, você poderia apontar seu navegador pra uma URL que seja gatilho pra ativar uma função, e de dentro da função você poderia conectar num banco de dados e devolver um HTML, você pode ficar tentado a colocar um site, ou pior, uma aplicação web inteira. E vai funcionar. Existem alguns frameworks web mais enxutos feitos pra tentar tirar vantagem disso.</p>
<p>Se você não tem problema em ter alta latência e tempo de resposta demorado de tempos em tempos, pode funcionar sim. Mas você precisa garantir que sua aplicação seja pequena. Como os containers de funções podem desligar a qualquer momento, quanto maior for sua aplicação mais vai demorar pra ele levantar um novo container no próximo evento. Eu pessoalmente acho que usar AWS Lambda e outros FaaS pra servir aplicações web é o uso errado da tecnologia.</p>
<p>Lembram como eu falei agora pouco que uma das formas de evitar gargalos no banco de dados é jogar o pedido de escrita numa fila e depois um worker que ouve essa fila ir escrevendo no banco? Esse seria um jeito. Você enfileira os pedidos no AWS SQS que é uma fila, conecta ele como event source no AWS Lambda, faz uma função que conecta no AWS Aurora e aí grava no banco. Ou outras coisas pesadas pra sua aplicação como processar imagens ou vídeos. Você manda uploads da sua aplicação pro AWS S3, daí manda enfileirar a ordem no AWS SQS de novo, que vira event source pra um Lambda que vai puxar o arquivo do S3, processar e jogar de volta no S3 ou manda arquivar no AWS Redshift.</p>
<p>Eu diria, de maneira simplória, que o AWS Lambda está pra AWS como um dyno de worker está pro Heroku. Ele não é nenhuma grande revolução como muito palestrante ou blog post faz parecer. Ele é mais um sub-produto do uso de containers pra tentar desperdiçar menos recursos. Em vez de você subir um EC2 inteiro e deixar ele esperando sem fazer nada até vir alguma coisa pra rodar, melhor criar um container que vai ficar simplesmente desligado se não tiver nenhuma atividade. Ele é melhor pra aplicações que não tem tanta atividade assim o tempo todo, por exemplo, quando de noite ou de fins de semana não vem nenhum novo evento ou vem muito pouco.</p>
<p>E nos casos onde sua aplicação tem picos difíceis de prever, com esse sistema ele pode subir múltiplos containers se a fila estiver com muita pressão. Então Function as a Service é só isso: um container com um código simples dentro de uma função, preferencialmente, que vai responder a eventos pré-configurados, seja de uma fila, seja um gatilho via HTTP, com o mínimo possível de configuração.</p>
<p>Como terceiro e último tema de hoje, quero compartilhar um pensamento com vocês. Eu não sou um idealista, eu não sou um ativista: eu gosto de pensar que sou mais uma testemunha da história. Se possível eu prefiro tentar explicar as coisas da forma como eu vejo, mas não julgo ninguém por estar de um lado ou de outro porque só a história vai realmente dizer quem estava certo. Eu prefiro estar atento e me mantendo anti-frágil às mudanças. Quem leu Nassim Taleb, sabe o que isso significa.</p>
<p>Faz uns 30 anos que estamos vivendo num mundo de tecnologia dividido entre o mundo de código livre e o de código proprietário. Todos sabem que até o fim dos anos 90 o mundo de código proprietário sempre foi dominante. Mas principalmente a partir do novo século que o mundo de código livre evoluiu muito rápido. Hoje em dia todo desenvolvedor que se preza usa ferramentas derivadas do mundo de código aberto. Tudo que eu vim falando nessa série toda, incluindo distribuições Linux, Docker, Kubernetes, MySQL, Redis, tudo isso é mundo de código livre.</p>
<p>Em 1995 Bill Gates soltou o famoso memorando interno na Microsoft explicando a ameaça do navegador à hegemonia do sistema operacional Windows e outros produtos como o Office. Em breve o navegador substituiria o sistema operacional. E ele estava certo.</p>
<p>Considere o seguinte: antigamente precisaríamos comprar um servidor de e-mails como um Microsoft Exchange. Finalmente surgiram opções de código aberto como Zimbra, Open Xchange e vários outros. A mesma coisa foi acontecendo com todo software proprietário. E por um tempo pareceu que o modelo estava funcionando e o mundo open source foi ganhando força até que as versões abertas passaram a superar as fechadas em muitos lugares, em particular em tecnologias de web, redes, servidores e infraestrutura.</p>
<p>Então surgiu o Gmail em 2006 e na sequência a Amazon e seu AWS, que inaugurou o conceito de empacotar as coisas como serviço, e expor esses serviços como APIs, em uma escala que não se imaginava. E muitas empresas seguiram o mesmo modelo, oferecendo software como serviço. Antes você pagava licenças caras pra comprar ou assinar pelo uso de software proprietário. O que mudou agora é que com os canais de distribuição da internet podemos fazer a mesma coisa que antes, que era comprar software encaixotado de prateleira ou via consultoria ou vendedores. Então o que realmente mudou foi o preço ser muito menor porque o software consegue chegar em mais pessoas e empresas.</p>
<p>Mas note: voltamos ao mesmo modelo dos anos 90. Você paga uma assinatura mas não é proprietário do software. Pior: você não tem acesso ao código do software, porque ele funciona fechado atrás do que chamamos de um walled garden ou jardim emparedado. É um jardim bonito, mas lembre-se que ele não é aberto. Apesar da Amazon, Google, Facebook, Netflix, Uber e todos os outros de fato colaborarem ainda com software livre porque seria burrice não fazer isso, já que todos usam Linux e outras ferramentas.</p>
<p>Mas o software que torna a Amazon a Amazon ou o Google o Google jamais vai ser aberto. O AWS é o Windows da Amazon. O Google G Suite é o Office do Google. Custa muito caro criar uma versão aberta que seja igualmente competitiva, especialmente porque estamos falando de larga escala. Quanto mais eles crescerem, mais difícil vai ser pra qualquer outra empresa conseguir competir com eles. Então é um mercado com uma barreira de entrada enorme. E você basicamente trocou o Microsoft Office proprietário pelo Google Docs que também é proprietário.</p>
<p>Na prática, a maior parte do software que realmente dependemos no nosso dia a dia é proprietário, mas achamos que está tudo bem porque roda num navegador e não no Windows, mas tudo que tem acesso livre na verdade é pago com os dados da sua privacidade - e eu nem sou necessariamente contra, mas se você de alguma forma achava que é idealista de software livre usando tecnologias do Google ou Facebook, hoje já está num mundo fechado na sua maior parte, e você é equivalente a ser evangelista da Microsoft nos anos 90. De novo, nenhum problema nisso, só esclarecendo que não é o que você achava.</p>
<p>Considere que muito do que hoje é mantido como software livre não são nada mais do que bibliotecas que consomem APIs. Eles sozinhos não servem pra nada, são apenas cascas em cima de clientes de HTTP. Toda biblioteca da Amazon, do Google, do Facebook conectam com os serviços deles e só servem pra isso. Muitos desenvolvedores de software livre como o Salvatore que criou o Redis tem reclamado disso faz meses. Eles chegaram a mudar a licença pra uma mais controversa que impede que empresas como Amazon ou Google possam usar produtos como o RedisSearch, RedisGraph, RedisJSON e outros produtos.</p>
<p>Aos poucos você vê uma Amazon agindo como a Microsoft dos anos 90, adotando software de fora, e criando modificações que atraiam as pessoas pra sua versão proprietária e depois fechar as pessoas dentro de seu walled garden. Veja o AWS Aurora que é um MySQL modificado e cuja modificação não é open source. Daí eles lançam um AWS Lambda e uma gama de outros produtos que basicamente vão prender você dentro da AWS. Uma vez que você adotar esses produtos e seu negócio realmente der certo, você não vai mais sair de lá. Lembra o que os desenvolvedores reclamavam de Windows e Office? A AWS é o novo Windows e Office caso ainda não tenham percebido.</p>
<p>Eu comecei a pensar em outra coisa por causa de retrogaming e meu hobby gostando de tecnologias antigas. Se eu pegar um computador dos anos 80, um Commodore 64, eu consigo rodar todos os softwares daquela época. Se eu pegar um Gameboy, ainda consigo rodar todos os cartuchos. E como foram feitos dumps dos cartuchos, CDs e DVDs, então todos os jogos foram preservados em algum site. Essa parte do passado está preservada pra sempre.</p>
<p>Mas se eu pegar um celular Android de primeira geração, o HTC G1, muitos dos serviços nele não funcionam. Porque eles são só cascas pra software as a service que já não existe mais. Vários hardwares de 10 anos atrás vão começar a falhar sem serviços pra conectar. Consoles de videogame é outro exemplo. Se a Playstation Network decidir que nenhum PS3 pode mais logar na rede deles, você basicamente perdeu o uso desse hardware. Todo jogo online daqui a 20 anos, sem os servidores proprietários, não vão servir pra mais nada. Não há como preservar mais esse software. Daqui 40 anos uma geração não vai ter como saber o que se usava nos anos 2010 a não ser por fotos de tela. Eu posso rodar o Office de 1995 hoje se quiser, mas não poderemos mais rodar o Google Docs de 2019 daqui a alguns anos.</p>
<p>E a direção dos software as a service indicam uma mudança de paradigma. Um mundo ideal de um Uber é onde a maioria das pessoas não precisasse mais comprar carros. Uber é carro como serviço. Pra um AirBnb o mundo ideal é onde a maioria das pessoas não precisasse mais comprar casas. AirBnb é casa como serviço. iFood é comida como serviço. Netflix é entretenimento como serviço. Spotify é música como serviço, você não tem mais propriedade sobre sua mídia de música ou filmes. E assim por diante.</p>
<p>Como eu disse antes, em tecnologia tudo é um trade-off. Estamos trocando propriedade por conveniência. Eu não digo que isso é bom ou ruim, o veredito ainda não foi definido. Mas eu achei interessante colocar como pensamento final desta série. O mundo da tecnologia desta década é diferente de 10 anos atrás, que foi diferente de 20 anos atrás, então não espere que daqui a 10 anos vai ser igual agora, você pode esperar uma mudança geral de ecossistema e tecnologias mais ou menos a cada 10 anos.</p>
<p>E com esse pensamento, eu tenho o prazer de finalmente encerrar esta série! Eu realmente não pensei muito quando comecei. Imaginava que fosse encerrar em 3 ou 4 episódios e acabou se tornando uma mini-série de 14 episódios! Eu publiquei o primeiro episódio em 22 de Janeiro e não pulei nenhuma semana. E eu quis encerrar exatamente hoje porque coincidiu de ser o dia do Endgame, claro!</p>
<p>Depois desse projeto eu quero tirar uma ou duas semanas de folga do canal e ver o que quero gravar a seguir. Não tenho dúvidas que só toquei na ponta do iceberg ainda, mas espero que tenha atiçado a curiosidade de todo mundo que queira se tornar um desenvolvedor. Mandem dúvidas nos comentários abaixo e sugestões de novos assuntos. Se curtiram o vídeo mandem um joinha, e por favor compartilhem o video pra ajudar o canal. Não esqueçam de assinar abaixo e clicar no sininho pra saberem quando eu voltar. A gente se vê na próxima, até mais!!</p>
]]></content:encoded><category>bancos-de-dados</category><category>cloud</category><category>arquitetura-de-software</category><category>akitando</category></item><item><title>Compartilhando models entre apps Rails - Parte 2</title><link>https://akitaonrails.github.io/2016/10/03/compartilhando-models-entre-apps-rails-parte-2/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/10/03/compartilhando-models-entre-apps-rails-parte-2/</guid><pubDate>Mon, 03 Oct 2016 15:57:00 -0300</pubDate><description>&lt;p&gt;Vamos continuar de onde parei na &lt;a href="http://www.akitaonrails.com/2016/10/03/compartilhando-models-entre-aplicacoes-rails-parte-1"target="_blank" rel="noopener"&gt;Parte 1&lt;/a&gt;, onde descrevi rapidamente como extrair lógica reusável de models de uma app Rails para um Rubygem testável.&lt;/p&gt;
&lt;p&gt;Se eu fosse construir uma segunda app Rails conectando direto ao mesmo banco da primeira, bastaria adicionar a dependência do gem extraído:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;central-support&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;github&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Codeminer42/cm42-central-support&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;master&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;central/support&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;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"&gt;
&lt;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"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Aí recriar os models incluindo os mesmos Concerns, e remover a capacidade de rodar &lt;code&gt;bin/rails db:migrate&lt;/code&gt; na app secundária (criando tasks vazias com o mesmo nome, por exemplo).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Vamos continuar de onde parei na <a href="http://www.akitaonrails.com/2016/10/03/compartilhando-models-entre-aplicacoes-rails-parte-1"target="_blank" rel="noopener">Parte 1</a>, onde descrevi rapidamente como extrair lógica reusável de models de uma app Rails para um Rubygem testável.</p>
<p>Se eu fosse construir uma segunda app Rails conectando direto ao mesmo banco da primeira, bastaria adicionar a dependência do gem extraí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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;central-support&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;Codeminer42/cm42-central-support&#39;</span><span class="p">,</span> <span class="ss">branch</span><span class="p">:</span> <span class="s1">&#39;master&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;central/support&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aí recriar os models incluindo os mesmos Concerns, e remover a capacidade de rodar <code>bin/rails db:migrate</code> na app secundária (criando tasks vazias com o mesmo nome, por exemplo).</p>
<p>Aliás, esse é um grande detalhe que deixei de fora na Parte 1: até aqui, o schema estava <strong>congelado</strong> no <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/spec/support/rails_app/db/schema.rb"target="_blank" rel="noopener">gem central-support</a>.</p>
<p>Daqui pra frente, você precisa controlar a evolução das tabelas mapeadas no gem a partir do próprio gem. A melhor abordagem é usar o <code>spec/support/rails_app</code> e criar normalmente migrations novas com <code>bin/rails g migration</code> por lá. Depois você move a migration para a pasta <code>lib/generators/central/templates/migrations</code>.</p>
<p>O <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/lib/generators/central/install_generator.rb"target="_blank" rel="noopener"><code>lib/generators/central/install_generator.rb</code></a> cuida de disponibilizar uma task <code>central:install</code> que joga as migrations novas na pasta <code>db/migrate</code> da sua aplicação como de costume. Basta rodar <code>bundle update central-support</code> para pegar as últimas mudanças, rodar <code>bin/rails g central:install</code> para criar as migrations novas (ele pula automaticamente as existentes) e rodar o <code>bin/rails db:migrate</code> normal. O código de um gerador de migration é bem simples, dá pra fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;securerandom&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rails/generators&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rails/generators/base&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;rails/generators/active_record&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Central</span>
</span></span><span class="line"><span class="cl">  <span class="k">module</span> <span class="nn">Generators</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">InstallGenerator</span> <span class="o">&lt;</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Generators</span><span class="o">::</span><span class="no">Base</span>
</span></span><span class="line"><span class="cl">      <span class="kp">include</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Generators</span><span class="o">::</span><span class="no">Migration</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">source_root</span> <span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s2">&#34;../templates&#34;</span><span class="p">,</span> <span class="bp">__FILE__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">def</span> <span class="nf">create_migrations</span>
</span></span><span class="line"><span class="cl">        <span class="no">Dir</span><span class="o">[</span><span class="s2">&#34;</span><span class="si">#{</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">source_root</span><span class="si">}</span><span class="s2">/migrations/*.rb&#34;</span><span class="o">].</span><span class="n">sort</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">filepath</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="nb">name</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="n">migration_template</span> <span class="s2">&#34;migrations/</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;db/migrate/</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="ss">skip</span><span class="p">:</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">          <span class="nb">sleep</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>migration_template</code> cuida de adicionar o timestamp correto no arquivo de migration, então você não precisa colocar manualmente, e o nome do arquivo template pode ser algo simples como <a href="https://github.com/Codeminer42/cm42-central-support/blob/master/lib/generators/central/templates/migrations/add_role_field_to_users.rb"target="_blank" rel="noopener"><code>migrations/add_role_field_to_user.rb</code></a>.</p>
<p>Dito isso, tem um segundo desafio que adicionei na minha app secundária interna: eu queria que ela tivesse o seu próprio banco principal e usasse o banco do Central como fonte secundária somente leitura.</p>
<p>Então as migrations da app secundária (vamos chamar de Central-2) vão rodar contra o seu próprio banco principal, e não contra o banco principal do Central. Isso traz o seguinte problema: a suíte de testes precisa conseguir criar e migrar os dois bancos de teste, isolados do Central. Só em produção é que o Central-2 deve conectar no banco do Central.</p>
<p>Toda aplicação Rails tem um <code>config/database.yml.sample</code> e um <code>db/schema.rb</code>, então comecei criando um <code>config/database_central.yml.sample</code> e um <code>db_central/schema.rb</code>.</p>
<p>O <code>config/database_central.yml.sample</code> já fica interessante:</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">development</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adapter</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encoding</span><span class="p">:</span><span class="w"> </span><span class="l">unicode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="l">fulcrum_development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pool</span><span class="p">:</span><span class="w"> </span><span class="m">5</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="c"># Warning: The database defined as &#34;test&#34; will be erased and</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># re-generated from your development database when you run &#34;rake&#34;.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c"># Do not set this db to the same as development or production.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adapter</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encoding</span><span class="p">:</span><span class="w"> </span><span class="l">unicode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">&lt;% if ENV[&#39;DATABASE_CENTRAL_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;DATABASE_CENTRAL_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">&lt;% else %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="l">central_test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">&lt;% end %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pool</span><span class="p">:</span><span class="w"> </span><span class="m">5</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">production</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adapter</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">encoding</span><span class="p">:</span><span class="w"> </span><span class="l">unicode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;DATABASE_CENTRAL_URL&#39;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pool</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#39;DB_POOL&#39;] || 5 %&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>Em produção, ele vai usar a variável de ambiente <code>DATABASE_CENTRAL_URL</code> para conectar no banco principal do Central.</p>
<p>Rodando os testes localmente, ele simplesmente conecta num banco local chamado <code>central_test</code>.</p>
<p>Agora, ao rodar testes no Gitlab-CI (ou qualquer outro CI), eu preciso configurar o <code>DATABASE_CENTRAL_URL</code> para apontar para um banco Postgresql secundário de testes.</p>
<p>No Gitlab, eu configuro o script de build 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">codeminer42/ci-ruby:2.3</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="l">postgres: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="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">central-bot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">untracked</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.ci_cache/</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">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">RAILS_ENV</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DATABASE_URL</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql://postgres:@postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DATABASE_CENTRAL_URL</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql://postgres:@postgres/central_test</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">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">bundle install --without development production -j $(nproc) --path .ci_cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cp config/database.yml.sample config/database.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cp config/database_central.yml.sample config/database_central.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">bin/rails --trace central:db:create central:db:schema:load</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">bin/rails --trace db:create db:schema:load</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">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">bundle exec rspec</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>Repare como eu copio os arquivos &ldquo;.sample&rdquo; para garantir que existam. E como eu rodo tasks que você conhece como <code>db:create db:schema:load</code> para criar o banco de teste normal, mas também tasks que você não conhece como <code>central:db:create central:db:schema:load</code>.</p>
<p>Eu defini essas tasks em <code>lib/tasks/db_central.rake</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">task</span> <span class="ss">spec</span><span class="p">:</span> <span class="o">[</span><span class="s2">&#34;central:db:test:prepare&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">namespace</span> <span class="ss">:central</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">namespace</span> <span class="ss">:db</span> <span class="k">do</span> <span class="o">|</span><span class="n">ns</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="s2">&#34;environment:set&#34;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:environment:set&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:drop</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:drop&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:create</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:create&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:setup</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:setup&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:reset</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:reset&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:migrate</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:migrate&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:rollback</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:rollback&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:seed</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:seed&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="ss">:version</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:version&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">namespace</span> <span class="ss">:schema</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">task</span> <span class="ss">:load</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:schema:load&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">task</span> <span class="ss">:dump</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:schema:dump&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">namespace</span> <span class="ss">:test</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">      <span class="n">task</span> <span class="ss">:prepare</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;db:test:prepare&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># append and prepend proper tasks to all the tasks defined here above</span>
</span></span><span class="line"><span class="cl">    <span class="n">ns</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">task</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">task</span><span class="o">.</span><span class="n">enhance</span> <span class="o">[</span><span class="s2">&#34;central:set_custom_config&#34;</span><span class="o">]</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">        <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="o">[</span><span class="s2">&#34;central:revert_to_original_config&#34;</span><span class="o">].</span><span class="n">invoke</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">task</span> <span class="ss">:set_custom_config</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># save current vars</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@original_config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="ss">env_schema</span><span class="p">:</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SCHEMA&#39;</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">config</span><span class="p">:</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">dup</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"># set config variables for custom database</span>
</span></span><span class="line"><span class="cl">    <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SCHEMA&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;db_central/schema.rb&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;db&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;db_central&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;db/migrate&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;db_central/migrate&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;db/seeds&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;db_central/seeds.rb&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">paths</span><span class="o">[</span><span class="s1">&#39;config/database&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="s2">&#34;config/database_central.yml&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Tasks</span><span class="o">::</span><span class="no">DatabaseTasks</span><span class="o">.</span><span class="n">database_configuration</span> <span class="o">=</span> <span class="no">CM</span><span class="p">(</span><span class="no">Rails</span><span class="o">.</span><span class="n">root</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;database_central.yml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">File</span><span class="o">.</span><span class="n">read</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">ERB</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">result</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">YAML</span><span class="o">.</span><span class="n">load</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">unwrap</span><span class="o">.</span><span class="n">freeze</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">task</span> <span class="ss">:revert_to_original_config</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># reset config variables to original values</span>
</span></span><span class="line"><span class="cl">    <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SCHEMA&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="vi">@original_config</span><span class="o">[</span><span class="ss">:env_schema</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span> <span class="o">=</span> <span class="vi">@original_config</span><span class="o">[</span><span class="ss">:config</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que defino um namespace de tasks Rake que começam com <code>central:</code>, e cada uma delas conecta no banco secundário descrito no <code>database_central.yml</code>. A sintaxe estranha vem do meu <a href="https://github.com/akitaonrails/chainable_methods"target="_blank" rel="noopener"><code>chainable_methods</code></a>, não dê muita atenção.</p>
<p>O <code>db_central/schema.rb</code> é basicamente uma cópia do <code>spec/support/rails_app/db/schema.rb</code> do gem central-support, com as mesmas tabelas. O runner de specs tanto do gem quanto dessa app secundária só carrega o schema no banco de teste.</p>
<p>Agora que temos os fundamentos básicos para os specs no lugar, podemos focar em como a aplicação em si consome esses models externos.</p>
<p>Começamos adicionando um initializer como <code>config/initializer/db_central.rb</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">DB_CENTRAL</span> <span class="o">=</span> <span class="no">CM</span><span class="p">(</span><span class="no">Rails</span><span class="o">.</span><span class="n">root</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;database_central.yml.sample&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">File</span><span class="o">.</span><span class="n">read</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">ERB</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">result</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">YAML</span><span class="o">.</span><span class="n">load</span>
</span></span><span class="line"><span class="cl">  <span class="o">.</span><span class="n">unwrap</span><span class="o">.</span><span class="n">freeze</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 caso eu leio do arquivo sample porque, diferente do build de CI, quando eu faço deploy no Heroku eu não tenho um script para copiar o sample para o yaml final. Isso popula a constante <code>DB_CENTRAL</code> com a URL do banco armazenada na variável de ambiente <code>DATABASE_CENTRAL_URL</code> que eu preciso configurar.</p>
<p>Aí crio um arquivo novo chamado <code>app/models/remote_application_record.rb</code> 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">RemoteApplicationRecord</span> <span class="o">&lt;</span> <span class="no">ApplicationRecord</span>
</span></span><span class="line"><span class="cl">  <span class="n">establish_connection</span> <span class="no">DB_CENTRAL</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="nb">self</span><span class="o">.</span><span class="n">abstract_class</span> <span class="o">=</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">unless</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">test?</span>
</span></span><span class="line"><span class="cl">    <span class="n">default_scope</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">readonly</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que você cria um novo connection pool para a configuração do banco secundário. Você deve ter esse <code>establish_connection</code> em um único lugar e fazer os models herdarem daqui. O <code>abstract_class = true</code> faz com que o ActiveRecord deixe de tentar carregar de uma tabela com o mesmo nome dessa classe.</p>
<p>Em seguida temos um <code>default_scope</code> travando o model como <code>readonly</code>. A gente quer abrir mão disso no ambiente de teste, porque eu ainda quero deixar o Factory Girl popular o banco de teste com dados falsos para os specs. Mas é uma boa ideia ter isso em produção só para garantir.</p>
<p>Por fim, posso criar todos os models que preciso, como <code>app/models/central/team.rb</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Central</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Team</span> <span class="o">&lt;</span> <span class="no">RemoteApplicationRecord</span>
</span></span><span class="line"><span class="cl">    <span class="nb">self</span><span class="o">.</span><span class="n">table_name</span> <span class="o">=</span> <span class="s1">&#39;teams&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Associations</span>
</span></span><span class="line"><span class="cl">    <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Validations</span>
</span></span><span class="line"><span class="cl">    <span class="kp">include</span> <span class="no">Central</span><span class="o">::</span><span class="no">Support</span><span class="o">::</span><span class="no">TeamConcern</span><span class="o">::</span><span class="no">Scopes</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A partir daqui posso fazer queries Arel normais como <code>Central::Team.not_archived.limit(5)</code>.</p>
<h3>Conclusões<span class="hx:absolute hx:-mt-20" id="conclusões"></span>
    <a href="#conclus%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se ainda não viu, dá uma olhada na <a href="http://www.akitaonrails.com/2016/10/03/compartilhando-models-entre-aplicacoes-rails-parte-1"target="_blank" rel="noopener">Parte 1</a> para mais detalhes.</p>
<p>Essa é uma receita simples para compartilhar lógica de model entre uma app Rails principal de leitura e escrita e uma app Rails secundária de só leitura. Elas compartilham a maioria (não todos) dos mesmos models, compartilham a mesma lógica (através de alguns Concerns) e compartilham o mesmo banco.</p>
<p>Nesse caso particular, a abordagem recomendada é criar um <a href="https://devcenter.heroku.com/articles/heroku-postgres-follower-databases"target="_blank" rel="noopener">Follower database</a>, que é como o Heroku chama um banco secundário replicado, e fazer minha aplicação secundária conectar nele (já que ela só precisa de uma fonte somente leitura).</p>
<p>Para cenários mais complicados, você vai precisar de uma solução mais elaborada como uma camada de API HTTP para garantir que apenas uma App gerencie as migrations dos models. Mas a abordagem do Rubygem deve ser &ldquo;boa o suficiente&rdquo; para muitos casos.</p>
<p>Se eu realmente precisar ir por esse caminho, não vai ser difícil transformar esse pequeno gem numa app Rails API completa. Se você nem consegue separar a lógica em Concern, também não vai conseguir separar como APIs, então encare isso como um exercício rápido, um primeiro passo para criar uma <a href="http://programmers.stackexchange.com/questions/184464/what-is-an-anti-corruption-layer-and-how-is-it-used"target="_blank" rel="noopener">Anti Corruption Layer</a>.</p>
<p>E como bônus, considere contribuir com os projetos open source <a href="https://github.com/Codeminer42/cm42-central"target="_blank" rel="noopener">Central</a> e <a href="https://github.com/Codeminer42/cm42-central-support"target="_blank" rel="noopener">central-support</a>. Pretendo construir uma alternativa competitiva ao Pivotal Tracker/Trello e estamos chegando lá!</p>
]]></content:encoded><category>rails</category><category>bancos-de-dados</category><category>arquitetura-de-software</category></item><item><title>Hackeando o Mattermost Team Edition</title><link>https://akitaonrails.github.io/2016/08/12/hackeando-o-mattermost-team-edition/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/08/12/hackeando-o-mattermost-team-edition/</guid><pubDate>Fri, 12 Aug 2016 14:43:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="http://www.akitaonrails.com/2016/08/09/moving-away-from-slack-into-rocket-chat-good-enough"target="_blank" rel="noopener"&gt;meu post anterior&lt;/a&gt; contei sobre minha migração do Slack para o Rocket.chat. Mas também mencionei que, antes do Rocket.chat, eu preferiria usar o Mattermost. Primeiro, porque é escrito em Go (leve, altamente concorrente, super estável), e segundo, porque a base de código mostra muito mais qualidade do que a do Rocket.chat (que parece super frágil, com praticamente nenhum teste automatizado).&lt;/p&gt;
&lt;p&gt;Mas minha maior reclamação com o Mattermost é que a Team Edition, gratuita e open source, não tem um recurso super importante: impedir que usuários apaguem grupos privados.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="http://www.akitaonrails.com/2016/08/09/moving-away-from-slack-into-rocket-chat-good-enough"target="_blank" rel="noopener">meu post anterior</a> contei sobre minha migração do Slack para o Rocket.chat. Mas também mencionei que, antes do Rocket.chat, eu preferiria usar o Mattermost. Primeiro, porque é escrito em Go (leve, altamente concorrente, super estável), e segundo, porque a base de código mostra muito mais qualidade do que a do Rocket.chat (que parece super frágil, com praticamente nenhum teste automatizado).</p>
<p>Mas minha maior reclamação com o Mattermost é que a Team Edition, gratuita e open source, não tem um recurso super importante: impedir que usuários apaguem grupos privados.</p>
<p>O <a href="http://www.akitaonrails.com/2016/08/09/moving-away-from-slack-into-rocket-chat-good-enough#comment-2832915684"target="_blank" rel="noopener">@iantien comentou</a> que na verdade os grupos privados nunca são realmente &ldquo;apagados&rdquo;, eles só ficam marcados como deletados, auditados, mas todos os dados continuam no banco. Só que a UI não tem como esconder a opção de &ldquo;delete&rdquo; dos usuários e não existe uma tela de Administração para desarquivar os grupos apagados.</p>
<p>De fato, dá para abrir uma sessão <code>psql</code> no seu banco PostgreSQL e simplesmente rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>update channels set deleteat = 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>Isso vai desarquivar e restaurar todos os canais apagados. Mas dá para ver como isso é um saco.</p>
<p>Discordo fortemente quando ele diz que só &ldquo;empresas com 10.000 usuários&rdquo; precisariam de um recurso desses. Mesmo em um time pequeno, qualquer usuário mal-humorado pode simplesmente arquivar um canal do nada e atrapalhar toda a comunicação do time nos grupos privados. Claro, a &ldquo;Town Square&rdquo; e outros canais públicos continuam funcionando, mas se você tem até mesmo um único usuário externo participando de projetos, por exemplo, você quer usar grupos privados para isolar a comunicação interna dos usuários externos.</p>
<p>Então, não ter a opção de permissões bem básicas (como impedir que membros apaguem canais ou grupos privados) é um baita empecilho mesmo para times pequenos. E sim, a taxa de USD 20/usuário/ano não é cara, mas como o Mattermost ainda tem menos recursos que o Rocket.chat, fica uma proposta difícil de vender.</p>
<p>Hackear o código diretamente e adicionar uma flag para desabilitar essa opção, em Go, até que é bem fácil, só que você teria que manter seu próprio fork (porque acho que o Mattermost não aceitaria um pull request de uma feature que já está na oferta paga, Enterprise, deles).</p>
<p>Mas depois que o @iantien comentou que nada é apagado e tudo fica auditado, rapidamente percebi que eu poderia usar os metadados de auditoria e bolar um jeito de restaurar automaticamente os canais (exceto quando for o system admin fazendo). Tudo isso sem mexer no código-fonte.</p>
<p>Dá para usar as várias ferramentas disponíveis no próprio PostgreSQL, a saber: <strong>TRIGGERS</strong>. Então, sem mais delongas, basta rodar isto no seu banco do Mattermost:</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">OR</span><span class="w"> </span><span class="k">REPLACE</span><span class="w"> </span><span class="k">FUNCTION</span><span class="w"> </span><span class="n">undelete_channel</span><span class="p">()</span><span class="w"> </span><span class="k">RETURNS</span><span class="w"> </span><span class="k">trigger</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="err">$$</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">DECLARE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">user_counter</span><span class="w"> </span><span class="nb">integer</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">channel_id</span><span class="w"> </span><span class="nb">character</span><span class="w"> </span><span class="nb">varying</span><span class="p">(</span><span class="mi">26</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">BEGIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">-- Apenas para operações de delete de canal
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">IF</span><span class="w"> </span><span class="k">NEW</span><span class="p">.</span><span class="n">action</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">LIKE</span><span class="w"> </span><span class="s1">&#39;%/channels/%/delete&#39;</span><span class="w"> </span><span class="k">THEN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">RETURN</span><span class="w"> </span><span class="k">NEW</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">END</span><span class="w"> </span><span class="k">IF</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">-- Verifica se é o system_admin
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">SELECT</span><span class="w"> </span><span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">user_counter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">FROM</span><span class="w"> </span><span class="n">users</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">NEW</span><span class="p">.</span><span class="n">userid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">AND</span><span class="w"> </span><span class="n">roles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;system_admin&#39;</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="k">IF</span><span class="w"> </span><span class="n">user_counter</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">THEN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">RETURN</span><span class="w"> </span><span class="k">NEW</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">END</span><span class="w"> </span><span class="k">IF</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="n">channel_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">split_part</span><span class="p">(</span><span class="k">NEW</span><span class="p">.</span><span class="n">action</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;/&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">7</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="k">UPDATE</span><span class="w"> </span><span class="n">channels</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">SET</span><span class="w"> </span><span class="n">deleteat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">channel_id</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="k">RETURN</span><span class="w"> </span><span class="k">NEW</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">END</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">$$</span><span class="w"> </span><span class="k">LANGUAGE</span><span class="w"> </span><span class="n">plpgsql</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">DROP</span><span class="w"> </span><span class="k">TRIGGER</span><span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">EXISTS</span><span class="w"> </span><span class="n">undelete_channel</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">audits</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TRIGGER</span><span class="w"> </span><span class="n">undelete_channel</span><span class="w"> </span><span class="k">AFTER</span><span class="w"> </span><span class="k">INSERT</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">audits</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">FOR</span><span class="w"> </span><span class="k">EACH</span><span class="w"> </span><span class="k">ROW</span><span class="w"> </span><span class="k">EXECUTE</span><span class="w"> </span><span class="k">PROCEDURE</span><span class="w"> </span><span class="n">undelete_channel</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>É isso, ela vai escutar novos inserts na tabela de audits, checar se é uma ação de &ldquo;channel delete&rdquo;, checar se não é um &lsquo;system_admin&rsquo;, e, sendo o caso, pega automaticamente o id do canal da URL REST da action e faz o UPDATE para trazê-lo de volta.</p>
<p>Já testei e na minha UI os usuários nem percebem que algo aconteceu. Nem o próprio usuário que tentou deletar vê o canal sumir, ele volta instantaneamente.</p>
<p>Então, se isso era a única coisa impedindo você de usar a Team Edition on-premise, gratuita, aí está. E com isso você pode derivar funções para também impedir que canais sejam renomeados, mas vou deixar isso como exercício para você (por favor, compartilhe nos comentários abaixo se fizer).</p>
<p>Happy Hacking!</p>
]]></content:encoded><category>bancos-de-dados</category><category>automacao</category></item><item><title>Moving away from Slack into Rocket.chat. Good Enough.</title><link>https://akitaonrails.github.io/2016/08/09/moving-away-from-slack-into-rocket-chat-good-enough/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/08/09/moving-away-from-slack-into-rocket-chat-good-enough/</guid><pubDate>Tue, 09 Aug 2016 16:21:00 -0300</pubDate><description>&lt;p&gt;In my quest to create my own private infrastructure I&amp;rsquo;ve already moved away from Pivotal Tracker (saving this for a future post), Bitbucket, Github, Travis and into GitLab and GitLab Runner. There are more services I will move away from in the future, so expect more posts like this.&lt;/p&gt;
&lt;p&gt;In &lt;a href="http://www.akitaonrails.com/2016/08/03/migrando-para-o-gitlab-sim-vale-a-pena"target="_blank" rel="noopener"&gt;my previous post&lt;/a&gt; I forgot to mention minimal firewall and SSL configuration, but you can check it here.&lt;/p&gt;
&lt;p&gt;One of the most difficult pieces to move away from is Slack. Don&amp;rsquo;t get me wrong, it&amp;rsquo;s a great tool and it gets the job done. The free-tier is really competent but I really want some of the payed features such as history of all conversations with search capabilities and uploads without limit.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In my quest to create my own private infrastructure I&rsquo;ve already moved away from Pivotal Tracker (saving this for a future post), Bitbucket, Github, Travis and into GitLab and GitLab Runner. There are more services I will move away from in the future, so expect more posts like this.</p>
<p>In <a href="http://www.akitaonrails.com/2016/08/03/migrando-para-o-gitlab-sim-vale-a-pena"target="_blank" rel="noopener">my previous post</a> I forgot to mention minimal firewall and SSL configuration, but you can check it here.</p>
<p>One of the most difficult pieces to move away from is Slack. Don&rsquo;t get me wrong, it&rsquo;s a great tool and it gets the job done. The free-tier is really competent but I really want some of the payed features such as history of all conversations with search capabilities and uploads without limit.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/555/big_Franz.png" alt="Codeminer Rocket"  loading="lazy" /></p>
<h2>The Cost Conundrum<span class="hx:absolute hx:-mt-20" id="the-cost-conundrum"></span>
    <a href="#the-cost-conundrum" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>But then you have to commit to pay USD 8/month per user (USD 6.67 if you pay ahead of time). If you&rsquo;re a small team (around a dozen people) it&rsquo;s well worth it and you should definitely pay and get over with.</p>
<p>If you have more than 20 people then it starts to become a conundrum. On my GitLab cost calculations, some people argued that saving USD 15k yearly is not such a big deal, and they&rsquo;re not wrong.</p>
<p>If you&rsquo;re from outside of the US coastal cities, specially from countries in South America, Asia where currency can be more than 3 times devalued, than it quickly starts to become a big deal.</p>
<p>Apart from that, we are currently offloading a whole lot of out IPs and knowledge into the care of companies we don&rsquo;t really know and where we don&rsquo;t hold any real stakes.</p>
<p>If you&rsquo;re a medium to big company it really starts to make sense to want to have more control over your own property. Of course, it costs some work and maintenance, so it&rsquo;s not for everybody.</p>
<p>What I will argue is that if you have <strong>150 people</strong> or more, and you&rsquo;re still growing, it&rsquo;s not such a big burden to jump into your own infrastructure.</p>
<blockquote>
  <p>You avoid paying around <a href="https://codeminer42.slack.com/pricing"target="_blank" rel="noopener"><strong>USD 12,000 a year</strong></a> or more, and instead you will pay <strong>USD 960 a year</strong> in Digital Ocean boxes and MMS plus a few hours every month in basic maintenance. That&rsquo;s 12 times cheaper.</p>

</blockquote>
<p>Again, you shouldn&rsquo;t do it just for the cost savings as the maintenance burden can easily not be worth it if you or your team are not devops savvy and you don&rsquo;t have anyone to keep monitoring it. For most companies, the conversation history is not so important and even the free-tier will be more than enough.</p>
<p>Cost is less relevant depending on your context, and there are many different uses for group chats. For example, if you&rsquo;re an <a href="https://news.ycombinator.com/item?id=9754626"target="_blank" rel="noopener">open source community</a>, you would try <a href="https://gitter.im"target="_blank" rel="noopener">Gitter</a> instead.</p>
<p>There are also <a href="https://www.google.com/search?q=slack&#43;alternatives"target="_blank" rel="noopener">many other alternatives</a> that you may want to try.</p>
<p>For the sake of this article, let&rsquo;s assume you&rsquo;re a small to medium company, in need of private rooms, many external users (for example, your clients), with at least 50 people. Then this can start to make sense. Any other scenario and we have to analyze it differently.</p>
<h2>Why I chose Rocket.chat<span class="hx:absolute hx:-mt-20" id="why-i-chose-rocketchat"></span>
    <a href="#why-i-chose-rocketchat" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>With the cost reasoning out of the way, then it&rsquo;s a matter of choosing which alternative to use.</p>
<p>I tried <a href="https://www.mattermost.org/"target="_blank" rel="noopener">MatterMost</a> first and I really wanted to like it. It has a very similar feature set to Slack and most importantly, it&rsquo;s written in Go. So it&rsquo;s both lightweight, very fast, and not so difficult to contribute. It uses MySQL or PostgreSQL which I like.</p>
<p>Unfortunatelly, it has an important <strong>show-stopper</strong> for my needs. You can create both public channels or private groups, but anyone can delete a private group. If you want permission control for that you must use their <a href="https://about.mattermost.com/pricing/"target="_blank" rel="noopener">&ldquo;Enterprise&rdquo; offering</a> and pay <strong>USD 20/year per user</strong>.</p>
<blockquote>
  <p>It&rsquo;s still at least <strong>3 times cheaper</strong> than Slack, so it may be a good option for you.</p>

</blockquote>
<p>I really want to have full-control, not just make it cheaper. I could pay Slack already if I didn&rsquo;t care, so I would not just pay an alternative and not have full-control (including the ability to tweak and improve the entire code base).</p>
<p>Then, it took me back to <a href="https://rocket.chat/"target="_blank" rel="noopener">Rocket.chat</a>. It&rsquo;s feature set also rivals Slack, it&rsquo;s good looking enough. But, it&rsquo;s made in Meteor. Now, I don&rsquo;t have anything against Meteor and I really think a Slack-clone is exactly the kind of use case where you could use Meteor to its full potential.</p>
<p>Technically I really think it&rsquo;s a downside to be forced to use Mongodb (Meteor requires Mongo). For small installations I really prefer to have PostgreSQL. MongoDB is competent, but for medium to big installations, and I will show you why below.</p>
<p>I <strong>strongly dislike</strong> the <a href="https://medium.com/friendship-dot-js/i-peeked-into-my-node-modules-directory-and-you-wont-believe-what-happened-next-b89f63d21558#.8jd3z3n6u"target="_blank" rel="noopener">culture of writing software without <em>minimal care</em></a> such as having a reasonably complete test suite. You browse through the many packages that comprise Rocket.chat and several of them have no tests whatsoever. Some packages do have jasmine tests, but the majority is lacking.</p>
<p>This is <a href="https://github.com/RocketChat/Rocket.Chat/blob/a5cb22bb0017f4c39654bf1e2895ae64acb0339b/packages/rocketchat-katex/tests/jasmine/client/unit/katex.spec.coffee"target="_blank" rel="noopener">one example</a> I picked randomly, out of the few test files I found:</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="nx">describe</span> <span class="s1">&#39;rocketchat:katex Client&#39;</span><span class="p">,</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">it</span> <span class="s1">&#39;should exist&#39;</span><span class="p">,</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">expect</span><span class="p">(</span><span class="nx">RocketChat</span><span class="p">.</span><span class="nx">katex</span><span class="p">).</span><span class="nx">toBeDefined</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>This is sad, sorry to say that, but it is. Not surprisingly I&rsquo;ve seen katex issues while using the interface. And if you think this was just a poor choice, I picked <a href="https://github.com/RocketChat/Rocket.Chat/blob/a5cb22bb0017f4c39654bf1e2895ae64acb0339b/packages/rocketchat-markdown/tests/jasmine/client/unit/markdown.spec.coffee"target="_blank" rel="noopener">another test file</a>:</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="nx">describe</span> <span class="s1">&#39;rocketchat:markdown Client&#39;</span><span class="p">,</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">it</span> <span class="s1">&#39;should exist&#39;</span><span class="p">,</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">expect</span><span class="p">(</span><span class="nx">RocketChat</span><span class="p">.</span><span class="nx">Markdown</span><span class="p">).</span><span class="nx">toBeDefined</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>Really?</p>
<p>So, the code itself is not so pretty, you have to keep this in mind. I&rsquo;d much rather use Mattermost (which is much better structured and with enough tests), but it&rsquo;s not entirely free, so it&rsquo;s not an option to me. For my scenario, I&rsquo;d rather have sloppy code (that minimally &ldquo;works&rdquo;) that I can tweak than code I can&rsquo;t see, for this particular venture. Many inexperienced developers may argue about the necessity of having automated tests, but not having them also impacts this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/552/big_Contributors_to_RocketChat_Rocket_Chat.png" alt="Rocket.chat contribution graph"  loading="lazy" /></p>
<p>If we see how the project is evolving over time, we can see that interest is diminishing. The lack of good automated tests also make it more difficult for new people to contribute without adding regressions, make contribution reviews more difficult and time consuming.</p>
<p>On the other hand the feature set is good enough as it is. There is only so much you can add to a Slack-clone. The upside is that if the code doesn&rsquo;t change too much, I can tweak it more without having many things breaking all the time. It&rsquo;s the complete opposite from the impressive contribution boost over at GitLab, for example.</p>
<p>Doing a few days of testing through the administration settings, user interface, having dozens of users doing real conversation, convinced me that even though the code quality is bad, the feature set is indeed there. I think I can live with that. Your mileage may vary though.</p>
<p>I am using <a href="http://meetfranz.com"target="_blank" rel="noopener">Franz</a> to open Slack, Rocket.chat, Hangouts, Messenger and any number of other communicators in one single app, which is great. And Rocket.chat does provide <a href="https://itunes.apple.com/us/app/rocket.chat/id1028869439?mt=8"target="_blank" rel="noopener">mobile</a> <a href="https://play.google.com/store/apps/details?id=com.konecty.rocket.chat&amp;hl=en"target="_blank" rel="noopener">apps</a> based off PhoneGap. Again, it&rsquo;s good, not great.</p>
<p>Many people have heavy bots or integrations into their Slack configuration. The alternatives such as Rocket.chat do support incoming and outgoing webhooks and most of the integrations should be possible (with some tweaking and hacking such as this <a href="https://github.com/FinndropStudios/GifRocket"target="_blank" rel="noopener">Giphy example</a>), so make a list of all important integrations and do a Google research on that before continuing.</p>
<h2>Planning the Infrastructure<span class="hx:absolute hx:-mt-20" id="planning-the-infrastructure"></span>
    <a href="#planning-the-infrastructure" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>All that having being said, I&rsquo;d recommend you try Rocket.chat&rsquo;s <a href="https://demo.rocket.chat"target="_blank" rel="noopener">demo server</a> first to see if it has the minimal features you need. And also that you install a dry-run in a small Digital Ocean box with the intention to blow it off later.</p>
<p>For a single-instance, stand-alone installation, temporary dry-run, I recommend you start by installing the <a href="https://www.digitalocean.com/community/tutorials/how-to-use-the-mongodb-one-click-application"target="_blank" rel="noopener">MongoDB One-Click Application</a> and follow <a href="https://www.digitalocean.com/community/tutorials/how-to-install-configure-and-deploy-rocket-chat-on-ubuntu-14-04"target="_blank" rel="noopener">this instruction</a> to install everything in one machine.</p>
<p>This is the worst possible production environment, which is why I am being so repetitive into saying that you should destroy this box after you do your testing.</p>
<p>As I&rsquo;ve said before, I am assuming a scenario where you have 50 or more people, with real clients, important communication going on. You don&rsquo;t want to keep a simple installation like this for production usage.</p>
<p>For a <strong>minimal production installation</strong> you will need at the very least 4 boxes with 1 GB of RAM each. Now, why 4?</p>
<p>This is an important section for most of the beginner Javascript programmers out there playing with the so called &ldquo;MEAN&rdquo; stack (MongoDB, Express, Angularjs, Node.js). I&rsquo;ve seen people deploy single-instance, stand-alone &ldquo;MEAN&rdquo; apps for production usage. And this is super bad.</p>
<blockquote>
  <p><strong>MongoDB is NOT MEANT to function with just one single instance!</strong></p>

</blockquote>
<p>More than that:</p>
<blockquote>
  <p><strong>DO NOT start an even number of MongoDB nodes, or at least install an Arbiter</strong></p>

</blockquote>
<p>So, the minimal setup for MongoDB is a <a href="https://docs.mongodb.com/manual/core/replica-set-architecture-three-members/"target="_blank" rel="noopener">Three-member Replica Set</a>, no less!</p>
<p>The article <a href="https://www.rainforestqa.com/blog/2012-11-05-mongodb-gotchas-and-how-to-avoid-them/"target="_blank" rel="noopener">&ldquo;MongoDB Gotchas &amp; How To Avoid Them&rdquo;</a> is a little bit old but you should be aware of several of the documented gotchas, including adding replica sets and avoid waiting too long to start sharding you data.</p>
<p>The most important part of making Rocket.chat reliable is to make your MongoDB minimally reliable, and this is a hidden cost you must consider.</p>
<h4>Installing a Three-Member Replica Set<span class="hx:absolute hx:-mt-20" id="installing-a-three-member-replica-set"></span>
    <a href="#installing-a-three-member-replica-set" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>For the sake of this article I will assume that you created 3 (three) MongoDB One-Click Applications on Digital Ocean&rsquo;s at the very least 1 GB RAM boxes (2 GB RAM machines are a good choice if you&rsquo;re in doubt, but never the 512 Mb) and another bare Ubuntu 14.04 box to be the web server. Create all machines in the same sub-region, all of them with private networking and backup enabled.</p>
<p>Take note of the public IPs and private IPs, for the sake of this article let&rsquo;s say you have this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Public IP     Private IP    Hostname

192.160.10.1  10.0.0.1      mongo1.yourdomain.com
192.160.10.2  10.0.0.2      mongo2.yourdomain.com
192.160.10.3  10.0.0.3      mongo3.yourdomain.com
192.160.10.4  10.0.0.4      yourdomain.com</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>In the commands and code snippets below, make sure to replace the fake IPs for your own private IPs.</p>
<p>As I said in <a href="http://www.akitaonrails.com/2016/08/03/migrando-para-o-gitlab-sim-vale-a-pena?"target="_blank" rel="noopener">my GitLab post</a>, create a swap file, configure the locale, set it up for automatic security updates, and do upgrade the Ubuntu packages.</p>
<p>The next first thing: configure the firewall in all machines (assuming <code>ufw</code> package is already installed):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo ufw reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow https
sudo ufw allow ssh
sudo ufw allow in on eth1 to any port 27017
sudo ufw enable</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>We are allowing MongoDB&rsquo;s 27017 port only for machines in the same private networking (through eth1, the public IP go through eth0). We&rsquo;re also allowing port 22 for SSH and although I am not listing it here in this article you should also consider installing and configuring <a href="https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04"target="_blank" rel="noopener">fail2ban</a>. Digital Ocean also has a <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu-14-04"target="_blank" rel="noopener">good post on UFW</a> for more details.</p>
<p>Next thing to do on the MongoDB machines is to <a href="https://docs.mongodb.com/manual/tutorial/transparent-huge-pages/"target="_blank" rel="noopener">Disable Transparent Huge Pages (THP)</a>. For that create a file <code>/etc/init.d/disable-transparent-hugepages</code>, with this content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="c1">### BEGIN INIT INFO</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Provides:          disable-transparent-hugepages</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Required-Start:    $local_fs</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Required-Stop:</span>
</span></span><span class="line"><span class="cl"><span class="c1"># X-Start-Before:    mongod mongodb-mms-automation-agent</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Default-Start:     2 3 4 5</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Default-Stop:      0 1 6</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Short-Description: Disable Linux transparent huge pages</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Description:       Disable Linux transparent huge pages, to improve</span>
</span></span><span class="line"><span class="cl"><span class="c1">#                    database performance.</span>
</span></span><span class="line"><span class="cl"><span class="c1">### END INIT INFO</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="nv">$1</span> in
</span></span><span class="line"><span class="cl">  start<span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> -d /sys/kernel/mm/transparent_hugepage <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nv">thp_path</span><span class="o">=</span>/sys/kernel/mm/transparent_hugepage
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="o">[</span> -d /sys/kernel/mm/redhat_transparent_hugepage <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nv">thp_path</span><span class="o">=</span>/sys/kernel/mm/redhat_transparent_hugepage
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s1">&#39;never&#39;</span> &gt; <span class="si">${</span><span class="nv">thp_path</span><span class="si">}</span>/enabled
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s1">&#39;never&#39;</span> &gt; <span class="si">${</span><span class="nv">thp_path</span><span class="si">}</span>/defrag
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">unset</span> thp_path
</span></span><span class="line"><span class="cl">    <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</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>Now you can register it as a service:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod 755 /etc/init.d/disable-transparent-hugepages
sudo update-rc.d disable-transparent-hugepages defaults
sudo /etc/init.d/disable-transparent-hugepages start</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>Stop MongoDB in all 3 machines:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service mongod stop</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>Now comes a complicated procedure that you should not do wrong. It&rsquo;s only complicated because you have to do it in all 4 machines minding their correct private IP addresses. If you already have your own DNS server, you can skip this, otherwise follow through carefully.</p>
<p>You will have to edit both <code>/etc/hostname</code> and <code>/etc/hosts</code>. Each machine will have a particular name and they should be able to find the other machines by name. Let&rsquo;s start by editing the <code>/etc/hostname</code> on each machine. Use the table in the beginning of this section, for example, in the machine with public IP <code>192.160.10.1</code> you must edit it&rsquo;s hostname to be <code>mongo1.yourdomain.com</code>, and so on for all the machines.</p>
<p>Once you have it done, you have to replace the line <code>127.0.0.1 localhost</code> in the <code>/etc/hosts</code> in each of them for the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>127.0.0.1 localhost mongo1.yourdomain.com
10.0.0.1 mongo1.yourdomain.com
10.0.0.2 mongo2.yourdomain.com
10.0.0.3 mongo3.yourdomain.com
10.0.0.4 yourdomain.com</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>This snippet assumes you&rsquo;re in the machine with private IP <code>10.0.0.1</code>, of course. Make sure you change <code>mongo1.yourdomain.com</code> in the first line for the hostname of the machine you&rsquo;re in. If this is too difficult for you, you definitely should not try to maintain your own infrastructure, so beware.</p>
<p>Finally, let&rsquo;s edit the <code>/etc/mongod.conf</code> with these changes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>storage:
  dbPath: /mongodb-data
...
net:
  port: 27017
  bindIp: 0.0.0.0
...
replication:
  replSetName: rs0</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>This should be exactly the same in all machines. And you must create this <code>/mongodb-data</code> directory like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir /mongodb-data
sudo chown -R mongodb:mongodb /mongodb-data</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>Now go ahead on machine <code>mongo1.yourdomain.com</code>, start up with <code>sudo service mongod start</code> and fire up the <code>mongo</code> command, and you should see something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># mongo
MongoDB shell version: 3.2.8
connecting to: test
&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>Let&rsquo;s now configure the replica set by issuing this command in the MongoDB command-line interface:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rs.initiate()
rs.add(&#39;mongo2.yourdomain.com&#39;)
rs.add(&#39;mongo3.yourdomain.com&#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>And you can now log in to the secondary machines and <code>sudo service mongod start</code> in all of them. They should all start joining the replica set and syncing.</p>
<p>You can check the status of the replication like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&gt; rs.conf()

{
  &#34;set&#34; : &#34;rs0&#34;,
  &#34;date&#34; : ISODate(&#34;2016-08-09T17:33:10.466Z&#34;),
  &#34;myState&#34; : 1,
  &#34;term&#34; : NumberLong(-1),
  &#34;heartbeatIntervalMillis&#34; : NumberLong(2000),
  &#34;members&#34; : [
    {
      &#34;_id&#34; : 1,
      &#34;name&#34; : &#34;mongo1.yourdomain.com:27017&#34;,
      &#34;health&#34; : 1,
      &#34;state&#34; : 1,
      &#34;stateStr&#34; : &#34;PRIMARY&#34;,
      &#34;uptime&#34; : 11727,
      &#34;optime&#34; : Timestamp(1470763990, 3),
      &#34;optimeDate&#34; : ISODate(&#34;2016-08-09T17:33:10Z&#34;),
      &#34;electionTime&#34; : Timestamp(1470752553, 1),
      &#34;electionDate&#34; : ISODate(&#34;2016-08-09T14:22:33Z&#34;),
      &#34;configVersion&#34; : 6,
      &#34;self&#34; : true
    },
    {
      &#34;_id&#34; : 2,
      &#34;name&#34; : &#34;mongo2.yourdomain.com:27017&#34;,
      &#34;health&#34; : 1,
      &#34;state&#34; : 2,
      &#34;stateStr&#34; : &#34;SECONDARY&#34;,
      &#34;uptime&#34; : 11693,
      &#34;optime&#34; : Timestamp(1470763988, 4),
      &#34;optimeDate&#34; : ISODate(&#34;2016-08-09T17:33:08Z&#34;),
      &#34;lastHeartbeat&#34; : ISODate(&#34;2016-08-09T17:33:10.026Z&#34;),
      &#34;lastHeartbeatRecv&#34; : ISODate(&#34;2016-08-09T17:33:08.887Z&#34;),
      &#34;pingMs&#34; : NumberLong(0),
      &#34;syncingTo&#34; : &#34;mongo3.yourdomain.com:27017&#34;,
      &#34;configVersion&#34; : 6
    },
    {
      &#34;_id&#34; : 3,
      &#34;name&#34; : &#34;mongo3.yourdomain.com:27017&#34;,
      &#34;health&#34; : 1,
      &#34;state&#34; : 2,
      &#34;stateStr&#34; : &#34;SECONDARY&#34;,
      &#34;uptime&#34; : 11693,
      &#34;optime&#34; : Timestamp(1470763988, 4),
      &#34;optimeDate&#34; : ISODate(&#34;2016-08-09T17:33:08Z&#34;),
      &#34;lastHeartbeat&#34; : ISODate(&#34;2016-08-09T17:33:09.994Z&#34;),
      &#34;lastHeartbeatRecv&#34; : ISODate(&#34;2016-08-09T17:33:08.655Z&#34;),
      &#34;pingMs&#34; : NumberLong(0),
      &#34;syncingTo&#34; : &#34;mongo1.yourdomain.com:27017&#34;,
      &#34;configVersion&#34; : 6
    }
  ],
  &#34;ok&#34; : 1
}</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>Each box should be syncing data to another box, one of them will be marked &ldquo;PRIMARY&rdquo; and the others will be &ldquo;SECONDARY&rdquo;. If one of them &ldquo;dies&rdquo; for some reason, one of the others will be elected as PRIMARY until you add a replacement box. Read more details on this procedure in <a href="https://www.digitalocean.com/community/tutorials/how-to-implement-replication-sets-in-mongodb-on-an-ubuntu-vps"target="_blank" rel="noopener">Digital Ocean&rsquo;s post about implementing replication sets</a>.</p>
<h4>Adding your MongoDB environment to MMS<span class="hx:absolute hx:-mt-20" id="adding-your-mongodb-environment-to-mms"></span>
    <a href="#adding-your-mongodb-environment-to-mms" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Another thing beginners will not realise is that it&rsquo;s a good idea to monitor your services. At the very least you should install MongoDB Cloud Manager agents into your servers. Just set up an account at <a href="https://www.mongodb.com/cloud"target="_blank" rel="noopener">MongoDB Atlas</a>.</p>
<p>It&rsquo;s beyond the point of this article to explain Atlas, but you should have no trouble installing the agents with the Group ID and API Key provided. Then install the monitoring agent and have the Manager figure out your existing deployment and replica set.</p>
<p>The basic subscription for monitoring will cost you an extra USD 39 a month, but believe me when I say it&rsquo;s worth to add all your MongoDB deployments under MMS.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/553/big_Deployment___Cloud_Manager__MongoDB_Cloud_Manager.png" alt="MMS"  loading="lazy" /></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/554/big_Host___Cloud_Manager__MongoDB_Cloud_Manager.png" alt="Replica Set Graphs"  loading="lazy" /></p>
<h4>Installing a multi-instance Node.js service<span class="hx:absolute hx:-mt-20" id="installing-a-multi-instance-nodejs-service"></span>
    <a href="#installing-a-multi-instance-nodejs-service" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Another mistake beginners in the MEAN stack do is to start up a single Node.js instance. Yes, Node.js asynchronous I/O nature makes it &ldquo;concurrent enough&rdquo; in 1 single thread. But you do want to maximize the rented machine so you should spin up at least one Node.js instance for each CPU core. A 1 GB RAM machine has just 1 core, but you can spin up at least 2 instances.</p>
<p>First of all, we must set up the <code>yourdomain.com</code> (<code>192.160.10.4</code> or <code>10.0.0.4</code> in the example) to support Node.js, let&rsquo;s do it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install -y npm curl graphicsmagick nginx git bc
sudo npm install -g n
sudo npm install -g forever
sudo npm install -g forever-service
sudo n 0.10.40</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>If you haven&rsquo;t already, <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-sudo-user-on-ubuntu-quickstart"target="_blank" rel="noopener">create a more restricted sudo user</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>adduser rocket # add a password when asked
usermod -aG sudo rocket
su - rocket # and always ssh in with `ssh rocket@yourdomain.com`</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>Within rocket&rsquo;s home directory let&rsquo;s install the Rocket.chat codebase:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cd /home/rocket
curl -L https://rocket.chat/releases/latest/download -o rocket.chat.tgz
tar zxvf rocket.chat.tgz
mv bundle Rocket.Chat
cd Rocket.Chat/programs/server
npm install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And now let&rsquo;s add a way for the machine to start the Rocket.chat whenever it reboots:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cd ~/Rocket.Chat
sudo forever-service install -s main.js -e &#34;ROOT_URL=https://rocketchat42.com/ MONGO_URL=mongodb://mongo1.yourdomain.com:27017/rocketchat MONGO_OPLOG_URL=mongodb://mongo1.yourdomain.com:27017/local PORT=3001&#34; rocketchat1
sudo forever-service install -s main.js -e &#34;ROOT_URL=https://rocketchat42.com/ MONGO_URL=mongodb://mongo1.yourdomain.com:27017/rocketchat MONGO_OPLOG_URL=mongodb://mongo1.yourdomain.com:27017/local PORT=3002&#34; rocketchat2

sudo start rocketchat1
sudo start rocketchat2</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>Let&rsquo;s also create SSL certificates (and for that you must have a properly registered domain, of course). Again, <a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-14-04"target="_blank" rel="noopener">Digital Ocean&rsquo;s documentation on Let&rsquo;s Encrypt</a> is very good. In summary, all you have to do is:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt</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>Now edit the <code>/etc/nginx/sites-available/default</code> and add this inside the server block:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>server {
  ...
  location ~ /.well-known {
          allow all;
  }
  ...
}</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>Now you can:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo service nginx reload

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

cd /opt/letsencrypt
./letsencrypt-auto certonly -a webroot --webroot-path=/usr/share/nginx/html -d yourdomain.com -d www.yourdomain.com</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>Again, replace <code>yourdomain.com</code> with your own registered domain, of course. The <code>letsencrypt</code> command will prompt you to enter your e-mail address and accept terms of service.</p>
<p><a href="https://letsencrypt.org/"target="_blank" rel="noopener">Let&rsquo;s Encrypt</a> is a fantastic free SSL provider. Their certificates are short lived and are meant to expire in 90 days, so you must set up auto-renewal. Start <code>sudo crontab -e</code> and add the following lines:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew &gt;&gt; /var/log/le-renew.log
35 2 * * 1 /etc/init.d/nginx reload</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>This should take care of SSL. Now you can edit your <code>/etc/nginx/sites-available/default</code> and replace it with this template:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Upstreams
upstream backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

server {
  listen 443 ssl;

  server_name yourdomain.com;

  ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  ssl_ciphers &#39;ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH&#43;AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA&#39;;
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;
  ssl_stapling on;
  ssl_stapling_verify on;
  add_header Strict-Transport-Security max-age=15768000;

  location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection &#34;upgrade&#34;;
    proxy_set_header Host $http_host;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forward-Proto http;
    proxy_set_header X-Nginx-Proxy true;

    proxy_redirect off;
  }

}

server {
  listen 80;
  server_name yourdomain.com www.yourdomain.com;
  return 301 https://$host$request_uri;
}</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>Just for the sake of completeness, again: replace <code>yourdomain.com</code> for your domain. Usually the <code>/etc/nginx/sites-enabled/default</code> is a symlink to <code>/etc/nginx/sites-available/default</code>, just check that as well. This configuration will load balance between the 2 node.js instances we configured and started before. And you can add as many as you want following the same <code>forever-install</code> procedure and adding the new instances to the <code>upstream</code> section in the nginx configuration above.</p>
<p>If everything is correct, you can restart nginx again: <code>sudo service nginx restart</code>.</p>
<p>And now you should have <code>https://yourdomain.com</code> already up and running. The first user that sign up will be the site administrator and from there you can customize Rocket.chat internally.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>As you can see, this is not a simple procedure to follow and I am assuming you have experience managing your own infrastructure. If you don&rsquo;t you definitely should NOT do this by yourself.</p>
<p>If you do it correctly, you should have a functional Slack-clone with minimal reliability (thanks in large part to Digital Ocean) and minimal cost (USD 80/month in the configuration I described that should be enough for more than a 100 users). Rocket.chat also offer more <a href="https://rocket.chat/docs/installation/"target="_blank" rel="noopener">documentation</a> for other environments including Docker configurations you might want to try. But the procedure above is sufficient for my needs.</p>
<p>I said it already but it&rsquo;s better to repeat it: don&rsquo;t fall for the trap of installing everything in a single box, with single instance MongoDB, and without proper monitoring. It&rsquo;s asking for trouble.</p>
<p>For now I am in the middle of the roll out. All developers in my company are already in the new deployment and soon half of the clients should also migrate (some will not be able to leave Slack just yet), but with Franz and the ability of full history and searchability this shoudn&rsquo;t be a concern even if a developer stays offline for some period of time.</p>
<p>I also don&rsquo;t advocate that everybody should be online and responding in real-time. It&rsquo;s unfeasible, unproductive, creates unnecessary tension. People should participate when they have free time, and they should be able to concentrate without worrying that they are missing something. That&rsquo;s why they should opt-out of being notified in the more busy channels and just enable notification on the private groups that matter.</p>
<p>Coincidentally Jason Fried just posted about concerns over group chats at <a href="https://m.signalvnoise.com/is-group-chat-making-you-sweat-744659addf7d#.3qxe09una"target="_blank" rel="noopener">Signal v. Noise</a>, but the gist is that group chat should be purposeful not yet another tool to create tension. People should definitelly get offline when they need to fully concentrate in their work and have the opportunity to catch up with interesting conversations later. And really important communication should go through e-mail or other tradicional ways, a simple <code>@all</code> don&rsquo;t cut it for company-wide announcements for example.</p>
<p>I hope this exercise gives you more perspective on what you can have and also raise awareness on the need for companies to regain more control over their own data, particularly knowledge. Erase your communication channels and you&rsquo;re losing years-worth of knowledge that can be invaluable.</p>
]]></content:encoded><category>reviews</category><category>devops</category><category>bancos-de-dados</category></item><item><title>How Fast is Elixir/Phoenix?</title><link>https://akitaonrails.github.io/2015/10/27/how-fast-is-elixir-phoenix/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/10/27/how-fast-is-elixir-phoenix/</guid><pubDate>Tue, 27 Oct 2015 16:23:00 -0200</pubDate><description>&lt;p&gt;I like to know what my tools are capable of, because that says how and where I can use them. I will not bore you by stating how awesome Elixir and Erlang are. You&amp;rsquo;ve heard/read it before.&lt;/p&gt;
&lt;p&gt;I will also not bore you with &amp;ldquo;15 minute blog&amp;rdquo; tutorials as there are &lt;a href="https://medium.com/@diamondgfx/introduction-fe138ac6079d#.iemenrs71"target="_blank" rel="noopener"&gt;many&lt;/a&gt; out there already.&lt;/p&gt;
&lt;p&gt;Instead, I&amp;rsquo;d like to show what Phoenix - the Rails-like web framework written in Elixir - is capable of.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I like to know what my tools are capable of, because that says how and where I can use them. I will not bore you by stating how awesome Elixir and Erlang are. You&rsquo;ve heard/read it before.</p>
<p>I will also not bore you with &ldquo;15 minute blog&rdquo; tutorials as there are <a href="https://medium.com/@diamondgfx/introduction-fe138ac6079d#.iemenrs71"target="_blank" rel="noopener">many</a> out there already.</p>
<p>Instead, I&rsquo;d like to show what Phoenix - the Rails-like web framework written in Elixir - is capable of.</p>
<p>You can make the same simulation as I did by repeating the following steps:</p>
<ol>
<li><a href="http://www.phoenixframework.org/docs/installation"target="_blank" rel="noopener">Install</a> Elixir and Phoenix</li>
<li>Generate a skeleton Phoenix app with a simple Post model</li>
<li>Create a Heroku app and add the <a href="http://www.phoenixframework.org/docs/heroku"target="_blank" rel="noopener">necessary buildpacks</a></li>
<li>Deploy the app to Heroku and run the migration</li>
<li>Open a remote console to Heroku and run the following to load the database with sample data:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">1000</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="sx">%{title: &#34;title </span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="sx">&#34;, body: to_string(n)}</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">(</span><span class="n">params</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="no">Blog</span><span class="o">.</span><span class="n">Post</span><span class="o">.</span><span class="n">changeset</span><span class="p">(</span><span class="o">%</span><span class="no">Blog</span><span class="o">.</span><span class="n">Post</span><span class="p">{},</span> <span class="n">params</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">each</span><span class="p">(</span><span class="n">fn</span> <span class="p">(</span><span class="n">changeset</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="no">Blog</span><span class="o">.</span><span class="n">Repo</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="n">changeset</span><span class="p">)</span> <span class="k">end</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>Feeling strange? Read the <a href="https://pragprog.com/book/elixir/programming-elixir"target="_blank" rel="noopener">Programming Elixir</a> book as soon as possible!</p>
<p>If you&rsquo;re a Rails developer, this is the equivalent of doing:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">1000</span><span class="p">)</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span> <span class="no">Post</span><span class="o">.</span><span class="n">create</span> <span class="ss">title</span><span class="p">:</span> <span class="s2">&#34;title </span><span class="si">#{</span><span class="n">n</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="ss">body</span><span class="p">:</span> <span class="n">n</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>Before anyone starts, No: Ruby is not better or Elixir is not worse because of this particular difference in lines of code, do not let this illusion fool you here or in any other language comparison.</p>
<p>Now we can fire up Blitz.io (load testing tool). The default settings of the free plan will simulate increasing the number of concurrent users from 1 all the way to 250. Blitz&rsquo;s default timeout is 1 second which is not a lot for web applications under heavy load, you can increase it up to 20 seconds just to be safe.</p>
<p>We will test the &ldquo;/posts&rdquo; endpoint, which will fetch 1,000 rows from the database (and it will suffer from bottlenecks in query processing time and the restricted amount of concurrent connections that the free Postgresql plan allows).</p>
<p>I ran this simulation several times and this is the result:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/521/Screen_Shot_2015-10-27_at_15.57.30.png" alt="Blitz /posts"  loading="lazy" /></p>
<p>Through the console a single request, rendering those 1,000 rows, is taking around 150ms end to end (fast!):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> [info] GET /posts
 [info] Sent 200 in 152ms</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>But when we put it under heavy traffic the response time go all the way up to 15 seconds. The app keeps up and responds all requests without dropping any of them. It takes more and more time because of the length of the request queue and aforementioned database bottlenecks.</p>
<p>This is expected because we are running on the slowest possible options on Heroku: the free tiers. The web dynos run in shared machines with possible heavy neighbors and the free database does not give any guarantees. So the Phoenix app is having to really struggle to deal with the ridiculous amount of traffic without a lot of resources to back it up. The free Postgresql database is probably the cause of most of the bottlenecks and increase in response time.</p>
<p>For example, if we remove the database from the equation and just return the default homepage (which I tweaked to render an additional random number, in order to know that it&rsquo;s not cached in any way) gives a very different result:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/522/Screen_Shot_2015-10-27_at_16.08.07.png" alt="Blitz homepage"  loading="lazy" /></p>
<p>It&rsquo;s still on the same free small web dyno, and it&rsquo;s rendering and returning a very simple 2.2kb HTML, compared to the previous 560 kb posts page.</p>
<p>It averages out to around 17ms for the entire roundtrip but the actual processing time of the page inside the dyno, as stated by the logs, is this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> [info] GET /
 [info] Sent 200 in 389µs</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>This is <strong>microseconds</strong>, a fraction of a milisecond, which is crazy fast. Response times still bump up a bit and goes all the way to 25ms, which is still very fast. Many things may be responsible for the bumps but I would bet it is heavier neighbors in the same machine making our tests dirty. We can&rsquo;t avoid it in a cloud environment but it&rsquo;s not bad.</p>
<p>Of course, in real world applications I would be careful to not ever have stupid endpoins returning half a megabyte worth of HTML and holding the database waiting for hundreds of rows that could be easily cached in-memory or even in memcached. This is a synthetic, very naive test just to see how far it can go. We just saw 2 extremes: an almost-static tiny page rendering and a stupid heavy rendering. A custom app, correctly done, will fall in between those.</p>
<p>This is speed that can&rsquo;t be ignored. It feels strange in the beginning, but Elixir quickly reminds you of the joys of programming in Ruby for the first time. Combined with the heavyweight industrial strength of Erlang&rsquo;s OTP underpinnings, and the above results, I can easily recommend Phoenix for any number of microservices. It&rsquo;s the best place to start testing in production as soon as possible and check how productive it can be.</p>
<p>I&rsquo;m very interested in exploring OTP more, so you can expect me writing somemore about it in the future.</p>
]]></content:encoded><category>performance</category><category>elixir-e-erlang</category><category>bancos-de-dados</category></item><item><title>[Heroku] ActiveRecord::ConnectionTimeoutError: Problemas de Max Pool Size, Sidekiq e Octopus</title><link>https://akitaonrails.github.io/2015/10/08/heroku-activerecord-connectiontimeouterror-problemas-de-max-pool-size-sidekiq-e-octopus/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/10/08/heroku-activerecord-connectiontimeouterror-problemas-de-max-pool-size-sidekiq-e-octopus/</guid><pubDate>Thu, 08 Oct 2015 15:41:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: o problema pode estar na sua configuração de Octopus!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR 2&lt;/strong&gt;: tem várias &amp;lsquo;contas de padeiro&amp;rsquo; que fiz pelo artigo e algumas coisas que podem não estar 100%, então se você ver alguma coisa que precise melhorar, não deixe de colocar nos comentários! Esse assunto fica mudando de tempos em tempos.&lt;/p&gt;
&lt;p&gt;Você talvez já tenha esbarrado nesta mensagem de erro algumas vezes, especialmente quando sua aplicação está sob alta demanda de tráfego ou rodando workers pesados de Sidekiq:&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>TL;DR</strong>: o problema pode estar na sua configuração de Octopus!</p>
<p><strong>TL;DR 2</strong>: tem várias &lsquo;contas de padeiro&rsquo; que fiz pelo artigo e algumas coisas que podem não estar 100%, então se você ver alguma coisa que precise melhorar, não deixe de colocar nos comentários! Esse assunto fica mudando de tempos em tempos.</p>
<p>Você talvez já tenha esbarrado nesta mensagem de erro algumas vezes, especialmente quando sua aplicação está sob alta demanda de tráfego ou rodando workers pesados de Sidekiq:</p>
<blockquote>
ActiveRecord::ConnectionTimeoutError: could not obtain a database connection within 5 seconds (waited 5.000134325 seconds). The max pool size is currently 5; consider increasing it.
</blockquote>
<p>Antes de mais nada, vale uma pequena explicação. Todo banco de dados tem um limite máximo de número de conexões, num Postgresql que você não fez tuning o default são <a href="https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server"target="_blank" rel="noopener">100 max_connections</a>.</p>
<p>Apps Ruby on Rails implementam a arquitetura de <a href="https://blog.engineyard.com/2014/scale-everything"target="_blank" rel="noopener">&ldquo;shared-nothing&rdquo;</a> onde cada processo Web é independente de estado. O estado fica no banco de dados ou outro storage externo.</p>
<p>Cada instância de uma App Rails vai precisar responder a uma requisição Web. Para conseguir concorrência, precisamos de múltiplas instâncias da App. Podemos conseguir isso de 2 maneiras: via threads (como Puma faz) ou via fork de processos (como Unicorn faz). O <a href="https://devcenter.heroku.com/articles/concurrency-and-database-connections"target="_blank" rel="noopener">Heroku tem uma excelente documentação</a> de como você deve configurar em um desses casos.</p>
<p>No caso de Unicorn, basicamente o número de Workers será o número de conexões que seu banco precisa aceita, um connection pool não serve de muita coisa. Se usar Puma, com threads, daí dentro do processo você configura o connection pool para compartilhar um número limitado de conexões com as diversas threads. Segure este pensamento para a próxima seção.</p>
<p>Resumindo:</p>
<ol>
<li>Se for usar <a href="https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server"target="_blank" rel="noopener">Puma</a>, com Rails 4.1+ ou superior, configure seu <tt>database.yml</tt> assim:</li>
</ol>
<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-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">production</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">url</span><span class="p">:</span><span class="w">  </span><span class="l">&lt;%= ENV[&#34;DATABASE_URL&#34;] %&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pool</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;%= ENV[&#34;DB_POOL&#34;] || ENV[&#39;MAX_THREADS&#39;] || 5 %&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>
<ol start="2">
<li>Se for usar Puma, com Rails inferior a 4.1, adicione o initializer <tt>config/initializers/database_connection.rb</tt>:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Use config/database.yml method if you are using Rails 4.1+</span>
</span></span><span class="line"><span class="cl"><span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">after_initialize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection_pool</span><span class="o">.</span><span class="n">disconnect!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="no">ActiveSupport</span><span class="o">.</span><span class="n">on_load</span><span class="p">(</span><span class="ss">:active_record</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">configurations</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">]</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">                <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">database_configuration</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;pool&#39;</span><span class="o">]</span>              <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;DB_POOL&#39;</span><span class="o">]</span>      <span class="o">||</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;MAX_THREADS&#39;</span><span class="o">]</span> <span class="o">||</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<ol start="3">
<li>Se for usar <a href="https://devcenter.heroku.com/articles/rails-unicorn"target="_blank" rel="noopener">Unicorn</a>, com Rails 4.1+ ou superior, configure o <tt>config/unicorn.rb</tt> adicionando:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">before_fork</span> <span class="k">do</span> <span class="o">|</span><span class="n">server</span><span class="p">,</span> <span class="n">worker</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># other settings</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">defined?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">disconnect!</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">after_fork</span> <span class="k">do</span> <span class="o">|</span><span class="n">server</span><span class="p">,</span> <span class="n">worker</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># other settings</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">defined?</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<ol start="4">
<li>Se for usar Unicorn, com Rails inferior a 4.1+, altere o trecho acima com o seguinte:</li>
</ol>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>after_fork do |server, worker|
  # other settings
  if defined?(ActiveRecord::Base)
    config = ActiveRecord::Base.configurations[Rails.env] ||
                Rails.application.config.database_configuration[Rails.env]
    config[&#39;pool&#39;] = ENV[&#39;DB_POOL&#39;] || 5
    ActiveRecord::Base.establish_connection(config)
  end
end</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>
<h2>Preciso usar Sidekiq<span class="hx:absolute hx:-mt-20" id="preciso-usar-sidekiq"></span>
    <a href="#preciso-usar-sidekiq" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com Sidekiq a coisa começa a ficar mais complicada porque temos que lidar com configuração de Redis também. Tanto um processo Rails web quanto um worker Sidekiq vão precisar falar com o Redis e com um Banco de Dados (dependendo do que é o worker).</p>
<p>Pior ainda, no caso de um processo Web, a idéia é que a requisição seja executada o mais rápido possível, então ele prende a conexão ao banco pelo menor tempo possível, milissegundos. No caso de um worker Sidekiq a idéia é que ele seja usado justamente para as coisas pesadas como importar arquivos grandes e carregar dados no banco, o que vai pendurar a conexão por muito tempo.</p>
<h2>Quantas conexões vou precisar?<span class="hx:absolute hx:-mt-20" id="quantas-conexões-vou-precisar"></span>
    <a href="#quantas-conex%c3%b5es-vou-precisar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um plano Heroku Postgresql Hobby Basic (de USD 9/mês) suporta um máximo de 20 conexões simultâneas.</p>
<p>Na prática, você pode subir 20 workers de Unicorn recebendo até 20 requisições simultâneas. Pode parecer pouco, mas se cada requisição levar 200ms pra processar (que é lento), isso são 5 requisições por segundo por processo. Com 20 processos, isso seriam 100 requisições por segundo ou 360 mil requisições numa hora, que é um número respeitável para qualquer aplicação pequena/média.</p>
<p>Se sua app consumir uns 200mb, isso seriam 2 processos por dyno, ou um máximo de até 10 dynos 1x (de USD 25/mês) para estourar o banco de dados. Essa a conta de padeiro, na prática não é exatamente isso porque penduramos a conexão em tempos diferentes por requisição e não é tão linear assim, mas pra efeitos de ilustração isso deve servir, no geral.</p>
<h2>Como calcular o pool pro Sidekiq<span class="hx:absolute hx:-mt-20" id="como-calcular-o-pool-pro-sidekiq"></span>
    <a href="#como-calcular-o-pool-pro-sidekiq" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com Sidekiq a coisa começa a ficar mais complicada porque temos que lidar com configuração de Redis também. Tanto um processo Rails web quanto um worker Sidekiq vão precisar falar com o Redis e com um Banco de Dados (dependendo do que é o worker).</p>
<p>No caso do Redis, você deve ler <a href="https://github.com/mperham/sidekiq/wiki/Advanced-Options"target="_blank" rel="noopener">esta página do Wiki do Sidekiq</a> e usar esta <a href="http://manuel.manuelles.nl/sidekiq-heroku-redis-calc/"target="_blank" rel="noopener"><strong>calculadora</strong></a> para saber que tipo de plano de serviços como Redis Cloud você vai precisar (sendo o limitante a quantidade máxima de conexões). De acordo com a calculadora, num cenário de 10 dynos, com 2 web threads, 10 workers de Sidekiq, precisaria configurar concorrência do Sidekiq para 21, e ter 23 conexões do lado do servidor de Sidekiq. Comece configurando o <tt>config/sidekiq.yml</tt> 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-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">:concurrency</span><span class="p">:</span><span class="w"> </span><span class="m">21</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 o <tt>config/initializers/sidekiq.rb</tt> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Sidekiq</span><span class="o">.</span><span class="n">configure_server</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">redis</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">url</span><span class="p">:</span> <span class="s1">&#39;redis://redis.example.com:7372/12&#39;</span><span class="p">,</span> <span class="ss">namespace</span><span class="p">:</span> <span class="s1">&#39;mynamespace&#39;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">database_url</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;DATABASE_URL&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">database_url</span>
</span></span><span class="line"><span class="cl">    <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;DATABASE_URL&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">database_url</span><span class="si">}</span><span class="s2">?pool=25&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Note that as of Rails 4.1 the `establish_connection` method requires</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># the database_url be passed in as an argument. Like this:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ActiveRecord::Base.establish_connection(ENV[&#39;DATABASE_URL&#39;])</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <tt>config.redis[:size]</tt> é calculado automaticamente de acordo com o valor de concorrência no <tt>sidekiq.yml</tt> então não precisa adicionar manualmente.</p>
<p>Agora nosso exercício fica mais complicado porque no caso de um processo Web, a idéia é que a requisição seja executada o mais rápido possível, então ele prende a conexão ao banco pelo menor tempo possível, milissegundos. No caso de um worker Sidekiq a idéia é que ele seja usado justamente para as coisas pesadas como importar arquivos grandes e carregar dados no banco, o que vai pendurar a conexão por muito tempo.</p>
<p>Daí a conta que fizemos anteriormente fica ruim. Porque se eu tiver os 10 workers de Sidekiq processando coisas pesadas ao mesmo tempo, e mexendo no banco de dados ao mesmo tempo, acabei de perder 10 das 20 conexões máximas que eu tinha no plano Hobby Basic. Agora ou eu diminuo o número de Web Dynos de 10 para 5 ou aumento o plano do banco de dados ou otimizo para não precisar usar tantas conexões acima.</p>
<h2>Connection Pool<span class="hx:absolute hx:-mt-20" id="connection-pool"></span>
    <a href="#connection-pool" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Para minimizar esses problemas existem connection pools.</p>
<p>No caso do connection pool do Active Record ele existe dentro de uma instância Rails. No caso do Unicorn não serve pra muita coisa. No caso do Puma, que é multi-thread (com <a href="https://github.com/phusion/passenger/wiki/Puma-vs-Phusion-Passenger"target="_blank" rel="noopener">caveats</a>), ajuda um pouco. As threads dentro de um mesmo processo compartilham as conexões nesse pool. Você pode mandar um número de threads maior que o número de conexões no pool, a idéia sendo que dificilmente todas as threads precisam de conexão o tempo todo.</p>
<p>O problema é que cada processo independente de Rails vai ter seu próprio pool. Se usar o padrão de 5 conexões por pool, e tiver 5 Web dynos com Puma, você vai precisar de <em>até</em> 25 conexões ao banco de dados. Isso sem contar o Sidekiq que tem seu próprio connection pool.</p>
<p>Às vezes você não viu que sua configuração ultrapassou o limite do banco porque seu site tem pouco throughput (poucas requisições realmente concorrendo simultaneamente). Mas quando tiver mais concorrência acontecendo, vai começar a sentir os problemas.</p>
<p>Muito rapidamente o problema é que quando você escalar a quantidade de Web ou Worker Dynos vai esbarrar no número máximo de conexões do plano do Postgresql mesmo quando não estiver de fato executando em todas essas conexões, mas elas estão penduradas nos diversos pool, ficando idle, desperdiçando conexões que poderiam estar sendo usadas.</p>
<p>Uma coisa que você pode tentar é adicionar o PgBouncer como a <a href="https://devcenter.heroku.com/articles/concurrency-and-database-connections"target="_blank" rel="noopener">própria documentação do Heroku ensina</a>. Pense nela como uma connection pool que é compartilhada por todos os seus processos de Rails, minimizando o desperdício.</p>
<p>Se estiver usando Rails 4.1 configure seu ambiente para desabilitar prepared statements primeiro:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku config:set PGBOUNCER_PREPARED_STATEMENTS=false</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 estiver usando Rails 4.0 nem tente, não vai funcionar.</p>
<p>Se estiver usando Rails 3.2 adicione o arquivo <tt>config/initializers/database_connection.rb</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;active_record/connection_adapters/postgresql_adapter&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ActiveRecord</span><span class="o">::</span><span class="no">ConnectionAdapters</span><span class="o">::</span><span class="no">PostgreSQLAdapter</span>
</span></span><span class="line"><span class="cl">  <span class="k">alias</span> <span class="n">initialize_without_config_boolean_coercion</span> <span class="kp">initialize</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">logger</span><span class="p">,</span> <span class="n">connection_parameters</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">config</span><span class="o">[</span><span class="ss">:prepared_statements</span><span class="o">]</span> <span class="o">==</span> <span class="s1">&#39;false&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="ss">prepared_statements</span><span class="p">:</span> <span class="kp">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="n">initialize_without_config_boolean_coercion</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">logger</span><span class="p">,</span> <span class="n">connection_parameters</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora para configurar o próprio Heroku execute:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

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

<div><pre><code>web: bin/start-pgbouncer-stunnel bundle exec puma -C config/puma.rb</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>
<h2>Sharding e Octopus<span class="hx:absolute hx:-mt-20" id="sharding-e-octopus"></span>
    <a href="#sharding-e-octopus" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Finalmente, existe outro cenário que eu até recomendo - com prudência. Normalmente quando usamos Sidekiq numa aplicação, eventualmente fazemos Workers pesados para gerar relatórios por exemplo. Um cenário onde você precisa fazer queries pesadas e lentas e você já esgotou todo seu conhecimento de otimização de SQL (criou índices, removeu N+1, denormalizou onde podia, etc).</p>
<p>Quando um worker desses roda, como expliquei antes, a conexão no banco vai ficar travada por muito tempo. Se muitos workers rodarem ao mesmo tempo, você vai engargalar o banco rapidamente, tanto em quantidade de conexões penduradas quanto próprio processamento geral do banco.</p>
<p>Uma solução rápida é criar uma configuração &ldquo;master-slave&rdquo; (ou &ldquo;primary-follower&rdquo;, na nomenclatura mais politicamente correta). No mundo do Heroku isso é feito criando um <a href="https://devcenter.heroku.com/articles/heroku-postgres-follower-databases"target="_blank" rel="noopener">banco &ldquo;Follower&rdquo;</a> que é <strong>read-only</strong> (somente de leitura) e para onde você vai direcionar apenas as queries pesadas!</p>
<p>Para isso você deve configurar a <a href="https://devcenter.heroku.com/articles/distributing-reads-to-followers-with-octopus"target="_blank" rel="noopener">gem Octopus</a> no seu projeto. Em resumo, comece adicionando a gem no seu <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;ar-octopus&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;octopus&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora <strong>Cuidado</strong> até pouco tempo atrás a documentação no Heroku estava defasada mas você deve criar o arquivo <tt>config/shards.yml</tt> com o exato 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="o">&lt;%</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;cgi&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;uri&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">attribute</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">force_string</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">value</span>
</span></span><span class="line"><span class="cl">    <span class="n">value_string</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">force_string</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;&#34;&#39;</span> <span class="o">+</span> <span class="n">value</span> <span class="o">+</span> <span class="s1">&#39;&#34;&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">value</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">: </span><span class="si">#{</span><span class="n">value_string</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">configs</span> <span class="o">=</span> <span class="k">case</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span>
</span></span><span class="line"><span class="cl"><span class="k">when</span> <span class="s1">&#39;development&#39;</span><span class="p">,</span> <span class="s1">&#39;test&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># use dev and test DB as feaux &#39;follower&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="mi">2</span><span class="p">){</span><span class="no">YAML</span><span class="o">::</span><span class="n">load_file</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&#34;config/database.yml&#34;</span><span class="p">))</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">]</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># staging, production, etc with Heroku config vars for follower DBs</span>
</span></span><span class="line"><span class="cl">  <span class="n">master_url</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;DATABASE_URL&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">slave_keys</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">.</span><span class="n">keys</span><span class="o">.</span><span class="n">select</span><span class="p">{</span><span class="o">|</span><span class="n">k</span><span class="o">|</span> <span class="n">k</span> <span class="o">=~</span> <span class="sr">/HEROKU_POSTGRESQL_.*_URL/</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">slave_keys</span><span class="o">.</span><span class="n">delete_if</span><span class="p">{</span> <span class="o">|</span><span class="n">k</span><span class="o">|</span> <span class="no">ENV</span><span class="o">[</span><span class="n">k</span><span class="o">]</span> <span class="o">==</span> <span class="n">master_url</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">slave_keys</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">env_key</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">begin</span>
</span></span><span class="line"><span class="cl">      <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">env_key</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">rescue</span> <span class="no">URI</span><span class="o">::</span><span class="no">InvalidURIError</span>
</span></span><span class="line"><span class="cl">      <span class="k">raise</span> <span class="s2">&#34;Invalid DATABASE_URL&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="s2">&#34;No RACK_ENV or RAILS_ENV found&#34;</span> <span class="k">unless</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;RAILS_ENV&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;RACK_ENV&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;color&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">env_key</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sr">/HEROKU_POSTGRESQL_(.*)_URL/</span><span class="p">)</span><span class="o">[</span><span class="mi">1</span><span class="o">].</span><span class="n">downcase</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;adapter&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="n">scheme</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;adapter&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;postgresql&#34;</span> <span class="k">if</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;adapter&#39;</span><span class="o">]</span> <span class="o">==</span> <span class="s2">&#34;postgres&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;database&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">path</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;username&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="n">user</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;password&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="n">password</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;host&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="n">host</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;port&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">uri</span><span class="o">.</span><span class="n">port</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="s1">&#39;params&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="no">CGI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">query</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">whitelist</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SLAVE_ENABLED_FOLLOWERS&#39;</span><span class="o">].</span><span class="n">downcase</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">)</span> <span class="k">rescue</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl"><span class="n">blacklist</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SLAVE_DISABLED_FOLLOWERS&#39;</span><span class="o">].</span><span class="n">downcase</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;, &#39;</span><span class="p">)</span> <span class="k">rescue</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">configs</span><span class="o">.</span><span class="n">delete_if</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span> <span class="n">whitelist</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">c</span><span class="o">[</span><span class="s1">&#39;color&#39;</span><span class="o">].</span><span class="n">in?</span><span class="p">(</span><span class="n">whitelist</span><span class="p">)</span> <span class="p">)</span> <span class="o">||</span> <span class="p">(</span> <span class="n">blacklist</span> <span class="o">&amp;&amp;</span> <span class="n">c</span><span class="o">[</span><span class="s1">&#39;color&#39;</span><span class="o">].</span><span class="n">in?</span><span class="p">(</span><span class="n">blacklist</span><span class="p">)</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">octopus:
</span></span></span><span class="line"><span class="cl"><span class="s">  replicated: true
</span></span></span><span class="line"><span class="cl"><span class="s">  fully_replicated: false
</span></span></span><span class="line"><span class="cl"><span class="s">  environments:
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;% if configs.present? %&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="s">%= &#34;- #{ENV[&#34;RAILS_ENV&#34;] || ENV[&#34;RACK_ENV&#34;] || Rails.env}&#34; %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;%=</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;RAILS_ENV&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="no">ENV</span><span class="o">[</span><span class="s2">&#34;RACK_ENV&#34;</span><span class="o">]</span> <span class="o">||</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span> <span class="s">%&gt;:
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;% configs.each_with_index do |c, i| %&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="s">%= c.has_key?(&#39;color&#39;) ? &#34;#{c[&#39;color&#39;]}_follower&#34; : &#34;follower_#{i + 1}&#34; %&gt;:
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;%=</span> <span class="n">attribute</span> <span class="s2">&#34;adapter&#34;</span><span class="p">,</span>  <span class="n">c</span><span class="o">[</span><span class="s1">&#39;adapter&#39;</span><span class="o">]</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;%= attribute &#34;database&#34;, c[&#39;database&#39;] %&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="s">%= attribute &#34;username&#34;, c[&#39;username&#39;] %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;%=</span> <span class="n">attribute</span> <span class="s2">&#34;password&#34;</span><span class="p">,</span> <span class="n">c</span><span class="o">[</span><span class="s1">&#39;password&#39;</span><span class="o">]</span><span class="p">,</span> <span class="kp">true</span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;%= attribute &#34;host&#34;,     c[&#39;host&#39;] %&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="s">%= attribute &#34;port&#34;,     c[&#39;port&#39;] %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;% (c[&#39;params&#39;] || {}).each do |key, value| %&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">      &lt;%=</span> <span class="n">key</span> <span class="s">%&gt;: &lt;%= value.first %&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="sx">% end </span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;% end %&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="sx">% else </span> <span class="s">%&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">    - none
</span></span></span><span class="line"><span class="cl"><span class="s">  &lt;% end %&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>Outra coisa, a documentação defasada instrui a criar um <tt>config/initializers/octopus.rb</tt>. Veja esta <a href="https://github.com/tchandy/octopus/issues/317#issuecomment-129480539"target="_blank" rel="noopener">issue no Github</a> para mais detalhes. Em vez disso coloque esta versão simplificada no <tt>config/initializers/octopus.rb</tt> apenas para conseguirmos escolher followers aleatórios (caso tenha mais de 1):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Octopus</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">shards_in</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">].</span><span class="n">try</span><span class="p">(</span><span class="ss">:[]</span><span class="p">,</span> <span class="n">group</span><span class="o">.</span><span class="n">to_s</span><span class="p">)</span><span class="o">.</span><span class="n">try</span><span class="p">(</span><span class="ss">:keys</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">followers</span>
</span></span><span class="line"><span class="cl">    <span class="n">shards_in</span><span class="p">(</span><span class="ss">:followers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">random_follower</span>
</span></span><span class="line"><span class="cl">    <span class="n">followers</span><span class="o">.</span><span class="n">sample</span><span class="o">.</span><span class="n">to_sym</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="o">&lt;&lt;</span> <span class="nb">self</span>
</span></span><span class="line"><span class="cl">    <span class="n">alias_method</span> <span class="ss">:followers_in</span><span class="p">,</span> <span class="ss">:shards_in</span>
</span></span><span class="line"><span class="cl">    <span class="n">alias_method</span> <span class="ss">:slaves_in</span><span class="p">,</span> <span class="ss">:shards_in</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Pequeno adendo, by Gabriel Sobrinho, um dos mantenedores do projeto Octopus que mandou um feedback a este post:</strong></p>
<blockquote>
Na verdade a documentação não está defasada, o cara que documentou usou um recurso chamado slave groups mas que é bem específico.
<p>Slave groups você cria grupos de slaves e na hora de fazer as consultas você especifica qual grupo quer usar, e nessa hora um round-robin é usado para balancear entre os slaves.</p>
<p>Provavelmente se você tiver mais de um follower e quiser fazer round-robin como tu fez com o initializer, slave groups devem se encaixar melhor.</p>
<p>Também note que o initializer da documentação quebrava ao ajustar o shards.yml porque ele tentava acessar os slave groups que não existem mais depois do ajuste que sugeri, por isso precisa remover ele.</p>
</blockquote>
<p>Se você não fizer mais nada, todo model ActiveRecord vai, por padrão, continuar lendo e escrevendo do banco primário. Eu não recomendo colocar o método <tt>replicated_model</tt> às cegas como a documentação básica ensina pois isso manda os writes (insert/update/delete) pro banco primário e reads (select) pro banco follower. Isso pode causar efeitos colaterais estranhos, condições de corrida onde você vai tentar ler alguma coisa que acabou de escrever mas ainda não foi replicado pro follower se for rápido demais.</p>
<p>Em vez disso recomendo separar manualmente onde você quer ler do follower, por exemplo, do worker do Sidekiq você faria:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">User</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">random_follower</span><span class="p">)</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:user_id</span><span class="o">]</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>Isso é o equivalente a fazer <tt>User.find(params[:user_id])</tt> no banco primário, mas vai mandar a query pra um dos seus followers. Daí você pode conectar Relations normalmente para fazer queries pesadas (lembre-se de usar <a href="http://api.rubyonrails.org/classes/ActiveRecord/Batches.html"target="_blank" rel="noopener">#find_each</a> para queries que retornam objetos demais antes de iterar sobre uma coleção grande demais).</p>
<p>Agora vem o pulo do gato que falei no &ldquo;TL;DR&rdquo; acima. Se você usa Sidekiq, pode começar a receber as mensagens de erro que é o título deste post. Você vai notar que o pool padrão de 5 é pequeno pra muitos workers de Sidekiq mas o que fizemos até aqui parece não funcionar, em particular a configuração do <tt>config/sidekiq.rb</tt> que mostramos acima, que sobrescreve a variável padrão <tt>DATABASE_URL</tt> com <tt>?pool=25</tt> (ou outro número, teste sua configuração).</p>
<p>Para passar o valor correto pro connection pool de ActiveRecord do Sidekiq, modifique seu <tt>config/sidekiq.rb</tt> 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Sidekiq</span><span class="o">.</span><span class="n">configure_client</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">redis</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">:size</span> <span class="o">=&gt;</span> <span class="mi">1</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">Sidekiq</span><span class="o">.</span><span class="n">configure_server</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">sidekiq_concurrency</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">options</span><span class="o">[</span><span class="ss">:concurrency</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">config</span>
</span></span><span class="line"><span class="cl">  <span class="n">pool_size</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;SIDEKIQ_DB_POOL&#39;</span><span class="o">]</span> <span class="o">||</span> <span class="n">sidekiq_concurrency</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="no">Octopus</span><span class="o">.</span><span class="n">config</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">][</span><span class="s1">&#39;master&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">merge!</span><span class="p">(</span><span class="ss">pool</span><span class="p">:</span> <span class="n">pool_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="no">Octopus</span><span class="o">.</span><span class="n">config</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">].</span><span class="n">each_key</span> <span class="k">do</span> <span class="o">|</span><span class="n">follower</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="no">Octopus</span><span class="o">.</span><span class="n">config</span><span class="o">[</span><span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">][</span><span class="n">follower</span><span class="o">].</span><span class="n">merge!</span><span class="p">(</span><span class="ss">pool</span><span class="p">:</span> <span class="n">pool_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">initialize_shards</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso porque você precisa modificar a configuração do Octopus e não somente do ActiveRecord diretamente. Fazendo isso a configuração correta de pool de banco deve passar corretamente pros workers de Sidekiq e evitar que você fique sem conexões rápido demais por ter um pool muito pequeno. Mas lembre-se do número máximo de conexões do seu banco de dados, quantos Web Dynos e quantos Worker Dynos você tem.</p>
<p>Este é um processo de tentativa e erro em como sempre, depende da sua aplicação. Mas espero que este post tenha o suficiente para deixar você aquecido no assunto para saber onde procurar. Nunca deixe uma aplicação crítica com tráfego pesado num plano como Hobby Basic, o próximo plano que é o Standard-0 já sobe o máximo de conexões de 20 para 120, o Standard-2 já sobe para 400 conexões. Um follower precisa ser do mesmo plano então ao adicionar um banco follower você dobra a quantidade de conexões, daí pode deixar os Web Dynos pendurados no banco primário e os workers pesados de Sidekiq pendurados nos followers. Com a configuração correta de pools, fica menos complexo de gerenciar assim.</p>
<p>Experimente o PgBouncer logo que puder, coloque um banco follower com Octopus e mande seu Sidekiq ficar separado da Web. Já é um bom começo. E nada substitui queries bem feitas (procure gems como o <a href="https://github.com/flyerhzm/bullet"target="_blank" rel="noopener">Bullet</a> para ajudar a achar N+1) e não esquecer os índices corretos (tente usar a gem <a href="https://github.com/plentz/lol_dba"target="_blank" rel="noopener">lol_dba</a> para achar alguns dos índices faltando - mas cuidado, nem todos são necessários)!</p>
<p>Monitoraramento em produção é essencial! New Relic é obrigatório, <a href="https://devcenter.heroku.com/articles/newrelic#ruby-installation-and-configuration"target="_blank" rel="noopener">não deixe de instalar</a> e acompanhar!</p>
<p>Agradecimentos aos meus companheiros Miners Luiz Varela, André Pereira e Carlos Lopes por dividir muito tempo debugando esses problemas comigo.</p>
]]></content:encoded><category>bancos-de-dados</category><category>rails</category><category>performance</category></item><item><title>[Small Bites] Debugando um Controller pelo Console</title><link>https://akitaonrails.github.io/2015/08/25/small-bites-debugando-um-controller-pelo-console/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/08/25/small-bites-debugando-um-controller-pelo-console/</guid><pubDate>Tue, 25 Aug 2015 15:48:00 -0300</pubDate><description>&lt;p&gt;Hoje fui investigar um problema estranho em produção onde um controller não estava reagindo da mesma forma que em staging (aqueles momentos Murphy!) No final o problema era um SQL insert travando e dando timeout em toda request, problema em infraestrutura no Heroku Postgresql, e não da aplicação, mas para isolar o sintoma o seguinte truque ajudou.&lt;/p&gt;
&lt;p&gt;Uma coisa que eu costumo fazer nesses casos é ligar o Rails Console (&lt;tt&gt;bundle exec rails c&lt;/tt&gt; ou &lt;tt&gt;bin/rails c&lt;/tt&gt;), criar a hash &lt;tt&gt;params&lt;/tt&gt; com os parâmetros iguais ao que a aplicação está recebendo e manualmente executar as linhas da action pra ver os resultados.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Hoje fui investigar um problema estranho em produção onde um controller não estava reagindo da mesma forma que em staging (aqueles momentos Murphy!) No final o problema era um SQL insert travando e dando timeout em toda request, problema em infraestrutura no Heroku Postgresql, e não da aplicação, mas para isolar o sintoma o seguinte truque ajudou.</p>
<p>Uma coisa que eu costumo fazer nesses casos é ligar o Rails Console (<tt>bundle exec rails c</tt> ou <tt>bin/rails c</tt>), criar a hash <tt>params</tt> com os parâmetros iguais ao que a aplicação está recebendo e manualmente executar as linhas da action pra ver os resultados.</p>
<p>Quando é pouca coisa isso ajuda a dar alguns insights. Porém neste caso a aplicação era grande e tinha uma quantidade enorme de <tt>before_action</tt> sendo executados e pra simular o mesmo ambiente eu precisaria executar tudo um atrás do outro. Em vez disso tem outra forma.</p>
<p>Ao abrir o Rails Console ele lhe dá um objeto que representa sua aplicação. Com ele você pode simular sua aplicação diretamente pelo console.</p>
<p>Por exemplo, para chegar ao ponto onde eu queria testar, primeiro precisa estar autenticado na aplicação. No meu caso basta fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>app.get &#34;/users/sign_in&#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>Isso carrega o ambiente pro formulário de login, incluindo o token CSRF que podemos visualizar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>app.session.to_hash
=&gt; {&#34;session_id&#34;=&gt;&#34;8dbf5adfe1738752bc05ce9e6d5ab9fc&#34;, &#34;_csrf_token&#34;=&gt;&#34;wp/0bjiEyRWgCfeBtkuFy&#43;yZ2G/IihC0X1uSafn4noQ=&#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>Basta copiar o <tt>_csrf_token</tt> e usar para submeter o formulário ao controller do Devise:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>app.post &#34;/users/sign_in&#34;, {&#34;utf8&#34;=&gt;&#34;✓&#34;, &#34;authenticity_token&#34;=&gt;&#34;wp/0bjiEyRWgCfeBtkuFy&#43;yZ2G/IihC0X1uSafn4noQ=&#34;, &#34;user&#34;=&gt;{&#34;login&#34;=&gt;&#34;john&#34;, &#34;password&#34;=&gt;&#34;test123&#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>Finalmente, posso executar a URL que eu preciso para ver exatamente o que está acontecendo no ambiente de produção:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>app.get &#34;/rota/que/quero/testar&#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>Isso não é novo, um dos primeiros posts que explica as diversas ferramentas que existem no console é do próprio Signal v Noise, de 2012, <a href="https://signalvnoise.com/posts/3176-three-quick-rails-console-tips"target="_blank" rel="noopener">&ldquo;Three quick Rails console tips&rdquo;</a>, escrito pelo Nick Quaranto. Para coisas rápidas e insights que podem ajudar a debugar e testar melhor sua app, este é um aliado muito poderoso!</p>
]]></content:encoded><category>rails</category><category>ferramentas-de-desenvolvimento</category><category>bancos-de-dados</category></item><item><title>[Heroku Tips] Usando Followers do Heroku Postgresql</title><link>https://akitaonrails.github.io/2014/11/10/heroku-tips-usando-followers-do-heroku-postgresql/</link><guid isPermaLink="true">https://akitaonrails.github.io/2014/11/10/heroku-tips-usando-followers-do-heroku-postgresql/</guid><pubDate>Mon, 10 Nov 2014 19:51:00 -0200</pubDate><description>&lt;p&gt;Uma das grandes vantagens de usar o Heroku é poder rapidamente aumentar o tamanho dos dynos para aumentar a capacidade de requisições simultâneas que sua aplicação pode responder. Porém existe um grande problema: você só vai pode aumentar suas dynos até o limite de conexões do seu banco de dados.&lt;/p&gt;
&lt;p&gt;Para isso veja direito os planos de &lt;a href="https://addons.heroku.com/heroku-postgresql"target="_blank" rel="noopener"&gt;Postgresql&lt;/a&gt;. Para aplicações médias, você provavelmente vai querer algo do tamanho do plano Standard 2, com até 400 conexões simultâneas e 3.5Gb de RAM. Na teoria, você poderia tranquilamente subir mais de 400 instâncias de Ruby (web e worker), considerando o pool de conexões. Na prática isso não deve acontecer, e isso depende muito do perfil da sua aplicação.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das grandes vantagens de usar o Heroku é poder rapidamente aumentar o tamanho dos dynos para aumentar a capacidade de requisições simultâneas que sua aplicação pode responder. Porém existe um grande problema: você só vai pode aumentar suas dynos até o limite de conexões do seu banco de dados.</p>
<p>Para isso veja direito os planos de <a href="https://addons.heroku.com/heroku-postgresql"target="_blank" rel="noopener">Postgresql</a>. Para aplicações médias, você provavelmente vai querer algo do tamanho do plano Standard 2, com até 400 conexões simultâneas e 3.5Gb de RAM. Na teoria, você poderia tranquilamente subir mais de 400 instâncias de Ruby (web e worker), considerando o pool de conexões. Na prática isso não deve acontecer, e isso depende muito do perfil da sua aplicação.</p>
<p>Especialmente considerando aplicações que precisam ou fazer queries muito complexas (relatórios, pré-cálculos que envolvam uma tabela inteira e vários joins) ou então uma aplicação onde escritas (inserts e updates) aconteçam em muito volume (uma aplicação como um Google Spreadsheet ou Twitter-like). Sempre acompanhe o dashboard do Heroku Postgresql para ver quais são os comandos mais lentos. Idealmente cada query deve estar na faixa das dezenas de milissegundos. Quando mais lenta for e quanto mais dados ele precisar carregar em memória, menos dessas você pode fazer concorrentemente no mesmo banco de dados.</p>
<p>Em particular, se você tem muitas tarefas pesadas, então já deveria estar usando workers de <a href="https://github.com/mperham/sidekiq/wiki/Deployment"target="_blank" rel="noopener">Sidekiq</a>. Porém se lembre que esses workers estarão usando o mesmo banco que os dynos web também vão usar. Nesse caso se você rodar uma query estupidamente pesada num worker, a experiência do usuário na web vai ser prejudicada.</p>
<p>Uma solução para isso é usar o recurso de <a href="https://devcenter.heroku.com/articles/heroku-postgres-follower-databases"target="_blank" rel="noopener">followers</a> que já está bem documentado. Quando você cria uma instância de Heroku Postgresql ele vai criar duas variáveis de ambiente baseado numa cor, por exemplo, HEROKU_POSTGRESQL_RED_URL e DATABASE_URL apontando pro endereço da máquina do Postgresql na Amazon AWS EC2.</p>
<p>Um follower é um banco slave do Postgresql, cujo endereço vai ser apontado numa outra variável de ambiente como, por exemplo, HEROKU_POSTGRESQL_AMBER_URL (digite o comando <tt>heroku config</tt> para ver essas variáveis). Um follower sempre vai estar pelo menos alguns milissegundos atrasado em relação ao banco master. Você sempre precisa se lembrar que nunca deve depender de um dado lido de um follower logo depois de ter feito o comando de Insert/Update.</p>
<p>No conceito básico, toda escrita deve ser feita somente no banco master (e ela sempre será gargalo de escrita, caso você tenha volumes muito estúpidos de escrita - e nesse caso vai ter que cair em outras opções que não é escopo deste post). E as leituras podem ser divididas em múltiplos slaves/followers (&ldquo;slave&rdquo; se você for old-school, &ldquo;follower&rdquo; se você for politicamente correto).</p>
<p>Porém uma aplicação Rails normal só sabe do banco que está apontado na variável DATABASE_URL, então como fazer para dividir as leituras no follower? Para isso existe a gem <a href="https://github.com/tchandy/octopus"target="_blank" rel="noopener">Octopus</a>, criada pelo Thiago Pradi e <a href="https://devcenter.heroku.com/articles/distributing-reads-to-followers-with-octopus"target="_blank" rel="noopener">bem documentada</a> pelo próprio Heroku. Resumindo o que já está documentado, comece adicionando a gem no <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;ar-octopus&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;octopus&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Rode o <tt>bundle install</tt> e agora baixe os seguintes arquivos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -L -o config/shards.yml https://gist.github.com/catsby/6923840/raw/0aaf94ccc383951118c43b9b794fc62e427c2e51/shards.yml
curl -L -o config/initializers/octopus.rb https://gist.github.com/catsby/6923632/raw/87b5abba2e22c3acf8ed35d06e0ab9ca1bd9f0d0/octopus.rb</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso feito, em desenvolvimento o master e o follower vão ser o seu banco normal de desenvolvimento. Em produção no Heroku ele vai usar os bancos HEROKU_POSTGRESQL_*_URL que não são o DATABASE_URL como followers.</p>
<p>Abra o arquivo <tt>config/initializers/octopus.rb</tt> e adicione o seguinte método:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">Octopus</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">random_follower</span>
</span></span><span class="line"><span class="cl">    <span class="n">followers</span><span class="o">.</span><span class="n">sample</span><span class="o">.</span><span class="n">to_sym</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que acontece é o seguinte, você pode simplesmente adicionar o seguinte método a um model que queira dividir a carga entre escrita no master e leitura no follower:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span>
</span></span><span class="line"><span class="cl">  <span class="n">replicated_model</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nunca coloque o método acima em <strong>todos</strong> os models indiscriminadamente. Você quer controlar em quais followers quer ler o que. No caso comum basta dividir entre todos os followers, mas caso tenha queries hiper-pesadas e queira dedicar um follower só pra isso, você também pode.</p>
<p>Um caso de uso comum é pegar workers de Sidekiq que façam muitos pré-cálculos, gerem relatórios ou denormalize tabelas grandes e faça 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># query pesada normal:</span>
</span></span><span class="line"><span class="cl"><span class="no">ModelPesado</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># query modificada pra rodar num follower:</span>
</span></span><span class="line"><span class="cl"><span class="no">ModelPesado</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">random_follower</span><span class="p">)</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</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>Sem o método <tt>Octopus.random_follower</tt> você teria que manualmente digitar o nome do follower que quer usar baseado na cor de sua variável de ambiente, por exemplo o HEROKU_POSTGRESQL_AMBER_URL você faria: <tt>.using(:amber_follower)</tt>. Com o método ele vai devolver o único follower que você tem ou aleatoriamente entre a lista de followers que você tem.</p>
<p>Outra forma é 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Octopus</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="no">Octopus</span><span class="o">.</span><span class="n">random_follower</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">ModelPesado</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:abc</span><span class="p">,</span> <span class="ss">:xyz</span><span class="p">)</span><span class="o">.</span><span class="n">scope_pesado</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span><span class="o">.</span><span class="n">find_each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="o">.</span><span class="n">algo_pesado</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uma coisa que parece necessária, como foi no caso de uma <a href="https://github.com/tchandy/octopus/issues/241"target="_blank" rel="noopener">issue do DelayedJob</a> se por algum motivo você precisar rodar um comando como Update dentro de um <tt>find_by_sql</tt>, lembre-se de explicitamente colocá-la no escopo do banco master:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Octopus</span><span class="o">.</span><span class="n">using</span><span class="p">(</span><span class="ss">:master</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Model</span><span class="o">.</span><span class="n">find_by_sql</span><span class="p">(</span><span class="s2">&#34;UPDATE ...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como disse antes, isso não é uma solução para que você jogue tudo pro follower. Avalie no próprio dashboard do seu banco de dados quais são as queries pesadas e no New Relic e comece movendo somente essas queries primeiro. Performance não é algo que você codifica sem medir: senão como saber se o que você fez realmente fez alguma diferença? Meça antes e meça depois. E continue nesse ciclo.</p>
<p>A gem Octopus e o recurso de Followers do Heroku Postgresql são excelentes opções para mover queries pesadas e descarregar seu banco master principal, o que vai garantir que sua aplicação possa escalar muito mais com poucas mudanças de código.</p>
]]></content:encoded><category>bancos-de-dados</category><category>sistemas-distribuidos</category><category>cloud</category></item><item><title>[Heroku Tips] Problemas iniciais com Rails 4 no Heroku</title><link>https://akitaonrails.github.io/2013/09/11/heroku-tips-problemas-iniciais-com-rails-4-no-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/2013/09/11/heroku-tips-problemas-iniciais-com-rails-4-no-heroku/</guid><pubDate>Wed, 11 Sep 2013 17:25:00 -0300</pubDate><description>&lt;p&gt;Se ainda não leu, dê uma olhada sobre o que já postei como &lt;a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener"&gt;dicas de Heroku&lt;/a&gt; e &lt;a href="http://www.akitaonrails.com/2013/05/02/opcoes-de-hospedagem-rails-heroku#.Ui81gWRgZ3Y"target="_blank" rel="noopener"&gt;minha opinião sobre o serviço&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recentemente tentei subir um projeto Rails 4 bem simples no Heroku e encontrei problemas logo na primeira tentativa de deploy. O problema é o seguinte: a forma mais aceita de configurar uma aplicação é usar variáveis de ambiente (&lt;a href="https://github.com/bkeepers/dotenv"target="_blank" rel="noopener"&gt;veja projetos como o dotenv-rails&lt;/a&gt;). No primeiro deploy essas variáveis não estão disponíveis, em particular o &lt;tt&gt;DATABASE_URL&lt;/tt&gt;. Na task &lt;tt&gt;assets:precompile&lt;/tt&gt; não deveria haver nada na inicialização que dependesse de conexão ao banco, mas algumas gems ainda não estão corrigidas dessa forma, em particular duas com esse bug já conhecido são o &lt;a href="https://github.com/gregbell/active_admin/issues/2342#issuecomment-23109359"target="_blank" rel="noopener"&gt;active_admin&lt;/a&gt; e o acts-as-taggable-on.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se ainda não leu, dê uma olhada sobre o que já postei como <a href="http://www.akitaonrails.com/2012/04/20/heroku-tips-enciclopedia-do-heroku"target="_blank" rel="noopener">dicas de Heroku</a> e <a href="http://www.akitaonrails.com/2013/05/02/opcoes-de-hospedagem-rails-heroku#.Ui81gWRgZ3Y"target="_blank" rel="noopener">minha opinião sobre o serviço</a>.</p>
<p>Recentemente tentei subir um projeto Rails 4 bem simples no Heroku e encontrei problemas logo na primeira tentativa de deploy. O problema é o seguinte: a forma mais aceita de configurar uma aplicação é usar variáveis de ambiente (<a href="https://github.com/bkeepers/dotenv"target="_blank" rel="noopener">veja projetos como o dotenv-rails</a>). No primeiro deploy essas variáveis não estão disponíveis, em particular o <tt>DATABASE_URL</tt>. Na task <tt>assets:precompile</tt> não deveria haver nada na inicialização que dependesse de conexão ao banco, mas algumas gems ainda não estão corrigidas dessa forma, em particular duas com esse bug já conhecido são o <a href="https://github.com/gregbell/active_admin/issues/2342#issuecomment-23109359"target="_blank" rel="noopener">active_admin</a> e o acts-as-taggable-on.</p>
<p>No final, a forma mais simples para resolver isso por enquanto é fazer o seguinte antes do primeiro deploy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>heroku labs:enable user-env-compile
heroku config:add DATABASE_URL=$(heroku config | awk &#39;/HEROKU_POSTGRESQL.*:/ {print $2}&#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>Leia a documentação dessa funcionalidade <a href="https://devcenter.heroku.com/articles/labs-user-env-compile"target="_blank" rel="noopener">user-env-compile</a> entendendo que ela não é a forma mais correta, é apenas um facilitador enquanto todas as gems não estão da forma correta.</p>
<h2>Rails 12 Factor<span class="hx:absolute hx:-mt-20" id="rails-12-factor"></span>
    <a href="#rails-12-factor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Rapidamente para não esquecer, no caso de apps Rails 4 não deixe de acrescentar o seguinte na sua <tt>Gemfile</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rails_12factor&#39;</span><span class="p">,</span> <span class="ss">group</span><span class="p">:</span> <span class="ss">:production</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 particular é importante para logging correto e servir assets estáticos, veja no <a href="https://github.com/heroku/rails_12factor"target="_blank" rel="noopener">Github deles</a> para mais informações.</p>
<h2>Migração de MySQL para PostgreSQL<span class="hx:absolute hx:-mt-20" id="migração-de-mysql-para-postgresql"></span>
    <a href="#migra%c3%a7%c3%a3o-de-mysql-para-postgresql" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Outro assunto que deve ser constante quando se considera mudar pra Heroku é ter que usar o Heroku Postgres (que é uma ótima opção). Mas muitos projetos, principalmente mais antigos, devem ter começado em MySQL.</p>
<p>A primeira coisa a fazer é verificar se você tem muitos SQL exclusivos de MySQL, funções e coisas do tipo. Se você usar ActiveRecord Relations padrão, não deveria ter nenhum problema.</p>
<p>O segundo problema é migrar os dados de um banco para o outro. Eu procurei várias opções mas a maioria é antiga e não funciona direito, a melhorzinha que achei foi uma task Rake. Ela tinha alguns probleminhas de usar API deprecada mas resolvi neste aqui:</p>
<script src="https://gist.github.com/akitaonrails/6529088.js"></script>
<p>Basta alterar seu <tt>config/database.yml</tt> para ter 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">development</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adapter</span><span class="p">:</span><span class="w"> </span><span class="l">postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="l">legaltorrents_development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">fred</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">localhost</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">production</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">adapter</span><span class="p">:</span><span class="w"> </span><span class="l">mysql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w"> </span><span class="l">legaltorrents_production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">fred</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">password</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>Coloca o script como <tt>lib/tasks/convert.rake</tt> e executa <tt>rake db:convert:prod2dev</tt>.</p>
<p>Depois disso ainda precisa atualizar as sequences de primary key do PostgreSQL desta forma:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ALTER SEQUENCE users_id_seq restart with (select max(id)&#43;1 from users) </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 deve ser feito para cada tabela que você tem. Se precisar atualizar em produção no Heroku, execute <tt>heroku run rails console</tt> e execute assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;ALTER SEQUENCE users_id_seq restart with (select max(id)+1 from users) &#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>Não esqueça que você pode fazer dumps do banco de dados de produção, colocar num banco de dados local para testar e tudo mais e se quiser pode gerar um dump local e restaurar de novo no Heroku. Leia a documentação deles sobre <a href="https://devcenter.heroku.com/articles/heroku-postgres-import-export"target="_blank" rel="noopener">PG Backups</a>.</p>
<p>Gerar um dump local é simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pg_dump -Fc --no-acl --no-owner -h localhost -U vagrant my_db &gt; mydb.dump</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E restaurar um dump do Heroku no seu banco local também:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pg_restore --verbose --clean --no-acl --no-owner -h localhost -U vagrant -d my_db b078.dump</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 deve resolver a maioria dos problemas.</p>
]]></content:encoded><category>rails</category><category>bancos-de-dados</category><category>devops</category></item><item><title>Quais são algumas das piores práticas para aplicações Ruby on Rails?</title><link>https://akitaonrails.github.io/2013/03/24/quais-sao-algumas-das-piores-praticas-para-aplicacoes-ruby-on-rails--2/</link><guid isPermaLink="true">https://akitaonrails.github.io/2013/03/24/quais-sao-algumas-das-piores-praticas-para-aplicacoes-ruby-on-rails--2/</guid><pubDate>Sun, 24 Mar 2013 12:45:00 -0300</pubDate><description>&lt;p&gt;Alguns dias atrás me pediram para responder no Quora a pergunta &lt;a href="http://qr.ae/TkQ3N"target="_blank" rel="noopener"&gt;&amp;ldquo;Ruby on Rails: Quais são algumas das piores práticas para aplicações Ruby on Rails?&amp;rdquo;&lt;/a&gt;. Foi uma resposta que teve muitos votos positivos então resolvi que seria um bom post para meu blog. Segue minha versão extendida.&lt;/p&gt;
&lt;p&gt;Existem diversas práticas ruins em desenvolvimento de aplicações em geral e em particular em Ruby on Rails. Eu mesmo já cometi e aprendi sobre várias elas na prática e em particular tive que pegar alguns projetos de resgate onde continuo vendo os mesmos erros acontecendo repetidamente, então vamos tentar resumir as principais que mais me irritam.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Alguns dias atrás me pediram para responder no Quora a pergunta <a href="http://qr.ae/TkQ3N"target="_blank" rel="noopener">&ldquo;Ruby on Rails: Quais são algumas das piores práticas para aplicações Ruby on Rails?&rdquo;</a>. Foi uma resposta que teve muitos votos positivos então resolvi que seria um bom post para meu blog. Segue minha versão extendida.</p>
<p>Existem diversas práticas ruins em desenvolvimento de aplicações em geral e em particular em Ruby on Rails. Eu mesmo já cometi e aprendi sobre várias elas na prática e em particular tive que pegar alguns projetos de resgate onde continuo vendo os mesmos erros acontecendo repetidamente, então vamos tentar resumir as principais que mais me irritam.</p>
<p><strong>1)</strong> Primeiro de tudo é a ausência de testes, ou cobertura muito pequena, ou testes que são muito frágeis. Pessoal, isso é uma coisa básica! Se ainda não fazem isso comecem imediatamente a adicionar os malditos testes! Quanto pior for a qualidade e cobertura dos testes proporcionalmente mais caro fica manter e expandir uma aplicação, ponto final. Codificadores que não escrevem testes demonstram muito pouco respeito pela sua própria prática.</p>
<p>Já discutimos ad nauseaum (dica pra começar: <a href="http://pragprog.com/book/achbd/the-rspec-book"target="_blank" rel="noopener">&ldquo;The RSpec Book&rdquo;</a>) sobre testes nos últimos anos então o que não falta é material para aprender e as melhores práticas. Não me importo se os testes são feitos &ldquo;by the book&rdquo; escrevendo testes antes do código ou se os testes são adicionados depois do código, contanto que no final existam testes que eu possa executar e ter segurança de que não estou criando bugs de regressão, quebrando coisas que não deveria. Não custa quase nada adicionar os testes mínimos, então adicionem.</p>
<p>Ultimamente tivemos diversos problemas de segurança, muita gente simplesmente atualizou suas gems para as mais recentes para pegar as correções. Somente quem tinha bons testes viu que algumas dessas correções <a href="http://blog.bugsnag.com/2013/03/20/rails-3-2-13-performance-regressions-major-bugs/"target="_blank" rel="noopener">introduziam regressões</a>. Quem não testou e só atualizou acabou colocando no ar versões com bugs.</p>
<p><strong>2)</strong> Escreva nomes em inglês dentro do seu código. Não me importo se você é brasileiro, italiano, francês ou o que for. Nomes de classes, de variáveis, de métodos, tudo deve ser em inglês. Estamos num mundo globalizado, não é pensar muito longe que amanhã um americano vai mexer no seu código todo em português. Além do problema de consistência: a sintaxe da linguagem, todas as bibliotecas padrão, é tudo em inglês. É uma enorme dissonância cognitiva ter nomes em português no meio. É como você estar lendo uma revista em português com diversos parágrafos em inglês no meio. Não faz nenhum sentido.</p>
<p>E aproveitando outra coisa que muitos não fazem: não é obrigatório usar o sistema de <a href="http://guides.rubyonrails.org/i18n.html"target="_blank" rel="noopener">localização do Rails</a>. Porém no primeiro momento onde você se ver adicionando strings de mensagens dentro de models e controllers, pare! Existe local para isso, e é em <tt>config/locales</tt>. Não tem nada pior do que achar dezenas de mensagens espalhadas por todas as classes.</p>
<p><strong>3)</strong> Hoje existem diversos frameworks e bibliotecas para front-end, seja para stylesheet (Bourbon, Bootstrap, Compass), para javascript (jQuery, Ember, Angular, Backbone), e diversas ferramentas (Sass, Coffeescript). Além disso temos toda uma <a href="http://akitaonrails.com/2012/07/01/asset-pipeline-para-iniciantes"target="_blank" rel="noopener">convenção e estruturas</a> em <tt>app/assets</tt> para organizar tudo issso. Parem de colocar styles e javascript inline espalhados pelas views! É sintoma de preguiça, de falta de conhecimento e tentativa de pular passos fazendo gambiarras. Não faça isso!</p>
<p><strong>4)</strong> Não tente ser mais esperto do que seu ORM (Object Relational Model), no caso o ActiveRecord. Na enorme maioria dos casos, se você estiver usando <tt>find_by_sql</tt> por motivos de otimizar &ldquo;performance&rdquo;, você está fazendo a coisa errada. Pior ainda, em quase todos os casos que já vi essa tentativa de otimizar na mão não só não havia esse ganho como ainda já vi adicionando buracos de segurança, como o famigerado <a href="http://rails-sqli.org"target="_blank" rel="noopener">&ldquo;SQL Injection&rdquo;</a>. Aliás, esse é um tema frequente: otimização prematura e desnecessária adicionando bugs de segurança.</p>
<p>Talvez 1% dos maiores sites do mundo precisem de SQLs realmente específicos otimizados manualmente depois de muitos testes em produção, com volume de dados e métricas bem definidas para provar a diferença. Mas a maioria de nós está nos 99% que não precisa disso: apenas aprenda a usar o ActiveRecord corretamente.</p>
<p>Por exemplo, o problema fazer N+1 queries desnecessariamente, como está explicado no <a href="http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations"target="_blank" rel="noopener">guia oficial de ActiveRecord</a> e que eu copio 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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">clients</span> <span class="o">=</span> <span class="no">Client</span><span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="n">clients</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">client</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">client</span><span class="o">.</span><span class="n">address</span><span class="o">.</span><span class="n">postcode</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse código vai fazer 11 queries no banco. A primeira para buscar 10 clientes e depois no loop para buscar o endereço de cada cliente. Isso é tão óbvio que ninguém deveria estar caindo mais nisso. O correto é fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">clients</span> <span class="o">=</span> <span class="no">Client</span><span class="o">.</span><span class="n">includes</span><span class="p">(</span><span class="ss">:address</span><span class="p">)</span><span class="o">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="n">clients</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">client</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="nb">puts</span> <span class="n">client</span><span class="o">.</span><span class="n">address</span><span class="o">.</span><span class="n">postcode</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesta versão já estamos puxando os endereços ao mesmo tempo que puxamos os clientes, em apenas 2 queries: uma para puxar os clientes e uma única pra puxar todos os endereços de todos os clientes. Não precisou escrever nenhum SQL na mão, bastou saber usar o ActiveRecord corretamente para cair de 11 para 2 queries.</p>
<p>Complementando e enfatizando: aprendam tudo sobre as as APIs do ActiveRecord e como as queries são montadas. Uma coisa horrorosa que já vi diversas vezes foram tentativas de ser &ldquo;esperto&rdquo; e fazer código como este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Subscription</span><span class="o">.</span><span class="n">all</span><span class="o">.</span><span class="n">select</span> <span class="p">{</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span> <span class="n">s</span><span class="o">.</span><span class="n">expired?</span> <span class="p">}</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span> <span class="n">s</span><span class="o">.</span><span class="n">destroy</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>Para ficar claro: isso <strong>NÃO</strong> é &ldquo;usar as funcionalidades funcionais de Ruby&rdquo;. Isso é ser ignorante quanto bancos de dados e ORMs em geral. Só para ficar absolutamente claro, esse código deveria ser assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Subscription</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="ss">expired</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span><span class="o">.</span><span class="n">destroy_all</span>
</span></span><span class="line"><span class="cl"><span class="no">Subscription</span><span class="o">.</span><span class="n">destroy_all</span><span class="p">(</span><span class="ss">expired</span><span class="p">:</span> <span class="kp">true</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>Lembrando que <tt>destroy</tt> permite chamar regras de negócio para cada objeto destruído e se usar <tt>delete</tt> é um comando simples SQL para apagar do banco apenas. Depende o que você pretende fazer ao destruir o objeto. Além disso o campo &ldquo;expired&rdquo; deveria ser um <a href="http://guides.rubyonrails.org/active_record_querying.html#scopes"target="_blank" rel="noopener">escopo</a> dentro do model. Como podem ver, uma simples linha pode ter múltiplas opções dependendo do que se pretende fazer. Novamente não tente ser mais esperto do que o ORM, ele provavelmente já tem tudo que você está imaginando, então não reinvente a roda e leia as APIs.</p>
<p>Outros sintomas básicos que já vi: chamar <tt>save(validate: false)</tt>. Se você está desativando a validação que você ou sua equipe colocou, o que está acontecendo? Existem sim, casos específicos de cargas de dados controlados que talvez possam precisar disso. Na maior parte dos casos não precisa. Na maior parte dos casos é o programador que teve preguiça de entender a aplicação fazendo uma gambiarra pra cumprir sua tarefa de forma &ldquo;rápida&rdquo; e largou uma bomba relógio pra trás (falta de testes esconde erros que esse tipo de ação causa).</p>
<p><strong>5)</strong> Não use um banco de dados NoSQL a menos que você tenha uma explicação técnica objetiva com argumentos que justifiquem a qualquer um de forma incontestável o porquê dessa escolha. Digo isso porque a maioria das vezes que alguém me falou em usar um MongoDB a resposta cai nas linhas do &ldquo;vai que um dia precisamos&rdquo;. E não, a maioria de vocês não precisa de um banco de dados NoSQL. Para começar a maioria sequer entende a diferença entre documentos com schema flexível (MongoDB, CouchDB), estruturas de chave-valor (Redis, Riak), ou estruturas orientadas a coluna (Cassandra).</p>
<p>Primeiro que não sabem que existem esses diferentes tipos, então não sabem qual desses é o que realmente resolve seu problema, finalmente não tem idéia do trabalho que é dar manutenção em produção desse tipo de infraestrutura. Aprendam o seguinte: usem bancos de dados relacionais, de preferência PostgreSQL. Vocês não precisam de mais do que isso. Na maior parte do tempo é a preguiça de entender o modelo entidade-relacional e achar que o modelo NoSQL é &ldquo;mais fácil&rdquo;.</p>
<p>Na minha experiência, ainda não cruzei com um projeto onde a presença de um banco NoSQL fosse indispensável. Em todos os casos que vi, eles acabavam fazendo o que um PostgreSQL faria com as duas mãos nas costas. <em>&ldquo;Ah, mas eu preciso fazer operações de geolocalização&rdquo;</em>. Avaliem o <a href="https://github.com/dazuma/activerecord-postgis-adapter"target="_blank" rel="noopener">PostGIS</a>. <em>&ldquo;Ah, mas eu tenho alguns atritutos dinâmicos que variam, então preciso de uma schema flexível&rdquo;</em>. Não necessariamente, provavelmente o suporte <a href="https://github.com/engageis/activerecord-postgres-hstore"target="_blank" rel="noopener">HStore</a> é tudo que você precisa.</p>
<p>Outros casos básicos: usar o banco de dados de produção pra produzir relatórios pesados. Ou o caso mais básico: ter apenas uma instância de banco para leitura e escrita. Não custa nada fazer o básico: uma instância master que é read-write e outra slave que é read-only. Mande as operações de escrita pra master e queries complicadas que consomem processamente para a slave. Mais do que isso, aprenda que a configuração padrão de todo banco de dados é ruim. Aprenda a fazer o tuning básico.</p>
<p>Em resumo: a maioria dos projetos ruins que peguei demonstravam baixo nível de entendimento tanto de bancos de dados relacionais em geral, baixo conhecimento de SQL (índices pessoal! índices!), e por consequência uso muito pobre do ActiveRecord.</p>
<p><strong>6)</strong> Falando nisso, outra coisa irritante é pegar um projeto que precisa de mais do que um <tt>bundle</tt> e <tt>rake db:migrate</tt> pra começar a funcionar. Seja porque tem um SOLR a ser configurado, um Redis, alguns rake tasks customizados que precisam ser executados, etc. Pessoalmente, existe um arquivo chamado <tt>README</tt> na raíz de todo projeto que não está lá de enfeite! Não vai custar 1 minuto preencher as informações básicas do ambiente nesse arquivo e economizar horas da sua equipe e dos programadores que vão herdar seu sistema no futuro. Ou você mesmo, que vai passar um tempo sem mexer nesse sistema e quando voltar vai ter que se lembrar de todos os detalhes.</p>
<p>Aliás, aprenda o básico de administração de sistemas Unix-like (Linux) antes de sair reclamando que Ruby é lento, que bancos de dados relacionais são lentos, que o framework é inseguro. Como você pode reclamar da segurança do framework se todas as portas dos seus servidores estão abertos publicamente??</p>
<p><strong>7)</strong> E falando em qualidade de código, cobertura de testes e segurança, usem as ferramentas que a comunidade criou e que vão ajudar muito na manutenção dos projetos. O <a href="https://codeclimate.com"target="_blank" rel="noopener">CodeClimate</a> vai checar a qualidade geral do seu código e também da segurança. Cheque o tempo todo, qualquer coisa abaixo de nível 3 é ruim. Configurem seu projeto no <a href="https://travis-ci.org"target="_blank" rel="noopener">Travis-CI</a> e, finalmente, executem o <a href="http://brakemanscanner.org"target="_blank" rel="noopener">Brakeman</a> e atualize essa gem em particular constantemente. Isso vai garantir o mínimo do mínimo do seu projeto.</p>
<p><strong>8)</strong> Eu gosto de usar os gráficos de projetos do Github. Ele conta muito sobre os programadores envolvidos no projeto. E antes que alguém diga, não, não é a comparação de quantidade de linhas de códigos ou quantidade absoluta de commits. É o comportamento individual comparado no tempo. Depois de algumas semanas é possível ver o que seria o &ldquo;normal&rdquo; desse programador. Então você começa a ver que a produtividade começa a cair, especialmente nas primeiras semanas, então próximo à entrega vê que dá picos. Normalmente isso é o programador que começou subestimando as tarefas do projeto e perto da entrega se desespera e começa a vomitar código. Comportamento muito ruim porque isso leva a pular passos como pular testes, escrever código sem cuidado, sem segurança e fazendo tudo que eu falei acima que não era pra fazer.</p>
<p>Quando esse comportamento começa a acontecer você vê o sintoma seguinte: programador reclamando do projeto, reclamando que o gerenciamento está ruim, que o cliente é ruim, que o projeto é ruim. São as desculpas mais comuns de péssimos programadores que cometeram erros e jogam seus erros nos outros. Impressionante como esse comportamento tem alta correlação com o tipo de gráfico que mencionei no Github.</p>
<p>Nessa mesma linha vamos combinar: código que está somente na sua máquina não está &ldquo;pronto&rdquo;. Pronto significa já no repositório, passando todos os testes no Travis-CI, feito deployment em produção e sem gerar bugs de regressão ou bugs de usabilidade que os clientes/usuários descobrem sozinhos na primeira vez que usam a nova versão. Aliás, por que diabos você saiu do escritório sem colocar o código em produção? Não há desculpa para isso. Cansei de ver programadores que &ldquo;esquecem&rdquo; de colocar o código em produção. O que você estava esperando? Um convite formal? Programador que demora pra colocar o código no repositório está fazendo as coisas erradas. Já mandei funcionários embora que depois dizem <em>&ldquo;ah, faltou eu fazer push do código no repositório&rdquo;</em>. O que é isso? Tentativa amadora de fazer código como refém?</p>
<p>Bullshit!</p>
<p>Essas são algumas más-práticas. Existem mais, quais são as piores que você já viu? Comentem e eu vou incorporar as mais interessantes no artigo também.</p>
]]></content:encoded><category>rails</category><category>engenharia-de-software</category><category>bancos-de-dados</category></item><item><title>Ruby on SimpleDB</title><link>https://akitaonrails.github.io/2007/12/16/ruby-on-simpledb/</link><guid isPermaLink="true">https://akitaonrails.github.io/2007/12/16/ruby-on-simpledb/</guid><pubDate>Sun, 16 Dec 2007 10:06:00 -0200</pubDate><description>&lt;p&gt;Bancos de Dados não relacionais é um tema alienígena à grande maioria dos web developers. Na realidade a maioria dos programadores sequer sabe que, o que eles chamam de “banco de dados”, é na realidade um “banco de dados relacional”, ou &lt;a href="http://en.wikipedia.org/wiki/Relational_database_management_system"target="_blank" rel="noopener"&gt;RDBMS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A Amazon recentemente anunciou seu novo serviço &lt;a href="http://www.amazon.com/b/ref=sc_fe_c_1_3435361_1?ie=UTF8&amp;amp;node=342335011&amp;amp;no=3435361&amp;amp;me=A36L942TSJ2AJA"target="_blank" rel="noopener"&gt;SimpleDB&lt;/a&gt; ou seu &lt;em&gt;database in the cloud&lt;/em&gt;. Ele trabalhará em conjunto com o serviço de storage S3 para prover armazenamento e administração de um &lt;strong&gt;banco de dados orientado a documentos&lt;/strong&gt; manipulada através de APIs SOAP. Veja, o SimpleDB não é relacional, não tem schema, portanto não é orientado a colunas.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Bancos de Dados não relacionais é um tema alienígena à grande maioria dos web developers. Na realidade a maioria dos programadores sequer sabe que, o que eles chamam de “banco de dados”, é na realidade um “banco de dados relacional”, ou <a href="http://en.wikipedia.org/wiki/Relational_database_management_system"target="_blank" rel="noopener">RDBMS</a>.</p>
<p>A Amazon recentemente anunciou seu novo serviço <a href="http://www.amazon.com/b/ref=sc_fe_c_1_3435361_1?ie=UTF8&amp;node=342335011&amp;no=3435361&amp;me=A36L942TSJ2AJA"target="_blank" rel="noopener">SimpleDB</a> ou seu <em>database in the cloud</em>. Ele trabalhará em conjunto com o serviço de storage S3 para prover armazenamento e administração de um <strong>banco de dados orientado a documentos</strong> manipulada através de APIs SOAP. Veja, o SimpleDB não é relacional, não tem schema, portanto não é orientado a colunas.</p>
<p><a href="http://www.chadfowler.com/2007/12/16/open-source-competition"target="_blank" rel="noopener">Chad Fowler</a> acabou de reportar que ele procurou no <a href="http://rubyforge.org/"target="_blank" rel="noopener">RubyForge</a> se alguém por acaso já não teria criado algum wrapper para as APIs do SimpleDB e, de fato, elas já existem. Temos os projetos <a href="http://rubyforge.org/projects/aws-simpledb/"target="_blank" rel="noopener">aws-simpledb</a>, o <a href="http://rubyforge.org/projects/aws-sdb/"target="_blank" rel="noopener">aws-sdb</a> e o <a href="http://rubyforge.org/projects/simpledb/"target="_blank" rel="noopener">simpledb</a>. E quem quiser estudar em detalhes o funcionamento das APIs do SimpleDB, deve ver a <a href="http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/GettingStartedGuide/"target="_blank" rel="noopener">documentação</a>? da própria Amazon.</p>
<h3>CouchDB<span class="hx:absolute hx:-mt-20" id="couchdb"></span>
    <a href="#couchdb" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse conceito não é novo, os interessados devem conhecer o <a href="http://www.couchdbwiki.com"target="_blank" rel="noopener">CouchDB</a>. Essencialmente os conceitos são os mesmos. Coincidentemente ambos foram escritos em <a href="http://www.erlang.org/faq/t1.html"target="_blank" rel="noopener">Erlang</a>, claro por causa das características de paralelismo inerentes a essa linguagem.</p>
<p>Não sei quanto ao CouchDB mas o SimpleDB é baseado no framework <a href="http://en.wikipedia.org/wiki/MapReduce"target="_blank" rel="noopener">MapReduce</a> do Google, para suportar processamentos absurdamente paralelos de dados em clusters não-confiáveis.</p>
<p>Segundo este <a href="http://www.automatthew.com/2007/12/amazon-simpledb-and-couchdb-compared.html"target="_blank" rel="noopener">site</a>, estas são as características em comum das duas:</p>
<ul>
<li>Bancos de dados não relacionais</li>
<li>Sem schema</li>
<li>Suporte a replicação de dados</li>
<li>acessados via HTTP</li>
</ul>
<p>E agora, onde eles diferem:</p>
<p>| SimpleDB | CouchDB |
| APIs em SOAP e um pseudo-REST | REST |
| chamadas REST usam apenas GET com parâmetros | chamadas REST usam os verbos GET, POST, PUT, DELETE com as semânticas corretas |
| chamadas especificam o banco de dados, registros, atributos, modificadores com parâmetros de query | chamadas especificam o banco de dados e o registro via URL, com parâmetros de query para modificadores |
| criação, atualização e deleção são atômicas ao nível individual de atributos | criação, atualização e deleção são atômicas |
| todos os dados são considerados strings UTF-8 | suporta todos os tipos de dados JSON (string, number, object, array, true, false, null) |
| automaticamente indexa dados | índices estão no controle do usuário, por ‘views’, definidas com funções Javascript, podem ser armazenadas como documentos, podem ser executadas a qualquer momento, como views temporárias |
| queries são limitadas a 5 segundos, com timeout, definidas com parâmetros de query HTTP, compostas de booleanos e conjuntos de operações com alguns operadores óbvios (=, !=, &gt;, etc) | queries são essencialmente views, com a adição de modificadores (start_key, end_key, count, descending) fornecidas como parâmetros de query HTTP |
| como os valores são string UTF-8, não há opções de ordenação | ordenação é flexível e arbitrariamente complexa, já que é baseada em tipos de dados JSON definidas nas views |
| respostas são em XML | respostas são em JSON |</p>
<p>Se quiser pesquisar um sistema parecido com o serviço SimpleDB, o CouchDB é uma excelente opção. E ela tem bindings para diversas linguagens, incluindo nosso Ruby, através do <a href="http://www.couchdbwiki.com/index.php?title=Getting_Started_with_Ruby"target="_blank" rel="noopener">CouchDb-Ruby</a>.</p>
<p>Copiando o exemplo do seu Wiki, eis como se criaria um banco de dados no CouchDb:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">=</span> <span class="no">Couch</span><span class="o">::</span><span class="no">Server</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="s2">&#34;5984&#34;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="n">server</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="s2">&#34;/foo/&#34;</span><span class="p">,</span> <span class="s2">&#34;&#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>Eis como se criaria um documento:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">=</span> <span class="no">Couch</span><span class="o">::</span><span class="no">Server</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="s2">&#34;5984&#34;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="n">doc</span> <span class="o">=</span> <span class="p">\</span><span class="o">&lt;</span><span class="p">\</span><span class="o">&lt;-</span><span class="no">JSON</span>  
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="ss">:&#34;comment&#34;</span><span class="p">,</span><span class="s2">&#34;body&#34;</span><span class="ss">:&#34;First Post!&#34;</span><span class="p">}</span>  
</span></span><span class="line"><span class="cl"><span class="no">JSON</span>  
</span></span><span class="line"><span class="cl"><span class="n">server</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="s2">&#34;/foo/document_id&#34;</span><span class="p">,</span> <span class="n">doc</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>E finalmente, como se busca um documento:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">=</span> <span class="no">Couch</span><span class="o">::</span><span class="no">Server</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="s2">&#34;5984&#34;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="n">res</span> <span class="o">=</span> <span class="n">server</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/foo/document_id&#34;</span><span class="p">)</span>  
</span></span><span class="line"><span class="cl"><span class="n">xml</span> <span class="o">=</span> <span class="n">res</span><span class="o">.</span><span class="n">body</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>Rails utiliza ActiveRecord, mas podemos facilmente substituir o ActiveRecord da mesma forma como já fazemos com ActiveResource, portanto <a href="http://groups.google.com/group/couchdb/browse_thread/thread/e1eac1af681baae3"target="_blank" rel="noopener">não vejo</a> porque não poderíamos ter algumas aplicações usando CouchDB ou SimpleDB como back-end. Alguém se habilita? :-)</p>
<h3>Divagando pela História<span class="hx:absolute hx:-mt-20" id="divagando-pela-história"></span>
    <a href="#divagando-pela-hist%c3%b3ria" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse assunto me interessa porque diferente da geração atual mais jovem de programadores, eu comecei com banco de dados não relacionais há quase <strong>20 anos</strong>. Conheci programadores que lidaram com o supra-sumo dos bancos de dados. Se você perguntar a qualquer jovem programador quais os melhores bancos de dados do mundo ele dirá: Oracle – e se for um xiita open source, talvez PostgreSQL. Não que esse bancos não sejam bons, pelo contrário. Porém, ainda são bebês na infância se compararmos com os bancos de dados realmente robustos que rodam há <strong>40 anos</strong> ininterruptos como o <a href="http://en.wikipedia.org/wiki/Information_Management_System"target="_blank" rel="noopener">IMS</a> da IBM. Todo mundo já ouviu falar de COBOL, mas eis o back end com o qual ele lida: um banco de dados hierárquico não-relacional (e não vamos esquecer do <a href="http://en.wikipedia.org/wiki/CICS"target="_blank" rel="noopener">CICS</a>, claro, mas gerenciadores transacionais são outro assunto, e o único com que trabalhei foi o <a href="http://en.wikipedia.org/wiki/Tuxedo_%28software%29"target="_blank" rel="noopener">Tuxedo</a>).</p>
<p>Tenho um grande amigo, ex-IBM, que lidou diretamente com esse tipo de sistema. Ele não vê graça nenhuma no que nós fazemos hoje. O que sempre me dizia: <em>nós já fazíamos essas coisas 20 ou 30 anos atrás.</em> Quer dizer, não sei porque tanta empolgação em conceitos que já são tão velhos. Muita gente não sabe, mas a IBM dos anos 60 e 70 criou basicamente tudo que conhecemos hoje como “tecnologia moderna”: sistemas distribuídos, sistemas operacionais com compartilhamento de tempo, gerenciadores transacionais, bancos de dados hierárquicos, bancos de dados relacionais, e-mail, etc.</p>
<p>Inovação <strong>científica</strong> (não trivialidades de consumo) necessariamente vem de laboratórios bem estabelecidos e financeiramente fortes. IBM, AT&amp;T, Bell Labs, universidades como Berkeley, MIT, vêm à mente. Basicamente a computação moderna saiu desses institutos.</p>
<p>Também conheci muitos consultores de outras tecnologias que ainda existem e se alguém trabalhar em algum grande banco, ou agências governamentais, talvez ainda encontre o bom e velho banco de dados <a href="http://en.wikipedia.org/wiki/Adabas"target="_blank" rel="noopener">Adabas</a>. Ele provavelmente foi o primeiro banco de dados comercial, criado em 1970. Mas o nome mais conhecido é a linguagem para manipular seus dados, o <a href="http://en.wikipedia.org/wiki/NATURAL"target="_blank" rel="noopener">NATURAL</a>. Já recebi algumas propostas de Natural alguns anos atrás. Esse produto é da Software AG, empresa alemã que fornecia o banco para ninguém menos que a SAP AG (se não me engano, as duas empresas ficavam em lados opostos da mesma rua). Então, a SAP adquiriu o Adabas, rebatizando-o de SAP DB. Até que recentemente esse banco se tornou o <a href="https://www.sdn.sap.com/irj/sdn/maxdb"target="_blank" rel="noopener">MaxDB</a>, que hoje faz parte da família <a href="http://www.mysql.com/sap/"target="_blank" rel="noopener">MySQL</a>, e este nome sim, garanto que os jovens de hoje reconhecem.</p>
<p>Outra plataforma que poucos ouvem falar mas vira e mexe eu vejo ou recebo proposta de trabalho: o banco de dados orientado a objetos <a href="http://en.wikipedia.org/wiki/Cach%c3%a9_%28software%29"target="_blank" rel="noopener">Caché</a>. Sua principal características: dizem que é o banco mais rápido do mundo (até agora), com seu sistema de estruturas de dados hierárquico em arrays multidimensionais. Nunca notei se havia um nicho de mercado que prefere Caché, mas a <a href="http://www.intersystems.com"target="_blank" rel="noopener">InterSystem</a> tem outro produto construído sobre o Caché chamado Ensemble. Recentemente participei de um projeto que substitui o Ensemble de uma grande telecom para SAP.</p>
<p>Num outro nicho, o mercado de saúde, hospitais, além do Ensemble outro produto que fazia sucesso na sua época foi o bom o velho <a href="http://en.wikipedia.org/wiki/MUMPS"target="_blank" rel="noopener">MUMPS</a>. Conheci um consultor de MUMPS uma vez que migrou para SAP. Pensem assim: MUMPS veio <strong>ANTES</strong> de C, portanto, diferente das linguagens imperativas atuais, ele não é inspirado em C. A sintaxe parece meio alienígena mas é interessante dar uma olhada.</p>
<p>Meu primeiro ‘banco de dados’, obviamente, foi em Basic. Claro, o mais rudimentar de todos os bancos: uma estrutura de dados de tamanho fixo, um arquivo binário, e navegaçao baseada em offset a partir do tamanho da estrutura, mais um pequeno índice para navegar mais rapidamente. Com 11 ou 12 anos, não se pode exigir mais do que isso. E eu não tinha acesso a grandes mainframes, claro, então IMS era algo que eu só ouviria falar anos depois.</p>
<p>Mas rapidamente migrei para <a href="http://en.wikipedia.org/wiki/DBASE"target="_blank" rel="noopener">dBase</a> III, da <a href="http://en.wikipedia.org/wiki/Ashton-Tate"target="_blank" rel="noopener">Ashton_Tate</a>. O pessoal que desenvolveu o dBase, da <a href="http://en.wikipedia.org/wiki/Jet_Propulsion_Laboratory"target="_blank" rel="noopener">Jet Propulsion Labs</a> (NASA) fez por brincadeira, para rodar sobre CP/M e depois vendeu para a Ashton-Tate. Para quem não se lembra, o antigo MS-DOS é um clone (mal-feito, claro) de CP/M. Naquela época tínhamos vários clones, como PC-DOS e DR-DOS. Coisas que gostava dessa época: eu não tinha nem idéia do que eram estrutura relacional, nem estrutura hierárquica. Para mim haviam apenas tabelas (DBF) e índices (NDX, IDX). Sabia existia redes locais, token ring, compartilhamento de arquivos via Netware, IPX/SPX e que DBFs tinham locks baseadas em tabela para que várias pessoas pudessem utilizar <em>quase</em> ao mesmo tempo. Também, sabia que essas estrutura tendiam a se corromper com extrema facilidade e me perguntava como elas conseguiam funcionar :-)</p>
<p>Rapidamente migrei para Clipper Autumn 86 (que estava no fim e eu lembro que tinha sérias limitações de memória no linkeditor, o limite de 500kb) e para o Clipper Summer 87, da Nantucket. O <a href="http://en.wikipedia.org/wiki/Clipper_programming_language"target="_blank" rel="noopener">Clipper</a> começou como um compilador de dBase III. Fiz muitos sisteminhas em Clipper e no começo dos anos 90 migrei para <a href="http://www.foxprohistory.org/tableofcontents.htm#how_it_started"target="_blank" rel="noopener">FoxPro</a>. Nessa época esse mercado ficou meio conturbado, a Ashton-Tate foi adquirida pela Borland, a Nantucket foi absorvida pela Computer Associates e a Fox foi para as asas da Microsoft. O Windows ainda estava apenas começando a se popularizar, todas elas lançaram algum produto gráfico, como o Visual dBase, CA-Visual Objects e o Visual FoxPro, respectivamente.</p>
<p>Foi quando comecei a largar o mercado baseado em derivados de dBase e foi para Pascal e Delphi. Mas isso é outra história ;-)</p>
]]></content:encoded><category>bancos-de-dados</category><category>ruby</category></item><item><title>Quick and Clean</title><link>https://akitaonrails.github.io/2007/02/21/quick-and-clean/</link><guid isPermaLink="true">https://akitaonrails.github.io/2007/02/21/quick-and-clean/</guid><pubDate>Wed, 21 Feb 2007 04:50:38 -0200</pubDate><description>&lt;p&gt;&lt;img src="https://akitaonrails.s3.amazonaws.com/files/ror_eXchange_logo.jpg" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Dia 9 de fevereiro aconteceu o evento &lt;a href="http://www.skillsmatter.com/rorexchange"target="_blank" rel="noopener"&gt;RoR eXchange 2007&lt;/a&gt;, da &lt;a href="http://www.skillsmatter.com/"target="_blank" rel="noopener"&gt;Skills Matter&lt;/a&gt;, co-organizadora da última &lt;a href="http://www.akitaonrails.com/2007/01/18/rails-1-2-admira%C3%A7%C3%A3o-rest-festival-http-e-celebra%C3%A7%C3%B5es-utf-8"target="_blank" rel="noopener"&gt;RailsConf&lt;/a&gt;. Foi um evento de um dia que reuniu alguns bons palestrantes para discutir os últimos desenvolvimentos no mundo RoR.&lt;/p&gt;
&lt;p&gt;Neste &lt;a href="http://skillsmatter.com/menu/479"target="_blank" rel="noopener"&gt;link&lt;/a&gt; vocês podem assistir a todas as palestras. Ainda não vi todas mas gostei muito da apresentação de Chad Fowler. Parte dela tem a ver com minha coluna de Janeiro na RubyOnBr – &lt;a href="http://www.rubyonbr.org/articles/2007/01/18/a-dieta-dos-controllers/"target="_blank" rel="noopener"&gt;A Dieta dos Controllers&lt;/a&gt; – onde tentei clarear uma dúvida constante: &lt;em&gt;“ok, sei que Rails é MVC, mas onde diabos coloco minha lógica de negócios?”&lt;/em&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://akitaonrails.s3.amazonaws.com/files/ror_eXchange_logo.jpg" alt=""  loading="lazy" /></p>
<p>Dia 9 de fevereiro aconteceu o evento <a href="http://www.skillsmatter.com/rorexchange"target="_blank" rel="noopener">RoR eXchange 2007</a>, da <a href="http://www.skillsmatter.com/"target="_blank" rel="noopener">Skills Matter</a>, co-organizadora da última <a href="http://www.akitaonrails.com/2007/01/18/rails-1-2-admira%C3%A7%C3%A3o-rest-festival-http-e-celebra%C3%A7%C3%B5es-utf-8"target="_blank" rel="noopener">RailsConf</a>. Foi um evento de um dia que reuniu alguns bons palestrantes para discutir os últimos desenvolvimentos no mundo RoR.</p>
<p>Neste <a href="http://skillsmatter.com/menu/479"target="_blank" rel="noopener">link</a> vocês podem assistir a todas as palestras. Ainda não vi todas mas gostei muito da apresentação de Chad Fowler. Parte dela tem a ver com minha coluna de Janeiro na RubyOnBr – <a href="http://www.rubyonbr.org/articles/2007/01/18/a-dieta-dos-controllers/"target="_blank" rel="noopener">A Dieta dos Controllers</a> – onde tentei clarear uma dúvida constante: <em>“ok, sei que Rails é MVC, mas onde diabos coloco minha lógica de negócios?”</em>.</p>
<p>Outra parte tem a ver com o que já mostramos sobre o suporte <a href="http://www.akitaonrails.com/category/rails-1-2"target="_blank" rel="noopener">REST</a> no novo Rails 1.2. Mas o ponto alto da palestra é um aviso (muitos não vão gostar, mas vamos lá): Ruby e Rails são anunciados como “linguagem simples”, “framework fácil”, “mais fácil e rápido que Java”, “mais simples de aprender”, etc. Tudo isso dá uma extrema falsa impressão de que qualquer peão (sem ser pejorativo, mas já sendo), que nunca programou na vida, estará desenvolvendo um Yahoo.com amanhã. Novamente, cuidado, essa é a falácia da <a href="http://www.skepdic.com/refuge/ctlessons/lesson1.html"target="_blank" rel="noopener">causa e efeito</a>: porque RoR é fácil, <em>qualquer um</em> aprende fácil.</p>
<p><a href="http://en.wikipedia.org/wiki/Edgar_F._Codd"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/files/200px-Edgar_F_Codd.jpg" alt=""  loading="lazy" /></a></p>
<p>Vamos consertar isso: <em>‘Ruby e Rails são tecnologias muito simples para todos que <strong>JÁ SÃO</strong> bons programadores hoje’</em>. Nos últimos tempos Chad teve a oportunidade de ver muitos códigos de terceiros, muitos deles códigos de novatos. O que ele viu deve horrorizar qualquer um: Rails não protege a aplicação de um mau programador.</p>
<p>Como disse Chad, Java e C# são tecnologias que foram desenvolvidas para programadores idiotas. Calma! Não quer dizer que quem programa em Java é idiota mas sim que ela foi pensada em termos de dar uma série de <em>proteções</em> para evitar erros banais. É isso que faz o compilador: <em>protege</em> seu código contra erros triviais de tipos (mas <em>apenas</em> contra erros triviais), por exemplo, o tipo de erro que um novato cometeria.</p>
<p>Ruby não tem essas proteções. Linguagens como Ruby, Python, Haskell, O’Caml, foram feitas para bons programadores, aqueles que sabem exatamente o que estão fazendo. Rails é uma excelente ferramenta <em>apenas</em>, e <em>somente apenas</em>, àqueles que se deram ao trabalho de estudar como ela funciona internamente.</p>
<p><a href="http://en.wikipedia.org/wiki/Alan_Kay"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/files/kay-alan.jpg" alt=""  loading="lazy" /></a></p>
<p>O exemplo de Chad é muito bom: em uma boa tecnologia de ORM como o Active Record do Rails ou mesmo um Hibernate em Java esconde boa parte do SQL que antes era feito manualmente. Agora imagine que você buscou uma lista de usuários do seu banco (find :all), colocou essa lista para iterar em um loop (.each) e está analisando as permissões de cada usuários para mostrar em uma tela. Você acabou de cair na armadilha conhecida como <strong>1 + N</strong> , ou seja, se sua lista de usuários tinha 200 usuários, você vai fazer pelo menos mais 200 queries no banco para buscar as permissões de cada um. Eu discuto isso no meu livro e mostro a diretiva :include dos finders, que é uma das possíveis soluções nesse caso.</p>
<p>Você só sabe onde, quando e como usar uma técnica como <a href="http://ruby.about.com/b/a/000070.htm"target="_blank" rel="noopener">:include</a> se souber exatamente o que o ActiveRecord fará no seu banco, como queries SQL se comportam, o que é um left outer join. Se não tiver nenhum desses conhecimentos, trará um impacto que poderia ser mortal à sua aplicação no médio prazo. Outro exemplo é uma homepage “ocupada” (cheia de pequenas queries e informações dinâmicas, uma página não-Web 2.0, digamos) que também terá um impacto enorme caso o programador não tenha noção das diversas técnicas de caching do Rails (outro assunto que também discuto no meu livro).</p>
<p>Em outras palavras: nem Ruby nem Rails protegerá uma aplicação de um programador folgado. Quando um bom programador (que já tem experiência em bancos de dados, em otimização do modelo request-response do HTTP, em configuração de servidores web como Apache, em ORM, em MVC, em Design Patterns, etc) se encontra com Ruby on Rails, temos um excelente casamento. Quando um programador que não investiu o devido tempo a estudar todos esses assuntos se encontra com Ruby on Rails, temos um desastre. E isso não é só com Rails. Coloque um programador que não tem o costume de se auto atualizar como deveria em qualquer plataforma de desenvolvimento e sempre teremos um desastre.</p>
<p><a href="http://en.wikipedia.org/wiki/Niklaus_Wirth"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/files/cernpeo4_5-04.jpg" alt=""  loading="lazy" /></a></p>
<p>Significa que não há salvação para os novatos? Claro que não, todo programador sênior já foi um júnior. A diferença é que o primeiro investiu cada hora livre de seu tempo em estudo. E eu quero dizer estudo com perspectiva, não um estudo míope de ferramentas, mas sim um estudo amplo de tecnologias. Por que o HTTP é como é? Por que SQL é como é? Por que o que chamamos de MVC atual é como é? Quais as melhores formas de usar todas essas tecnologias em conjunto? Inclusive essa é a linha mestra que guia meu livro: um livro que apenas o leva passo a passo do estágio 1 ao 2, não ensina nada. <em>“Para buscar todas as linhas da tabela, digite find(:all)”</em>. Esse é o tipo de ensinamento que torna possíveis bons programadores em programadores folgados. Ele não diz o principal: “por que”, “quais as alternativas”, “o que acontece por baixo”, “quais minhas opções”.</p>
<p>Por muito tempo associamos programação a dois estilos: <em>quick’n dirty</em> (rápido e sujo) ou <em>slow’n clean</em> (lento e limpo), ou seja, se você programar rápido demais, sem pensar muito, apenas cuspindo código, terá algo sujo, difícil de dar manutenção e cheio de bugs. Para ter um sistema estável, com código razoavelmente simples de dar manutenção futura, é preciso planejar de antemão tudo que fará e ir muito devagar. A nova geração Agile de desenvolvimento postula que é possível sim, ser <strong>quick’n clean</strong> , ou seja rápido e limpo. Mas algo que está implícito é: para ser rápido e limpo é preciso ser esforçado, auto-didata e estar sempre experimentando técnicas e tecnologias.</p>
<p>Ninguém consegue ser quick’n clean em qualquer plataforma, linguagem ou tecnologia, mesmo tendo Q.I. de Einstein. Experiência não se herda nem se compra: se adquire, com esforço, treinamento contínuo e de qualidade. Para um jogador profissional de basquete, fazer cestas de três pontos é algo quase trivial. Para um principiante é a coisa mais difícil do mundo, a diferença é que o primeiro provavelmente treinou esse lance por várias horas, durantes vários dias, em vários anos a ponto disso se tornar trivial. É um conceito que vale para qualquer coisa, não apenas programação: quanto mais treinamos, mais as coisas difíceis se tornam simples e podemos partir para treinar algo mais difícil.</p>
<p><a href="http://en.wikipedia.org/wiki/Donald_Knuth"target="_blank" rel="noopener"><img src="https://akitaonrails.s3.amazonaws.com/files/donald_knuth7.jpg" alt=""  loading="lazy" /></a></p>
<p>Lembre-se: David Hansson, Marcel Molina, Sam Stepherson, Miguel De Icasa, Guido van Rossum, Rasmus Lerdorf, Larry Wall, Anders Hejlsberg, Alan Cox e mesmos outros célebres como Bjarne Stroustrup, Dennis Ritchie, Alan Kay, Niklaus Wirth, Donald Knuth e centenas de outros. Acha que está nos genes? Um bom programador nasce geneticamente bem dotado? A diferença entre um programador ruim e estes nomes é a quantidade de esforço investido em estudo, pesquisa e experimentação. Não existe fórmula mágica, curso à jato de 6 meses, voodoo, que substitua o puro e simples suor.</p>
<p>Enfim, Chad repete o que eu disse na minha coluna: lógica de negócios, SQL, operações no banco, devem estar nos models. Os controllers devem ser modelados na filosofia REST (que não é coisa nova) e simular operações CRUD (atenção aos models implícitos na forma de tabelas de relacionamento many-to-many). Leia meu livro, leia os artigos na categoria <a href="http://www.akitaonrails.com/category/rails-1-2"target="_blank" rel="noopener">Rails 1.2</a> neste blog, assine os feeds de sites como <a href="http://www.rubyinside.com/"target="_blank" rel="noopener">Ruby Inside</a> e outras dezenas de ótimos blogs como do <a href="http://drnicwilliams.com/"target="_blank" rel="noopener">Dr. Nic</a>, <a href="http://ryandaigle.com/"target="_blank" rel="noopener">Ryan’s Scraps</a>, etc. E principalmente: <strong>estude</strong> , mas estude muito, constantemente e sempre. Não existe ponto final em tecnologia, ninguém jamais poderá dizer <em>“agora já sei tudo que preciso”</em>, qualquer um que acha isso ainda não passa de um principiante.</p>
]]></content:encoded><category>rails</category><category>ruby</category><category>bancos-de-dados</category></item><item><title>David Hansson e Opinionated Software</title><link>https://akitaonrails.github.io/2006/12/05/david-hansson-e-opinionated-software/</link><guid isPermaLink="true">https://akitaonrails.github.io/2006/12/05/david-hansson-e-opinionated-software/</guid><pubDate>Tue, 05 Dec 2006 07:38:37 -0200</pubDate><description>&lt;p&gt;Enviei ontem ao Ronie minha coluna de dezembro ( &lt;strong&gt;link atualizado – 06/12&lt;/strong&gt; ) para a RubyOnbr entitulada &lt;a href="http://rubyonbr.org/articles/2006/12/06/rails-sucesso-pela-arrogncia/"target="_blank" rel="noopener"&gt;&lt;strong&gt;Rails: Sucesso pela Arrogância?&lt;/strong&gt;&lt;/a&gt; que dá as bases para este artigo que David &lt;a href="http://www.loudthinking.com/arc/000516.html"target="_blank" rel="noopener"&gt;postou no Loud Thinking&lt;/a&gt;. Quem ainda não ouviu falar ou não sabe o que é “Opinionated Software” deve ler minha coluna primeiro para entender isso. Vamos ao artigo:&lt;/p&gt;
&lt;h4&gt;Escolha uma única camada de esperteza&lt;span class="hx:absolute hx:-mt-20" id="escolha-uma-única-camada-de-esperteza"&gt;&lt;/span&gt;
&lt;a href="#escolha-uma-%c3%banica-camada-de-esperteza" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Christopher Petrilli repete um frequente mal-entendido sobre Active Record, o ORM de Rails, em &lt;a href="http://blog.amber.org/2005/09/27/least-common-denominator/"target="_blank" rel="noopener"&gt;Menor Denominador Comum&lt;/a&gt;. O raciocínio diz que o MySQL está nos segurando de tomar vantagem de funcionalidades mais avançadas dos bancos de dados disponíveis no PostgreSQL, Oracle e outros. E que se pelo menos MySQL fosse mais esperto, tivesse mais funcionalidades, estaríamos abrançando-as de braços abertos. &lt;em&gt;Errado&lt;/em&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Enviei ontem ao Ronie minha coluna de dezembro ( <strong>link atualizado – 06/12</strong> ) para a RubyOnbr entitulada <a href="http://rubyonbr.org/articles/2006/12/06/rails-sucesso-pela-arrogncia/"target="_blank" rel="noopener"><strong>Rails: Sucesso pela Arrogância?</strong></a> que dá as bases para este artigo que David <a href="http://www.loudthinking.com/arc/000516.html"target="_blank" rel="noopener">postou no Loud Thinking</a>. Quem ainda não ouviu falar ou não sabe o que é “Opinionated Software” deve ler minha coluna primeiro para entender isso. Vamos ao artigo:</p>
<h4>Escolha uma única camada de esperteza<span class="hx:absolute hx:-mt-20" id="escolha-uma-única-camada-de-esperteza"></span>
    <a href="#escolha-uma-%c3%banica-camada-de-esperteza" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Christopher Petrilli repete um frequente mal-entendido sobre Active Record, o ORM de Rails, em <a href="http://blog.amber.org/2005/09/27/least-common-denominator/"target="_blank" rel="noopener">Menor Denominador Comum</a>. O raciocínio diz que o MySQL está nos segurando de tomar vantagem de funcionalidades mais avançadas dos bancos de dados disponíveis no PostgreSQL, Oracle e outros. E que se pelo menos MySQL fosse mais esperto, tivesse mais funcionalidades, estaríamos abrançando-as de braços abertos. <em>Errado</em>.</p>
<p>Active Record é <a href="http://www.oreillynet.com/pub/a/network/2005/08/30/ruby-rails-david-heinemeier-hansson.html?page=1"target="_blank" rel="noopener">opinionated software</a>, assim como o resto de Rails. Essa é uma questão de opinião, não restrições. E a opinião é a seguinte: eu não <em>quero</em> meu banco de dados mais esperto! Mantenha <a href="http://en.wikipedia.org/wiki/HOMR"target="_blank" rel="noopener">esses crayons</a> firmes no lugar, por favor.</p>
<p>Ao contrário de Cristopher, eu considero que stored procedures e constraints são vilãs e destruidores imprudentes da coerência. Não, Sr. Banco de Dados, você não pode ter minha lógica de negócios. Suas ambições procedurais não darão frutos e só terá minha lógica sobre meu morto e frio cadáver orientado a objetos.</p>
<p>Antes que o lado induzido por DBA do seu cérebro exploda com essa afirmação, por favor leia o artigo de Martin Fowler sobre <a href="http://martinfowler.com/bliki/DatabaseStyles.html"target="_blank" rel="noopener">a diferença entre bancos de dados de aplicação e de integração</a>. E perceba que minhas opiniões estão confinadas em lidar com bancos de dados de aplicação (e que fazer integração pelo banco de dados pertence a um tempo quando Barrados no Baile ainda era um sucesso na TV). Com sorte isso deve ter acalmado vocês novamente.</p>
<p>Em outras palavras, quero uma camada única de esperteza: meu domain model. Orientação a objetos tem tudo a ver com encapsulamento de esperteza. Deixá-lo metade espalhado pelo banco de dados é uma terrível violação dessas nobres intenções. E eu não quero fazer parte disso.</p>
<p>Dito tudo <em>isso</em>: vá com o que resolver seu problema. Active Record surpreedentemente perdoa suas transgressões. Isso enquanto não estiver apostando todas as suas fichas na esperança de que nós mudaremos as coisas quando MySQL “crescer” e adicionar todas essas Funcionalidades Enterprise para se tornar maior e melhor do que um “projeto de brinquedo”. Você morrerá pobre, estou lhe dizendo.</p>
<p><strong>Atualização</strong> : Alex Bunardzic tem uma excelente sequência entitulada <a href="http://lesscode.org/2005/09/29/should-database-manage-the-meaning/"target="_blank" rel="noopener">Devem os Bancos de Dados Gerenciar o Significado?</a> – fique ligado.</p>
<p><strong>nota do Akita 1</strong> : calma, Rails funciona com a grande maioria dos melhores bancos de dados do mercado. PostgreSQL, Oracle, DB2, SQL Server, etc. O ponto do David é que se você depende de muitos dos recursos como stored procedures, terá muito mais trabalho para que o Active Record trabalhe bem para você. Se seguir as Convenções, não terá trabalho algum. Nada é impossível, apenas mais trabalhoso. Leiam os comentários do artigo original para mais pontos de vista e dicas.</p>
<p><strong>nota do Akita 2</strong> : preste bastante atenção ao uso do seu banco de dados. Eu expliquei isso no meu livro mas vou repetir: Bancos de dados de aplicação são usados para transações e pouco processamento maciço de dados. Se precisar de processamento maciço (transformações, cálculos, carga) de ordens de grandeza de gigabytes ou milhares de bilhões de linhas, por exemplo, então estamos falando de um conjunto de problemas completamente separados de um Domain Model de aplicação Web. Nesse caso não faz sentido trafegar essa quantidade-monstro de dados para um servidor de aplicação Rails, processar tudo e mandar de volta ao banco: é melhor realmente separar e isolar esse problema e resolvê-lo diretamente no banco, nesse caso, com stores procedures ou programas residentes no banco, onde ele poderá otimizar a melhor estratégia para esse processamento maciço.</p>
<p><strong>nota do Akita 3</strong> : como um comentarista no artigo bem lembrou, todo banco de dados de aplicação, se crescer o suficiente e concentrar mais de uma aplicação sobre os mesmos dados, se tornará um banco de dados de integração. E nesse caso se faz necessário uma camada de negócios integrada aos N aplicativos. É onde EJBs no mundo Java começam a fazer sentido. Esse é um caso onde Rails ainda não se encaixa muito bem: compartilhar o Domain Model entre múltiplos aplicativos. Não é impossível, mas também não é trivial. Um exemplo de solução: uma aplicação Rails que serve de servidor de Web Service para outras aplicações Rails, por exemplo, ajudaria, principalmente com o novíssimo suporte REST/Active Resource do Rails 1.2.</p>
]]></content:encoded><category>rails</category><category>arquitetura-de-software</category><category>bancos-de-dados</category></item></channel></rss>