<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Alves</title>
    <description>The latest articles on DEV Community by Michael Alves (@michael_d_rei).</description>
    <link>https://dev.to/michael_d_rei</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F888291%2Fb71e45ad-4472-48c8-9c04-b5fc1e84595f.jpg</url>
      <title>DEV Community: Michael Alves</title>
      <link>https://dev.to/michael_d_rei</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michael_d_rei"/>
    <language>en</language>
    <item>
      <title>Cloud Spanner: tentando entender o que ele resolve e brincando com o emulador local</title>
      <dc:creator>Michael Alves</dc:creator>
      <pubDate>Tue, 04 Aug 2026 02:09:43 +0000</pubDate>
      <link>https://dev.to/michael_d_rei/cloud-spanner-tentando-entender-o-que-ele-resolve-e-brincando-com-o-emulador-local-e0l</link>
      <guid>https://dev.to/michael_d_rei/cloud-spanner-tentando-entender-o-que-ele-resolve-e-brincando-com-o-emulador-local-e0l</guid>
      <description>&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Por que o Spanner existe...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bancos relacionais&lt;/li&gt;
&lt;li&gt;Transações ACID&lt;/li&gt;
&lt;li&gt;Consistência e escalabilidade global&lt;/li&gt;
&lt;li&gt;Bancos NoSQL&lt;/li&gt;
&lt;li&gt;Distribuição pelo mundo&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Como ele resolve esses problemas?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. Replicação utilizando Paxos&lt;/li&gt;
&lt;li&gt;2. Transações distribuídas&lt;/li&gt;
&lt;li&gt;3. TrueTime&lt;/li&gt;
&lt;li&gt;4. Commit Wait&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Emulando o Cloud Spanner com &lt;code&gt;cloud-spanner-emulator&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conector do Spanner para Active Record&lt;/li&gt;
&lt;li&gt;Ferramenta para testes de carga&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conclusão&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Referências&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algumas semanas atrás, fui tomado pelo tédio em relação às coisas que eu precisava estudar. Atualmente, no ano de 2026 (para você que está lendo isso no futuro), estamos vivendo o hype absoluto dos agentes de IA. Eu confesso que, apesar de também estar empolgado, testando e usando agentes de IA para trabalhar e tudo mais, eu estava à procura de outra coisa para estudar, algo que fugisse um pouco de ficar testando harnesses e ferramentas de IA, pensando em como posso usar novos agentes para escrever código, economizar tokens e blá-blá-blá.&lt;/p&gt;

&lt;p&gt;Dito isso, juntando o útil ao agradável, já que é uma tecnologia que vou utilizar no meu trabalho, comecei a estudar sobre o &lt;strong&gt;Cloud Spanner&lt;/strong&gt;, o serviço de banco de dados relacional distribuído e globalmente escalável desenvolvido pelo Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que o &lt;strong&gt;Spanner&lt;/strong&gt; existe...
&lt;/h2&gt;

&lt;p&gt;Lendo os artigos publicados no Google Schollar eu entendo que o Cloud Spanner nasce para responder a seguinte pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Como construir um banco de dados que funcione em vários datacenters espalhados pelo mundo sem abrir mão da consistência das transações?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quando precisamos escolher um banco de dados para um projeto, além de considerar custos, a afinidade da equipe e os objetivos do software que sera construido, normalmente precisamos escolher entre os benefícios oferecidos por um banco de dados relacional e as características de escalabilidade e distribuição presentes em muitos bancos NoSQL.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bancos relacionais
&lt;/h4&gt;

&lt;p&gt;Os bancos de dados relacionais, como MySQL e PostgreSQL, tendem a priorizar estrutura, relacionamentos e consistência e, com isso, trazem um pacote como por exemplo as transações &lt;strong&gt;ACID&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Transações ACID
&lt;/h4&gt;

&lt;p&gt;Transações ACID são um conjunto de propriedades que garantem que as operações em um banco de dados sejam executadas de forma confiável:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;A&lt;/strong&gt; vem de &lt;strong&gt;Atomicidade&lt;/strong&gt;: ou todas as operações da transação acontecem ou nenhuma delas acontece.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;C&lt;/strong&gt; vem de &lt;strong&gt;Consistência&lt;/strong&gt;: a transação mantém o banco de dados em um estado válido, respeitando suas regras e restrições.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;I&lt;/strong&gt; vem de &lt;strong&gt;Isolamento&lt;/strong&gt;: transações concorrentes são executadas de forma isolada, evitando que uma transação interfira incorretamente no resultado de outra.&lt;/li&gt;
&lt;li&gt;E, por fim, o &lt;strong&gt;D&lt;/strong&gt; vem de &lt;strong&gt;Durabilidade&lt;/strong&gt;: depois que uma transação é confirmada (&lt;code&gt;COMMIT&lt;/code&gt;) seus dados permanecem persistidos mesmo em caso de falha.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Consistência e escalabilidade global
&lt;/h4&gt;

&lt;p&gt;Em um banco de dados que oferece consistência forte, quando um dado é alterado e a transação é confirmada as leituras que vão ser executadas devem refletir essa alteração de acordo com as garantias de consistência oferecidas pelo banco de dados.&lt;/p&gt;

&lt;p&gt;Consequentemente, a escalabilidade global de bancos relacionais tradicionais pode se tornar complicada. Isso acontece porque em um banco de dados distribuído manter a consistência forte entre servidores espalhados pelo mundo é caro e complexo.&lt;/p&gt;

&lt;p&gt;Imagine dois servidores: um no Brasil e outro nos Estados Unidos. Se uma transação altera um dado no Brasil, o banco precisa coordenar essa alteração com os demais servidores para garantir que as réplicas mantenham as garantias de consistência esperadas. Isso pode exigir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comunicação entre regiões, aumentando a latência;&lt;/li&gt;
&lt;li&gt;coordenação entre servidores;&lt;/li&gt;
&lt;li&gt;replicação síncrona, que pode reduzir o desempenho;&lt;/li&gt;
&lt;li&gt;maior complexidade para lidar com falhas de rede e indisponibilidade de regiões.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Bancos NoSQL
&lt;/h4&gt;

&lt;p&gt;E então temos os bancos de dados NoSQL. Particularmente, eu usei poucos bancos de dados desse tipo (Redis conta? kkk). Mas bancos como Bigtable, Cassandra e DynamoDB garantem uma enorme escalabilidade, pois não dependem da mesma estrutura de relacionamentos e joins entre tabelas presente nos bancos relacionais, o que pode facilitar a distribuição dos dados e a escalabilidade horizontal.&lt;/p&gt;

&lt;p&gt;Em muitos sistemas NoSQL distribuídos, após uma alteração diferentes réplicas podem temporariamente apresentar valores diferentes, mas eventualmente convergem para o mesmo estado. Porém, essa não é uma característica obrigatória de todos os bancos NoSQL, já que alguns oferecem consistência forte ou diferentes níveis de consistência.&lt;/p&gt;

&lt;h4&gt;
  
  
  Distribuição pelo mundo
&lt;/h4&gt;

&lt;p&gt;O &lt;strong&gt;Spanner&lt;/strong&gt; foi criado justamente para tentar eliminar essa escolha.&lt;/p&gt;

&lt;p&gt;Ele é um banco de dados realmente global e permite que um único banco esteja distribuído entre diversos datacenters ao redor do mundo, sem que a aplicação precise lidar manualmente com a replicação dos dados.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Spanner&lt;/strong&gt; oferece consistência forte em escala global, eu acho que esse talvez seja o maior diferencial apontado nos artigos, pois o &lt;strong&gt;Spanner&lt;/strong&gt; oferece serializabilidade externa, garantindo que as transações distribuídas sejam executadas com propriedades ACID e que exista uma ordem global consistente entre as transações. Em outras palavras, mesmo com servidores em continentes diferentes, o sistema consegue manter uma visão consistente sobre a ordem em que as transações foram confirmadas.&lt;/p&gt;

&lt;p&gt;Além disso, temos a escalabilidade automática (ou até o seu cartão de crédito aguentar $$$$$). O sistema consegue dividir os dados (sharding), mover os dados automaticamente, rebalancear a carga e distribuir e replicar os dados entre diferentes datacenters sem intervenção humana. Sim, sem intervenção humana. Isso me lembra o assunto das IAs que eu estou tentando fugir...&lt;/p&gt;

&lt;h2&gt;
  
  
  Como ele resolve esses problemas?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bzl715c0m4znkr6h7e4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bzl715c0m4znkr6h7e4.png" alt=" " width="403" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Essa imagem mostra a arquitetura de software do &lt;strong&gt;Spanner&lt;/strong&gt;, mais especificamente como o sistema organiza a replicação, consenso e transações distribuídas entre diferentes datacenters.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A arquitetura é baseada em dados particionados em tablets, que são replicados por meio de grupos &lt;strong&gt;Paxos&lt;/strong&gt; distribuídos entre diferentes datacenters.&lt;/p&gt;

&lt;p&gt;Sobre essa infraestrutura, o &lt;strong&gt;Spanner&lt;/strong&gt; implementa mecanismos para executar transações distribuídas e, por meio do &lt;strong&gt;TrueTime&lt;/strong&gt;, consegue atribuir timestamps globalmente significativos às transações. Essas ideias trabalham em conjunto para fornecer não apenas tolerância a falhas e replicação, mas também consistência externa, fazendo com que a ordem de serialização das transações respeite a ordem em que elas realmente ocorreram.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Replicação utilizando Paxos
&lt;/h3&gt;

&lt;p&gt;O primeiro problema é garantir que os dados continuem disponíveis e consistentes mesmo quando estão distribuídos entre várias máquinas e datacenters. O &lt;strong&gt;Spanner&lt;/strong&gt; resolve isso particionando os dados em tablets e organizando cada conjunto de dados em um &lt;strong&gt;grupo Paxos&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Um grupo Paxos é um conjunto de réplicas que mantém uma cópia do mesmo conjunto de dados e usa o algoritmo de consenso &lt;a href="https://medium.com/@mani.saksham12/raft-and-paxos-consensus-algorithms-for-distributed-systems-138cd7c2d35a" rel="noopener noreferrer"&gt;Paxos&lt;/a&gt; para concordar sobre quais operações devem ser aplicadas e em qual ordem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cada grupo possui várias réplicas, que podem estar distribuídas geograficamente. O Paxos é responsável por fazer com que essas réplicas concordem sobre a ordem das operações realizadas naquele grupo. Dessa forma, uma falha de uma máquina ou de uma réplica não significa necessariamente a perda dos dados ou a indisponibilidade do sistema, desde que o grupo ainda consiga operar com as réplicas necessárias.&lt;/p&gt;

&lt;p&gt;É importante falar que &lt;strong&gt;Paxos&lt;/strong&gt; não resolve sozinho o problema das transações distribuídas. Ele garante consenso dentro de cada grupo de replicação. Quando uma transação precisa modificar dados pertencentes a mais de um grupo Paxos, é necessário um mecanismo adicional para coordenar esses grupos.&lt;/p&gt;

&lt;p&gt;É aí que entram as &lt;strong&gt;transações distribuídas&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Transações distribuídas
&lt;/h3&gt;

&lt;p&gt;Uma transação que acessa apenas um grupo Paxos é relativamente simples pois o próprio mecanismo de Paxos combinado com a lock table, já fornece os mecanismos necessários para a execução transacional. Porém, quando uma única transação envolve dados pertencentes a vários grupos Paxos, é necessário coordenar todos esses grupos para que a operação seja concluída de forma atômica.&lt;/p&gt;

&lt;p&gt;Nesse cenário, o &lt;strong&gt;Spanner&lt;/strong&gt; utiliza &lt;strong&gt;Two-Phase Commit (2PC)&lt;/strong&gt; para coordenar os diferentes participantes da transação. Um dos grupos participantes é escolhido como coordenador, e seu Participant Leader atua como Coordinator Leader. Os demais grupos possuem seus próprios Participant Leaders, que participam do protocolo de commit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0h6igrm7ekx5goa5f3yu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0h6igrm7ekx5goa5f3yu.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada participante precisa preparar sua parte da transação. Os participantes que não são o coordenador adquirem os locks de escrita e registram seu estado de preparação utilizando Paxos. Depois eles informam ao coordenador o timestamp de preparação. O coordenador aguarda as respostas dos participantes e escolhe um timestamp de commit que respeite as restrições necessárias para preservar a ordem da transação. O resultado do commit também é registrado por meio do Paxos, garantindo que o estado do Transaction Manager seja replicado.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. TrueTime
&lt;/h3&gt;

&lt;p&gt;Mesmo resolvendo replicação e transações distribuídas, ainda existe outro problema que é como determinar a ordem global das transações?&lt;/p&gt;

&lt;p&gt;Em um banco distribuído globalmente, diferentes máquinas possuem relógios diferentes. Mesmo utilizando sincronização de relógios, sempre existe algum nível de incerteza. Se cada máquina simplesmente utilizasse seu próprio relógio local para definir o timestamp de uma transação, poderia acontecer de uma transação que ocorreu posteriormente receber um timestamp menor que uma transação anterior.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Spanner&lt;/strong&gt; resolve esse problema com o &lt;strong&gt;TrueTime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Em vez de fornecer um único valor afirmando "agora são exatamente X horas", a API retorna um intervalo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TT.now() = [earliest, latest]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A ideia é que o tempo físico real esteja garantidamente dentro desse intervalo.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Spanner&lt;/strong&gt; mantém essa incerteza pequena utilizando múltiplas referências de tempo, incluindo GPS e relógios atômicos. O sistema não tenta fingir que todos os relógios são perfeitamente sincronizados. Pelo contrário: ele conhece explicitamente a margem de erro dos relógios e utiliza essa informação nos algoritmos de transação.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Commit Wait
&lt;/h3&gt;

&lt;p&gt;Mas existe uma dificuldade pois mesmo que o &lt;strong&gt;Spanner&lt;/strong&gt; conheça o intervalo de incerteza do relógio, ele ainda precisa garantir que um timestamp de commit não seja interpretado como pertencente ao futuro quando a transação já estiver sendo observada como concluída.&lt;/p&gt;

&lt;p&gt;Por isso existe o &lt;strong&gt;Commit Wait&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O coordenador escolhe um timestamp de commit &lt;code&gt;s&lt;/code&gt;, baseado no limite superior fornecido pelo TrueTime. Antes de permitir que a transação seja confirmada e observada pelos clientes, o sistema aguarda até que o limite inferior do intervalo retornado pelo TrueTime seja maior que &lt;code&gt;s&lt;/code&gt;. Nesse momento, o sistema pode garantir que o timestamp de commit &lt;code&gt;s&lt;/code&gt; pertence definitivamente ao passado.&lt;/p&gt;

&lt;p&gt;O ponto mais importante é que o Commit Wait não serve simplesmente para "sincronizar os relógios". O relógio continua tendo incerteza. O que o &lt;strong&gt;Spanner&lt;/strong&gt; faz é usar essa incerteza a seu favor.&lt;/p&gt;

&lt;p&gt;Se uma transação recebe um timestamps, o sistema espera até que tenha certeza de que o tempo real já ultrapassou. Somente então o commit pode ser observado externamente. Dessa maneira, se uma segunda transação começar depois que a primeira já foi confirmada e observada pelo cliente, o TrueTime garante que a segunda transação não receberá um timestamp que viole essa ordem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonyfpfulhcqkl77xzw9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonyfpfulhcqkl77xzw9t.png" alt=" " width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Resumo da ópera (Sim, foi gerado por IA Sherlock)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Após esse resumo sobre as principais features implementadas no &lt;strong&gt;Spanner&lt;/strong&gt; que permitem trabalhar com transações distribuídas, dados replicados em datacenters ao redor do mundo e toda a consistência que torna o banco confiável, vamos partir para a mão na massa e emular o Spanner localmente e dar uma brincada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emulando o Cloud Spanner com  &lt;code&gt;cloud-spanner-emulator&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;É importante lembrar que o Spanner Emulator possui algumas limitações quando comparado ao Google Cloud Spanner disponibilizado pelo gcloud. O emulador é voltado principalmente para desenvolvimento e testes locais e não reproduz toda a infraestrutura distribuída do Spanner em produção. Por isso, ele não simula recursos como a replicação real entre regiões e datacenters, a distribuição física dos dados, a alta disponibilidade proporcionada pela infraestrutura global do Google Cloud, nem representa fielmente os comportamentos de latência e falhas de uma arquitetura distribuída real.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para seguir, é necessário ter &lt;a href="https://git-scm.com/install/linux" rel="noopener noreferrer"&gt;Git&lt;/a&gt;, &lt;a href="https://www.ruby-lang.org/en/documentation/installation/" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;, &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; e &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; instalados na sua máquina. Eu uso Ubuntu como sistema operacional, então os comandos utilizados aqui são voltados para ele.&lt;/p&gt;

&lt;p&gt;Primeiro, faça o clone do projeto na sua máquina clicando &lt;a href="https://github.com/michaelalves204/spanner-emulator-test" rel="noopener noreferrer"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;O Spanner Emulator será executado via Docker Compose. A configuração desse serviço está definida no arquivo &lt;code&gt;docker-compose.yml&lt;/code&gt; dentro do projeto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;spanner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/cloud-spanner-emulator/emulator&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9010:9010"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9020:9020"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é uma configuração simples do Spanner Emulator, executando localmente em um container Docker, sem a necessidade de utilizar o gcloud para subir ou gerenciar a instância localmente.&lt;/p&gt;

&lt;p&gt;Também não estamos configurando aqui um cenário com múltiplas réplicas ou uma topologia distribuída. A ideia é apenas ter uma instância local do emulator para podermos realizar nossos testes&lt;/p&gt;

&lt;p&gt;Para subir o serviço em segundo plano, o comando é o:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ele vai criar e executar o container do Spanner Emulator. Para garantir que ele está funcionando corretamente, você pode verificar os containers em execução com o comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você tera um retorno parecido com esse&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CONTAINER ID   IMAGE                                    COMMAND                  CREATED       STATUS         PORTS                                                                                      NAMES
7e5383d98efd   gcr.io/cloud-spanner-emulator/emulator   &lt;span class="s2"&gt;"./gateway_main --ho…"&lt;/span&gt;   11 days ago   Up 8 seconds   0.0.0.0:9010-&amp;gt;9010/tcp, &lt;span class="o"&gt;[&lt;/span&gt;::]:9010-&amp;gt;9010/tcp, 0.0.0.0:9020-&amp;gt;9020/tcp, &lt;span class="o"&gt;[&lt;/span&gt;::]:9020-&amp;gt;9020/tcp   spanner-spanner-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora que temos o Spanner rodando, vou explicar como o código funciona passo a passo. Se quiser se aventurar sozinho pelo código, o README do projeto vai te ajudar a se situar.&lt;/p&gt;

&lt;p&gt;Se você não conhece Ruby, nós usamos o Bundler para gerenciar e instalar nossas bibliotecas, que em Ruby chamamos de gems. As gems utilizadas pelo projeto, assim como suas respectivas versões, estão definidas no arquivo Gemfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'dotenv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 3.2'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'google-cloud-spanner'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 2.36'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'mutex_m'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 0.3.0'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A gem principal para o nosso estudo é a &lt;code&gt;google-cloud-spanner&lt;/code&gt;. Ela vai nos permitir utilizar a API do Spanner, conectar ao banco de dados e executar transações. O legal é que, nessa versão da gem, não precisamos adicionar uma gem secundária para gerenciar um pool de conexões, pois o próprio cliente do Spanner já trabalha com o gerenciamento de sessões.&lt;/p&gt;

&lt;p&gt;A gem &lt;code&gt;dotenv&lt;/code&gt; é responsável por carregar nossas variáveis de ambiente a partir do arquivo &lt;code&gt;.env&lt;/code&gt;. Também temos a &lt;code&gt;mutex_m&lt;/code&gt;, que foi adicionada por causa da versão do Ruby utilizada no projeto. Basicamente, ela é uma dependência necessária para o funcionamento da &lt;code&gt;google-cloud-spanner&lt;/code&gt; nessa versão do Ruby.&lt;/p&gt;

&lt;p&gt;Dito isso, podemos instalar as gems utilizando o comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No contexto da gem &lt;strong&gt;google-cloud-spanner&lt;/strong&gt;, um client é o objeto que representa sua conexão lógica com o serviço do Cloud Spanner. Ele é o ponto de entrada para acessar instâncias, bancos de dados e outros recursos.&lt;/p&gt;

&lt;p&gt;Então temos um &lt;em&gt;wrapper&lt;/em&gt; para criar nosso client em &lt;code&gt;client.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'google/cloud/spanner'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dotenv/load'&lt;/span&gt;

&lt;span class="no"&gt;Dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;

&lt;span class="c1"&gt;# Wraps the Google::Cloud::Spanner project client.&lt;/span&gt;
&lt;span class="c1"&gt;# Provides a configured Spanner client connected to the emulator.&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloud&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Spanner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;project_id: &lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Em &lt;code&gt;instance.rb&lt;/code&gt; temos uma classe para a criação de uma instancia do Spanner basicamente essa instancia é o &lt;strong&gt;nível mais alto de organização&lt;/strong&gt; é basicamente um container que agrupa bancos de dados. A instance define &lt;strong&gt;onde os dados moram&lt;/strong&gt; e &lt;strong&gt;quanto recurso&lt;/strong&gt; eles têm (no emulador isso é irrelevante mas em produção, cada instance tem N nós com processamento e armazenamento dedicados).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Instance&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;   &lt;span class="c1"&gt;# cria o client do projeto Spanner (Google::Cloud::Spanner)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:client&lt;/span&gt;             &lt;span class="c1"&gt;# expõe o client pra outros usarem (ex: Database#client)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;
    &lt;span class="vi"&gt;@client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test-instance'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# busca a instance existente&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;            &lt;span class="c1"&gt;# tenta criar a instance&lt;/span&gt;
      &lt;span class="s1"&gt;'test-instance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# ID da instance&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Test Instance'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# nome amigável&lt;/span&gt;
      &lt;span class="ss"&gt;config: &lt;/span&gt;&lt;span class="s1"&gt;'emulator-config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# região/emulador&lt;/span&gt;
      &lt;span class="ss"&gt;nodes: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;                          &lt;span class="c1"&gt;# quantidade de nós&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;wait_until_done!&lt;/span&gt;                  &lt;span class="c1"&gt;# aguarda a criação terminar (é async)&lt;/span&gt;

    &lt;span class="n"&gt;find&lt;/span&gt;                                &lt;span class="c1"&gt;# depois busca e retorna a instance criada&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloud&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AlreadyExistsError&lt;/span&gt;
    &lt;span class="n"&gt;find&lt;/span&gt;                                &lt;span class="c1"&gt;# se já existia, só retorna a existente&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para concluir a configuração, agora precisamos criar um banco de dados e fornecer um client conectado a esse banco para executar nossas transações, isso acontece na classe &lt;code&gt;database.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Importa a classe responsável por gerenciar a instância do Spanner.&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'instance'&lt;/span&gt;

&lt;span class="c1"&gt;# Gerencia o ciclo de vida e o acesso ao banco de dados Spanner.&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt;
  &lt;span class="c1"&gt;# Define o identificador da instância do Spanner.&lt;/span&gt;
  &lt;span class="no"&gt;INSTANCE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test-instance'&lt;/span&gt;

  &lt;span class="c1"&gt;# Define o identificador do banco de dados.&lt;/span&gt;
  &lt;span class="no"&gt;DATABASE_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'test-db'&lt;/span&gt;

  &lt;span class="c1"&gt;# Inicializa e armazena a instância do Spanner.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Cria o banco ou retorna o banco existente.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# Cria o banco e aguarda a conclusão da operação.&lt;/span&gt;
    &lt;span class="vi"&gt;@instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;wait_until_done!&lt;/span&gt;

    &lt;span class="c1"&gt;# Retorna uma referência ao banco criado.&lt;/span&gt;
    &lt;span class="vi"&gt;@instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloud&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AlreadyExistsError&lt;/span&gt;
    &lt;span class="c1"&gt;# Retorna o banco caso ele já exista.&lt;/span&gt;
    &lt;span class="vi"&gt;@instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Retorna um cliente reutilizável para acessar o banco.&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;
    &lt;span class="c1"&gt;# Cria o cliente apenas uma vez e reutiliza nas próximas chamadas.&lt;/span&gt;
    &lt;span class="vi"&gt;@client&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora que temos toda a configuração explicada, precisamos de tabelas, pois estamos lidando com um banco de dados, afinal. Para isso, é uma prática legal criar &lt;strong&gt;migrations&lt;/strong&gt;, que são scripts pré-definidos utilizados para criar e alterar tabelas, índices e outros recursos do banco de dados de forma versionada.&lt;/p&gt;

&lt;p&gt;No projeto, temos a pasta &lt;code&gt;migrations&lt;/code&gt;, que contém as migrações responsáveis por criar as tabelas &lt;code&gt;Users&lt;/code&gt; e &lt;code&gt;Accounts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Exemplo em &lt;code&gt;migrations/create_accounts.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'base'&lt;/span&gt;

&lt;span class="c1"&gt;# Migration that creates the Accounts table.&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateAccounts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Migration&lt;/span&gt;
  &lt;span class="no"&gt;DDL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
    CREATE TABLE Accounts (
      AccountId INT64 NOT NULL,
      Owner STRING(MAX) NOT NULL,
      Balance NUMERIC NOT NULL,
      UpdatedAt TIMESTAMP OPTIONS (allow_commit_timestamp=true)
    ) PRIMARY KEY (AccountId)
&lt;/span&gt;&lt;span class="no"&gt;  SQL&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para executar as migrações, temos um &lt;code&gt;Rakefile&lt;/code&gt; que contém tarefas automatizadas escritas em Ruby. O comando para executar a tarefa responsável por rodar as migrações é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se tudo ocorrer como o esperado nos teremos duas tabelas criadas no Emulador de Spanner. Para testar, vamos criar alguns registros e listar os dados de &lt;code&gt;User&lt;/code&gt; e &lt;code&gt;Account&lt;/code&gt;. Para isso temos os &lt;code&gt;models&lt;/code&gt;, que basicamente representam as nossas tabelas criadas no Spanner e facilitam a interação com os dados por meio do código Ruby.&lt;/p&gt;

&lt;p&gt;Exempo em &lt;code&gt;models/user.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'base'&lt;/span&gt;

&lt;span class="c1"&gt;# Represents a user record in the Users table.&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="no"&gt;TABLE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Users'&lt;/span&gt;
  &lt;span class="no"&gt;COLUMNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[UserId Name UpdatedAt]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;commit_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cloud&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Spanner&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ColumnValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit_timestamp&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="no"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;commit_timestamp&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para criar um registro, precisamos acessar o &lt;code&gt;irb&lt;/code&gt;, que é o console interativo do Ruby. Com ele, podemos carregar nossas classes, instanciar objetos e executar as operações que precisamos diretamente pelo terminal.&lt;/p&gt;

&lt;p&gt;Comando para acessar o &lt;code&gt;irb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;irb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Bem facil você não acha?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para criar um registro&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'models/user'&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ainda no mesmo console para conferir se o registro foi feito com sucesso, execute o &lt;code&gt;find&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você deve receber uma resposta assim&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;UserId: 123, Name: &lt;span class="s2"&gt;"John"&lt;/span&gt;, UpdatedAt: 2026-08-02 21:51:49.196166 UTC&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso significa que conseguimos criar e listar registros no Cloud Spanner. O próximo passo agora é executar os &lt;code&gt;examples&lt;/code&gt; que estão no código e que permitem explorar algumas particularidades desse banco de dados.&lt;/p&gt;

&lt;p&gt;O primeiro é o &lt;code&gt;examples/transfer.rb&lt;/code&gt;. Esse código serve para demonstrar o conceito de &lt;strong&gt;ACID&lt;/strong&gt;, mais especificamente a &lt;strong&gt;atomicidade&lt;/strong&gt; das transações no Cloud Spanner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../models/account'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../config/logger'&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;

&lt;span class="c1"&gt;# Garante dados limpos (upsert é idempotente)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt; &lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="no"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt; &lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="no"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="no"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'Initial balance'&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"  #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:AccountId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Owner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# Successful transfer: read, validate, debit, credit inside a transaction&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'SELECT AccountId, Balance FROM Accounts WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Insufficient funds'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;

  &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_update&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE Accounts SET Balance = Balance - @amount WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_update&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE Accounts SET Balance = Balance + @amount WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'After transfer'&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"  #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:AccountId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Owner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# Rollback: an exception inside the block prevents commit&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s1"&gt;'SELECT Balance FROM Accounts WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Balance: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;

    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_update&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE Accounts SET Balance = Balance - @amount WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;RuntimeError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Transfer denied: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'Balances unchanged'&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"  #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:AccountId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Owner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  O que ele faz?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cria o banco e garante que as contas existam:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice: $1000&lt;/li&gt;
&lt;li&gt;Bob: $0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Executa uma transferência de $700 dentro de uma transação:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifica o saldo de Alice.&lt;/li&gt;
&lt;li&gt;Retira $700 de Alice.&lt;/li&gt;
&lt;li&gt;Adiciona $700 a Bob.&lt;/li&gt;
&lt;li&gt;Se tudo der certo, a transação faz commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Testa um rollback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tenta retirar $500 de Alice.&lt;/li&gt;
&lt;li&gt;Como o saldo é insuficiente, uma exceção é lançada.&lt;/li&gt;
&lt;li&gt;A transação não é confirmada e a alteração é desfeita.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso demonstra que o Spanner consegue manter a &lt;strong&gt;consistência dos dados&lt;/strong&gt; durante operações que envolvem múltiplas alterações. Nesse caso, a transferência é &lt;strong&gt;atômica&lt;/strong&gt;: ou as duas operações que são retirar o dinheiro de Alice e adicionar o dinheiro a Bob são confirmadas, ou nenhuma delas é aplicada.&lt;/p&gt;

&lt;p&gt;Para rodar basta executar (fora do console &lt;code&gt;irb&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby examples/transfer.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exemplo de resposta&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Initial balance
  &lt;span class="c"&gt;#1 Alice: $1000.0&lt;/span&gt;
  &lt;span class="c"&gt;#2 Bob: $0.0&lt;/span&gt;
After transfer
  &lt;span class="c"&gt;#1 Alice: $300.0&lt;/span&gt;
  &lt;span class="c"&gt;#2 Bob: $700.0&lt;/span&gt;
Transfer denied: Balance: &lt;span class="nv"&gt;$0&lt;/span&gt;.3e3
Balances unchanged
  &lt;span class="c"&gt;#1 Alice: $300.0&lt;/span&gt;
  &lt;span class="c"&gt;#2 Bob: $700.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outro exemplo adicionado é o &lt;code&gt;examples/concurrency.rb&lt;/code&gt; Esse código testa a concorrência e o isolamento transacional do Cloud Spanner. Ele cria 10 threads simultâneas. Cada uma tenta transferir $100 da Alice para o Bob&lt;/p&gt;

&lt;p&gt;Como as 10 transações acontecem ao mesmo tempo, várias podem tentar alterar o mesmo registro da Alice.&lt;/p&gt;

&lt;p&gt;O que o Spanner faz?&lt;/p&gt;

&lt;p&gt;O Spanner detecta que existem transações concorrentes conflitantes e algumas podem ser abortadas para preservar a consistência.&lt;/p&gt;

&lt;p&gt;A Gem então pode tentar novamente automaticamente a transação abortada.&lt;/p&gt;

&lt;p&gt;O exemplo em &lt;code&gt;examples/concurrency.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../models/account'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'../config/logger'&lt;/span&gt;

&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# Concurrency test: multiple threads racing to debit the same account.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Spanner detects conflicts and aborts all but one transaction.&lt;/span&gt;
&lt;span class="c1"&gt;# The client automatically retries aborted transactions.&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt; &lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="no"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt; &lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="no"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="no"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;THREADS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="no"&gt;AMOUNT&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;successful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;failed&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;THREADS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"[T&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] attempt #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s1"&gt;'SELECT Balance FROM Accounts WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Insufficient funds'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;AMOUNT&lt;/span&gt;

        &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s1"&gt;'UPDATE Accounts SET Balance = Balance - @amount WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="no"&gt;AMOUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s1"&gt;'UPDATE Accounts SET Balance = Balance + @amount WHERE AccountId = @id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="no"&gt;AMOUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="n"&gt;successful&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
      &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"[T&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] committed after &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; attempt(s)"&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;RuntimeError&lt;/span&gt;
      &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
      &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"[T&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] denied after &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; attempt(s)"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;alice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%w[Balance]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;keys: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;bob&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%w[Balance]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;keys: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Successful: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;successful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Denied: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s2"&gt;"Alice: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Bob: $&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bob&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;zero?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bob&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Balance&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
  &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'Correct'&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt; &lt;span class="s1"&gt;'Unexpected balance'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para executar&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby examples/concurrency.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exemplo de resposta&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;T1] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T6] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T7] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T10] attempt &lt;span class="c"&gt;#1&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T6] committed after 1 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T7] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T10] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T1] attempt &lt;span class="c"&gt;#2&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T7] committed after 2 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T10] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T1] attempt &lt;span class="c"&gt;#3&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T10] committed after 3 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T1] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#4&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T1] committed after 4 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#5&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T3] committed after 5 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] attempt &lt;span class="c"&gt;#6&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#6&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#6&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#6&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#6&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T2] committed after 6 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] attempt &lt;span class="c"&gt;#7&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#7&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#7&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#7&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T5] committed after 7 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#8&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] attempt &lt;span class="c"&gt;#8&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#8&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T9] committed after 8 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] attempt &lt;span class="c"&gt;#9&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#9&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T4] committed after 9 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] attempt &lt;span class="c"&gt;#10&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;T8] committed after 10 attempt&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;==================================================&lt;/span&gt;
Successful: 10, Denied: 0
Alice: &lt;span class="nv"&gt;$0&lt;/span&gt;.0, Bob: &lt;span class="nv"&gt;$1000&lt;/span&gt;.0
Correct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resumidamente, o resultado mostra que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As 10 threads começaram simultaneamente, tentando transferir $100.&lt;/li&gt;
&lt;li&gt;Como todas alteram o saldo da mesma conta (Alice), ocorreram conflitos entre as transações.&lt;/li&gt;
&lt;li&gt;O Spanner abortou algumas transações conflitantes, e o cliente reexecutou automaticamente essas transações.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Por isso, algumas transações precisaram de várias tentativas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;T1 → 4 tentativas&lt;/li&gt;
&lt;li&gt;T8 → 10 tentativas&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No final, todas as 10 transações foram concluídas com sucesso.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O saldo final está correto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice: $0&lt;/li&gt;
&lt;li&gt;Bob: $1000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso demonstra que o Spanner consegue controlar a concorrência e manter a consistência dos dados mesmo com várias transações sendo executadas simultaneamente.&lt;/p&gt;

&lt;p&gt;Esse é um código simples, criado apenas para testar o Spanner Emulator. É importante lembrar que o emulador não consegue reproduzir todas as principais funcionalidades e benefícios de executar o Spanner em uma infraestrutura real do Google Cloud, como a replicação de dados entre múltiplas regiões e a distribuição dos dados entre diferentes datacenters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conector do Spanner para Active Record
&lt;/h3&gt;

&lt;p&gt;O código que mostrei foi desenvolvido em Ruby. Porém, para facilitar a integração do Cloud Spanner com aplicações desenvolvidas em Ruby on Rails, existe a gem &lt;a href="https://rubygems.org/gems/activerecord-spanner-adapter" rel="noopener noreferrer"&gt;&lt;code&gt;activerecord-spanner-adapter&lt;/code&gt;&lt;/a&gt;, que funciona como um adaptador entre o Active Record e o Spanner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ferramenta para testes de carga
&lt;/h3&gt;

&lt;p&gt;Para avaliar o desempenho do Cloud Spanner sob diferentes níveis de carga e concorrência, podemos utilizar o &lt;a href="https://github.com/cloudspannerecosystem/spanner-benchmarks-tutorial/tree/main#overview" rel="noopener noreferrer"&gt;YCSB&lt;/a&gt; (Yahoo! Cloud Serving Benchmark). O próprio &lt;a href="https://cloud.google.com/blog/products/databases/benchmarking-spanner-for-key-value-workloads?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt; utiliza o YCSB em benchmarks do Spanner para avaliar workloads de chave-valor, simulando diferentes padrões de leitura e escrita.&lt;/p&gt;

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

&lt;p&gt;No fim das contas, o que mais me chamou atenção no Spanner não foi uma única tecnologia isolada, mas a forma como várias ideias diferentes trabalham juntas para um problema extremamente chato e dificil de se resolver.&lt;/p&gt;

&lt;p&gt;O Spanner tenta unir coisas que durante muito tempo pareciam exigir escolhas diferentes entre o modelo relacional e as transações ACID dos bancos tradicionais com a distribuição global e a escalabilidade horizontal esperadas de sistemas distribuídos.&lt;/p&gt;

&lt;p&gt;Para isso, ele combina replicação e consenso com Paxos, transações distribuídas com Two-Phase Commit, controle de concorrência, e utiliza o TrueTime e o Commit Wait para garantir uma ordem global consistente entre as transações. Cada uma dessas peças resolve um problema específico, mas é a junção desse "Lego" de ideias que permite ao Spanner oferecer consistência forte em uma arquitetura distribuída globalmente.&lt;/p&gt;

&lt;p&gt;No nosso pequeno laboratório local (que obviamente não conseguimos reproduzir toda essa infraestrutura) por mais simples que tenha sido, foi suficiente para brincar com algumas das ideias que vimos ao longo do artigo: criar tabelas, executar transações, testar atomicidade, provocar concorrência e observar o banco lidando com conflitos entre transações.&lt;/p&gt;

&lt;p&gt;E acho que esse é o ponto principal deste artigo. O objetivo aqui não era explicar absolutamente tudo sobre o Spanner, muito menos substituir a documentação oficial. A ideia era simplesmente entender por que ele existe, quais problemas ele tenta resolver, como suas principais peças se encaixam e colocar a mão no código para ver um pouco disso funcionando na prática.&lt;/p&gt;

&lt;p&gt;No final, talvez a melhor forma de resumir o Spanner seja justamente aquela pergunta do começo, que é como construir um banco de dados que funcione em vários datacenters espalhados pelo mundo sem abrir mão da consistência das transações?&lt;/p&gt;

&lt;p&gt;Para mim o &lt;strong&gt;Cloud Spanner&lt;/strong&gt; é uma boa resposta.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://static.googleusercontent.com/media/research.google.com/pt-BR//archive/spanner-osdi2012.pdf" rel="noopener noreferrer"&gt;https://static.googleusercontent.com/media/research.google.com/pt-BR//archive/spanner-osdi2012.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://static.googleusercontent.com/media/research.google.com/pt-BR//pubs/archive/45855.pdf" rel="noopener noreferrer"&gt;https://static.googleusercontent.com/media/research.google.com/pt-BR//pubs/archive/45855.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.acm.org/doi/epdf/10.1145/3035918.3056103" rel="noopener noreferrer"&gt;https://dl.acm.org/doi/epdf/10.1145/3035918.3056103&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@mani.saksham12/raft-and-paxos-consensus-algorithms-for-distributed-systems-138cd7c2d35a" rel="noopener noreferrer"&gt;https://medium.com/@mani.saksham12/raft-and-paxos-consensus-algorithms-for-distributed-systems-138cd7c2d35a&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@sylvain.tiset/two-phase-commit-the-good-the-bad-and-the-blocking-eee29e1f5a84" rel="noopener noreferrer"&gt;https://medium.com/@sylvain.tiset/two-phase-commit-the-good-the-bad-and-the-blocking-eee29e1f5a84&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/blog/products/databases/benchmarking-spanner-for-key-value-workloads" rel="noopener noreferrer"&gt;https://cloud.google.com/blog/products/databases/benchmarking-spanner-for-key-value-workloads&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Implementando um Sistema de Ranking com Rating Elo em Ruby</title>
      <dc:creator>Michael Alves</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:45:10 +0000</pubDate>
      <link>https://dev.to/michael_d_rei/implementando-um-sistema-de-ranking-com-rating-elo-em-ruby-3740</link>
      <guid>https://dev.to/michael_d_rei/implementando-um-sistema-de-ranking-com-rating-elo-em-ruby-3740</guid>
      <description>&lt;h1&gt;
  
  
  Rating Elo e o filme A Rede Social
&lt;/h1&gt;

&lt;p&gt;Um dos filmes que me fez querer entrar na área de tecnologia foi &lt;em&gt;A Rede Social&lt;/em&gt;. O filme conta a história de como Mark Zuckerberg fundou o Facebook, eu descobri várias coisas legais com esse filme. A primeira delas foi que o melhor amigo do Mark era um brasileiro chamado Eduardo e esse brasileiro processou o Mark depois por ter perdido suas ações na empresa. A segunda coisa é que, antes do Facebook, dois irmãos tiveram a ideia de uma rede social para pessoas de Harvard, chamaram o Mark para desenvolver e, depois, esses dois caras também acabaram processando o Mark por plágio, Entre outras coisas, é um ótimo filme, com uma boa trilha sonora.&lt;/p&gt;

&lt;p&gt;Nesse filme o Mark briga com a namorada, enche a cara de álcool e decide ter uma ideia bem canalha, que é hackear os Facebooks de Havard (Os Facebooks de Harvard eram diretórios digitais que consistiam em catálogos com as fotos, nomes, cursos e interesses dos calouros), pegar as imagens com os rostos das alunas e fazer um site onde ele compara qual aluna era "mais atraente" (Sim, isso foi bem bizarro e escroto). Nesse momento, ele pede ao seu amigo Eduardo para explicar qual algoritmo usar para fazer o ranking dessas garotas. Nesse caso, era o algoritmo que o Eduardo usava para ranquear os jogadores de xadrez. Daí temos a lendária cena da janela do quarto do Mark contendo a equação usada para gerar o ranking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yiszkuxkoel6t50brdq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yiszkuxkoel6t50brdq.png" alt=" " width="799" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tirando o fato de que essa equação foi usada para o mal, eu fiquei pensando se ela era real e por que foi usada para aquela finalidade de ranking.&lt;/p&gt;

&lt;p&gt;Eu pedi para a IA ajustar a imagem para trazer com mais clareza a equação usada.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4xbjdl27vo3xo4jdnwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4xbjdl27vo3xo4jdnwz.png" alt=" " width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apesar da imagem ter o expoente parecendo uma fração, a equação seria esta:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ge19u6i85tiakl5ie6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4ge19u6i85tiakl5ie6.png" alt=" " width="210" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pesquisando, eu descobri que isso se chama Rating Elo. Segundo a Wikipédia:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O Rating Elo é um método estatístico utilizado para calcular a força relativa entre jogadores de xadrez, inventado pelo físico americano Arpad Elo e utilizado no ranking da FIDE. O método funciona adequadamente quando utilizado da maneira que foi projetado, embora existam algumas limitações. O rating também foi adaptado para outros esportes, como, por exemplo, o tênis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Porque usar essa equação para ranquear?
&lt;/h1&gt;

&lt;p&gt;É uma boa pergunta. Vamos criar um modelo mental para visualizar melhor o problema. Digamos que temos que ranquear os melhores jogadores de xadrez.&lt;/p&gt;

&lt;p&gt;Acredito que o modelo mental mais simples seria ranquear pelo número de vitórias.&lt;/p&gt;

&lt;p&gt;Exemplo:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jogador&lt;/th&gt;
&lt;th&gt;Vitórias&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;João&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maria&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pedro&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;À primeira vista, parece justo colocar João em primeiro lugar, já que ele possui mais vitórias.&lt;/p&gt;

&lt;p&gt;Mas esse modelo possui um problema: ele considera apenas a quantidade de vitórias, ignorando a força dos adversários enfrentados.&lt;/p&gt;

&lt;p&gt;Imagine que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;João venceu 20 partidas contra iniciantes.&lt;/li&gt;
&lt;li&gt;Maria venceu 16 iniciantes e 2 mestres.&lt;/li&gt;
&lt;li&gt;Pedro venceu 5 mestres.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nesse cenário, as vitórias não possuem o mesmo valor.&lt;/p&gt;

&lt;p&gt;Era esperado que um mestre derrotasse um iniciante, então as 20 vitórias de João trazem pouca informação sobre sua habilidade real. Por outro lado, as 5 vitórias de Pedro contra mestres são resultados muito mais difíceis e inesperados, indicando que ele provavelmente é mais forte do que seu histórico inicial sugere.&lt;/p&gt;

&lt;p&gt;O sistema Elo foi criado para resolver exatamente esse problema: em vez de contar apenas quantas partidas cada jogador venceu, ele também considera a força dos adversários derrotados. Dessa forma, vencer um jogador forte vale mais do que vencer um jogador fraco, tornando o ranking muito mais representativo da habilidade real dos participantes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Vamos implementar a equação
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;400.00&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;rating_players_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;rating_players_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicando cada linha, o &lt;code&gt;ELO_DIVISOR&lt;/code&gt; é uma constante em Ruby, usada para indicar que esse valor não deve mudar.&lt;/p&gt;

&lt;p&gt;A próxima linha é o nosso método, &lt;code&gt;def rating_elo&lt;/code&gt;. Esse método recebe dois parâmetros, que seriam equivalentes ao RA e ao RB.&lt;/p&gt;

&lt;p&gt;Na próxima linha (&lt;code&gt;rating_players_result = (rating_player_b - rating_player_a) / ELO_DIVISOR&lt;/code&gt;), calculamos a expectativa de pontuação dos jogadores, ou seja, a probabilidade de o jogador A vencer o jogador B.&lt;/p&gt;

&lt;p&gt;Exemplo de uso (copie e cole o código no console Ruby, o IRB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;400.00&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;rating_players_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;rating_players_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Criamos variáveis para definir o rating inicial de cada jogador&lt;/span&gt;
&lt;span class="n"&gt;rating_player_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;span class="n"&gt;rating_player_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;

&lt;span class="c1"&gt;# Usamos o método para calcular a probabilidade de vitória do jogador A&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Pegamos apenas duas casas decimais para facilitar o entendimento do resultado&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado da execução:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;027&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rating_player_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rating_player_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.09090909090909091&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;030&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.09&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;031&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, o nosso jogador &lt;strong&gt;A&lt;/strong&gt; tem apenas 9% de chance de ganhar do jogador &lt;strong&gt;B&lt;/strong&gt;. Faz sentido, pois o rating inicial dele é menor.&lt;/p&gt;

&lt;p&gt;Se invertermos a ordem, o jogador &lt;strong&gt;B&lt;/strong&gt; terá 91% de chance de vitória.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;031&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rating_player_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_player_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.9090909090909091&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;032&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.91&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;033&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora precisamos usar a fórmula Elo e, após isso, calcular o novo rating depois de uma partida. Para isso, precisamos definir o quanto o rating muda ao finalizar uma partida.&lt;/p&gt;

&lt;p&gt;Para isso, vamos usar uma constante chamada &lt;code&gt;K_FACTOR&lt;/code&gt;. Ela é o parâmetro que controla o quanto o rating vai mudar após uma vitória ou derrota.&lt;/p&gt;

&lt;p&gt;Eu vou definir que o nosso &lt;code&gt;K_FACTOR&lt;/code&gt; é &lt;strong&gt;90&lt;/strong&gt; e também vou criar uma classe para os nossos jogadores, facilitando a atualização do rating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;400.0&lt;/span&gt;
&lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:rating&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="vi"&gt;@rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora precisamos adicionar a lógica do Elo com atualização de rating. Seguindo a lógica de uma partida normal, teremos atualização para vitória, derrota e empate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_a_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_b_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Busca o jogador A no hash de jogadores através do nome&lt;/span&gt;
  &lt;span class="n"&gt;player_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_a_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Busca o jogador B no hash de jogadores através do nome&lt;/span&gt;
  &lt;span class="n"&gt;player_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_b_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Calcula automaticamente o resultado do jogador B&lt;/span&gt;

  &lt;span class="c1"&gt;# Exemplos:&lt;/span&gt;
  &lt;span class="c1"&gt;# Resultado de A   Resultado de B&lt;/span&gt;
  &lt;span class="c1"&gt;# 1.0 (vitória)   0.0&lt;/span&gt;
  &lt;span class="c1"&gt;# 0.5 (empate)    0.5&lt;/span&gt;
  &lt;span class="c1"&gt;# 0.0 (derrota)   1.0&lt;/span&gt;
  &lt;span class="n"&gt;score_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;score_a&lt;/span&gt;

  &lt;span class="c1"&gt;# Calcula a expectativa de pontuação de A contra B&lt;/span&gt;
  &lt;span class="n"&gt;expected_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Calcula a expectativa de B&lt;/span&gt;
  &lt;span class="n"&gt;expected_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Atualiza o rating dos jogadores&lt;/span&gt;
  &lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score_a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;expected_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score_b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;expected_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basicamente, com o método &lt;code&gt;play&lt;/code&gt;, nós decidimos o vencedor da partida ou se houve empate.&lt;/p&gt;

&lt;p&gt;Exemplo de uso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Player A vence&lt;/span&gt;
&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Michael"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Angelica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Empate&lt;/span&gt;
&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Michael"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Angelica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Player A perde&lt;/span&gt;
&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Michael"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Angelica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último, adicionamos um método para trazer o ranking dos jogadores ordenados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;leaderboard&lt;/span&gt;
  &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ljust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora adicionamos tudo em uma classe chamada &lt;code&gt;Ranking&lt;/code&gt; e temos o código completo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;400.0&lt;/span&gt;
&lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt;
  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:rating&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="vi"&gt;@rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Ranking&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@players&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;ELO_DIVISOR&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loser_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;loser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loser_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expected_winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expected_loser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rating_elo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;expected_winner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;K_FACTOR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;expected_loser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; venceu &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;loser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;leaderboard&lt;/span&gt;
    &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ljust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@players&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;ranking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"Judit"&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Angelica"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Michael"&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Maria"&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Pedro"&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"Eric"&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Legal, temos a equação funcionando. Agora podemos usá-la na prática para criar um pequeno sistema de ranking.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ranqueando jogadores
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jogador&lt;/th&gt;
&lt;th&gt;Rating inicial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Judit&lt;/td&gt;
&lt;td&gt;1900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Angelica&lt;/td&gt;
&lt;td&gt;1800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Michael&lt;/td&gt;
&lt;td&gt;1600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maria&lt;/td&gt;
&lt;td&gt;1300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pedro&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eric&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Esses são os nossos jogadores. Perceba que a &lt;strong&gt;Judit&lt;/strong&gt; já começa com um rating inicial bem alto, provavelmente porque ela é uma boa jogadora e tem muita experiência. E temos o Eric. Vamos partir do pressuposto de que ele é um jogador promissor, porém teve poucas vitórias.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rodada 1 — Grande surpresa
&lt;/h3&gt;

&lt;p&gt;Vamos colocar o jogador Eric, último do ranking, para enfrentar o top 3 de jogadores: Judit, Angelica e Michael. E vamos dar a vitória para o Eric.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Eric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Judit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Eric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Angelica"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Eric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Michael"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Judit&lt;/span&gt;    &lt;span class="mf"&gt;1810.89&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Angelica&lt;/span&gt; &lt;span class="mf"&gt;1712.6&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Michael&lt;/span&gt;  &lt;span class="mf"&gt;1522.1&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Eric&lt;/span&gt;     &lt;span class="mf"&gt;1354.41&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Maria&lt;/span&gt;    &lt;span class="mi"&gt;1300&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Pedro&lt;/span&gt;    &lt;span class="mi"&gt;1200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que observar?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eric ganha muitos pontos.&lt;/li&gt;
&lt;li&gt;Judit perde muitos pontos.&lt;/li&gt;
&lt;li&gt;Angelica perde muitos pontos.&lt;/li&gt;
&lt;li&gt;Michael perde muitos pontos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;Era um resultado extremamente improvável.&lt;/p&gt;

&lt;p&gt;Após enfrentar o top 3 e vencer, Eric subiu para a quarta posição do ranking. Ao mesmo tempo que a equação beneficia o jogador que se arrisca e enfrenta os melhores do ranking, ela também pune o jogador de alto nível que perdeu.&lt;/p&gt;

&lt;p&gt;Vamos criar outras rodadas para elucidar os conceitos da equação.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rodada 2 — Resultado esperado
&lt;/h3&gt;

&lt;p&gt;Judit vence Pedro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Judit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Pedro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;067&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leaderboard&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Judit&lt;/span&gt;    &lt;span class="mf"&gt;1813.49&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Angelica&lt;/span&gt; &lt;span class="mf"&gt;1712.6&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Michael&lt;/span&gt;  &lt;span class="mf"&gt;1522.1&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Eric&lt;/span&gt;     &lt;span class="mf"&gt;1354.41&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Maria&lt;/span&gt;    &lt;span class="mi"&gt;1300&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Pedro&lt;/span&gt;    &lt;span class="mf"&gt;1197.4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que observar?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Judit ganha poucos pontos.&lt;/li&gt;
&lt;li&gt;Pedro perde poucos pontos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;O sistema já esperava que uma jogadora com rating 1810.89 vencesse um jogador com rating 1200.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rodada 3 — Empate surpreendente
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Eric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Judit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mo"&gt;06&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leaderboard&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Judit&lt;/span&gt;    &lt;span class="mf"&gt;1774.47&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Angelica&lt;/span&gt; &lt;span class="mf"&gt;1712.6&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Michael&lt;/span&gt;  &lt;span class="mf"&gt;1522.1&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Eric&lt;/span&gt;     &lt;span class="mf"&gt;1393.43&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Maria&lt;/span&gt;    &lt;span class="mi"&gt;1300&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Pedro&lt;/span&gt;    &lt;span class="mf"&gt;1197.4&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que observar?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eric ganha pontos.&lt;/li&gt;
&lt;li&gt;Judit perde pontos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mesmo sem vitória.&lt;/p&gt;

&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;Empatar contra alguém muito mais forte já é melhor do que o esperado.&lt;/p&gt;

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

&lt;p&gt;O sistema que implementamos aqui é uma versão simplificada do Rating Elo, mas já é suficiente para demonstrar o principal conceito por trás da fórmula: não basta contar vitórias e derrotas, também é necessário considerar a força dos adversários enfrentados.&lt;/p&gt;

&lt;p&gt;Na prática, sistemas de ranking costumam ser bem mais sofisticados. Uma possível evolução seria tornar o &lt;strong&gt;K_FACTOR&lt;/strong&gt; dinâmico, fazendo com que ele varie de acordo com o nível ou experiência do jogador. Jogadores novos poderiam ter um fator maior para que o sistema identificasse rapidamente sua habilidade real, enquanto jogadores mais experientes teriam um fator menor, deixando o ranking mais estável.&lt;/p&gt;

&lt;p&gt;Outra possibilidade seria considerar o histórico de partidas do jogador. Alguém que mantém uma alta taxa de vitórias ao longo do tempo poderia receber ajustes diferentes de um jogador com desempenho inconsistente. Também poderíamos criar regras específicas para séries de vitórias, frequência de partidas ou até diferentes pesos para torneios.&lt;/p&gt;

&lt;p&gt;O objetivo deste artigo não foi construir o sistema de ranking perfeito, mas entender a matemática por trás do Elo e implementar uma versão funcional do algoritmo. A partir daqui, existem diversas formas de aumentar a complexidade do modelo e adaptá-lo para diferentes tipos de jogos e aplicações.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://pt.wikipedia.org/wiki/Rating_Elo" rel="noopener noreferrer"&gt;Rating Elo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/ai-enthusiast/the-elo-rating-system-a-data-driven-approach-to-ranking-competitor-fdd34d5b2a11" rel="noopener noreferrer"&gt;The Elo Rating System: A Data-Driven Approach to Ranking Competitor&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Concorrência e Paralelismo em Ruby</title>
      <dc:creator>Michael Alves</dc:creator>
      <pubDate>Wed, 21 May 2025 22:29:49 +0000</pubDate>
      <link>https://dev.to/michael_d_rei/concorrencia-e-paralelismo-em-ruby-2e2j</link>
      <guid>https://dev.to/michael_d_rei/concorrencia-e-paralelismo-em-ruby-2e2j</guid>
      <description>&lt;p&gt;Software concorrente, paralelismo, múltiplas threads, esses assuntos sempre acabam surgindo, independentemente do seu nível de senioridade. Por muito tempo, esses temas foram verdadeiros pesadelos para mim. Eu me lembro de estudar concorrência usando C na faculdade. Eu fazia os exercícios, e eles funcionavam, mas, por muito tempo, parecia que eu não entendia muito bem como o computador conseguia fazer a "mágica" de executar meus programas de forma mais "rápida".&lt;/p&gt;

&lt;p&gt;Este guia foi feito para abordar os conceitos de processos, clone de processos e threads, usando Ruby como linguagem base.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby: Uma Linguagem Amigável e Elegante
&lt;/h2&gt;

&lt;p&gt;Ruby é uma linguagem de programação interpretada e multi paradigma, com tipagem dinâmica e gerenciamento automático de memória. O interpretador mais comum é o &lt;strong&gt;MRI&lt;/strong&gt;, sigla para &lt;em&gt;Matz's Ruby Interpreter&lt;/em&gt;, também conhecido como &lt;strong&gt;CRuby&lt;/strong&gt; por ser implementado em C. Ele é considerado o interpretador padrão da linguagem e o mais amplamente utilizado pela comunidade.&lt;/p&gt;

&lt;p&gt;MRI por padrão oferece suporte a tão falada concorrência.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recurso&lt;/th&gt;
&lt;th&gt;Suporte?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;✅ Sim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fork&lt;/td&gt;
&lt;td&gt;✅ Sim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processos&lt;/td&gt;
&lt;td&gt;✅ Sim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Vamos utilizar duas versões diferentes de interpretadores Ruby para os testes. Para alternar entre eles de forma fácil e rápida, usaremos o gerenciador de versões &lt;a href="https://github.com/rbenv/rbenv" rel="noopener noreferrer"&gt;rbenv&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Comandos para instalar as versões usadas aqui:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbenv install 3.3.5
rbenv install jruby-9.4.8.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comando para usar uma versão especifica de forma global:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbenv global 3.3.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comando para listar as versões instaladas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbenv versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  system
* 3.3.5 (set by /home/michael/.rbenv/version)
  jruby-9.4.8.0

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Processos
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Usaremos o Ubuntu como sistema operacional, ou seja, os conceitos abordados aqui se aplicam a sistemas do tipo &lt;strong&gt;UNIX-like&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Os programas que rodam no seu Sistema operacional estão dentro de uma estrutura chamada &lt;strong&gt;processos&lt;/strong&gt;. Os processos são independentes entre si e tem seus próprios recursos, como espaço em memória. O sistema operacional é inteligente o suficiente para gerenciar esses processos de forma simultânea usando um escalonador preemptivo. O fato de cada processo ter seu próprio espaço na memória permite que o sistema operacional lide com eles de forma concorrente. Ele consegue fazer trocas de contexto (context switch), aumentando a eficiência no uso da CPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcbxo914i8ct3c7kjrma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcbxo914i8ct3c7kjrma.png" alt=" " width="441" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exemplo: você está assistindo a um vídeo no YouTube, editando um texto no LibreOffice e recebendo mensagens no seu Telegram, cada tarefa dessa é um processo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sua CPU tem múltiplos &lt;strong&gt;núcleos&lt;/strong&gt;, mas você provavelmente tem dezenas de processos ativos. O sistema operacional faz a troca de contexto entre esses processos em questão de milissegundos, salvando o estado do processo atual e carregando o de outro.&lt;/p&gt;

&lt;p&gt;É possivel listar os processos ativos do seu sistema com o comando &lt;code&gt;top&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Segundo o manual (man top): The  top  program  provides  a  dynamic  real-time  view of a running system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cada processo possui um estado próprio e é identificado por um PID (&lt;em&gt;Process ID&lt;/em&gt;). O sistema operacional reconhece os processos como uma unidade de &lt;strong&gt;concorrência&lt;/strong&gt;. Os processos também podem se comunicar entre si por meio de IPC (&lt;em&gt;Inter-Process Communication&lt;/em&gt;), um conjunto de mecanismos que permite essa troca de informações.&lt;/p&gt;

&lt;p&gt;É possível criar um processo filho por meio da &lt;em&gt;syscall&lt;/em&gt; &lt;em&gt;&lt;strong&gt;clone&lt;/strong&gt;&lt;/em&gt; do sistema operacional.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O que é uma &lt;em&gt;syscall&lt;/em&gt;? é um mecanismo pelo qual um programa de computador solicita serviços ao kernel do sistema operacional no qual ele está sendo executado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quando essa chamada é executada, o processo pai é clonado, e um novo processo filho é criado com uma cópia do seu espaço de memória, fila de execução e descritores de arquivos, tudo isso dependendo dos parâmetros passados à syscall.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Segue a documentação da syscall clone diretamente do manual Linux &lt;a href="https://man7.org/linux/man-pages/man2/clone3.2.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man2/clone3.2.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vamos criar um processo filho a partir de um processo pai utilizando &lt;a href="https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Process+Groups" rel="noopener noreferrer"&gt;fork&lt;/a&gt; em Ruby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processo pai: PID = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fork&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processo filho: PID = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, PPID = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ppid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processo filho terminou."&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processo pai continua executando..."&lt;/span&gt;
&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Processo pai terminou após o filho."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saida&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Processo pai: PID &lt;span class="o"&gt;=&lt;/span&gt; 2864854
Processo pai continua executando...
Processo filho: PID &lt;span class="o"&gt;=&lt;/span&gt; 2864890, PPID &lt;span class="o"&gt;=&lt;/span&gt; 2864854
Processo filho terminou.
Processo pai terminou após o filho.
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O processo pai imprime seu próprio PID e, em seguida, cria um processo filho usando fork. O filho imprime seu PID e o do pai (PPID), simulando uma tarefa demorada com &lt;strong&gt;sleep&lt;/strong&gt;. Enquanto isso, o pai continua executando, aguarda a finalização do filho com &lt;em&gt;Process.wait&lt;/em&gt;, e só então encerra sua execução.&lt;/p&gt;

&lt;p&gt;É possivel que um processo filho se comunique com o processo principal através de mensagens (&lt;em&gt;pipe&lt;/em&gt;), no ruby conseguimos fazer essa comunicação usando &lt;a href="https://docs.ruby-lang.org/en/master/IO.html" rel="noopener noreferrer"&gt;&lt;em&gt;&lt;strong&gt;IO.pipe&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;

&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fork&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
  &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Olá do processo filho!"&lt;/span&gt;
  &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="n"&gt;mensagem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gets&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Pai recebeu: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mensagem&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;

&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Pai recebeu: Olá &lt;span class="k"&gt;do &lt;/span&gt;processo filho!
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 2866592
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Threads
&lt;/h2&gt;

&lt;p&gt;Agora que falamos um pouco sobre processos, vamos falar das threads. Threads são como "partes" de um processo, elas compartilham os mesmos recursos como a memória com o processo principal e com outras threads do mesmo processo. Enquanto um processo é um contêiner que possui memória, recursos e ao menos uma &lt;em&gt;thread&lt;/em&gt;, as &lt;em&gt;threads&lt;/em&gt; são unidades de execução dentro desse contêiner, trabalhando de forma colaborativa no mesmo espaço de memória.&lt;/p&gt;

&lt;p&gt;As &lt;em&gt;threads&lt;/em&gt; são úteis quando queremos executar múltiplas tarefas simultaneamente dentro de um único processo, oferecendo uma forma leve e eficiente de alcançar a tão desejada concorrência.&lt;/p&gt;

&lt;p&gt;Vamos escrever um programa que cria 4 &lt;em&gt;threads&lt;/em&gt; que executam um cálculo de raiz quadrada.&lt;/p&gt;

&lt;p&gt;Cada &lt;em&gt;thread&lt;/em&gt; calcula a soma das raízes quadradas dos números de 0 até 99.999.999, totalizando &lt;strong&gt;400 milhões de operações&lt;/strong&gt; (Sim é bastante coisa). O programa utiliza a biblioteca &lt;em&gt;&lt;strong&gt;Benchmark&lt;/strong&gt;&lt;/em&gt; para medir e exibir o tempo total gasto para que todas as threads concluam o processamento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'date'&lt;/span&gt;

&lt;span class="no"&gt;NUM_THREADS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="no"&gt;WORK_PER_THREAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100_000_000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trabalho_pesado&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="no"&gt;WORK_PER_THREAD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Thread &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; terminou! &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Iniciando benchmark com &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;NUM_THREADS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; threads... em &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;tempo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realtime&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;NUM_THREADS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

    &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Thread trabalhando: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;trabalho_pesado&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Tempo total: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tempo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; segundos"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Iniciando benchmark com 4 threads... em 2025-05-18T12:15:31-03:00
Thread trabalhando: 31440
Thread trabalhando: 31460
Thread trabalhando: 31480
Thread trabalhando: 31500
Thread 31500 terminou! 2025-05-18T12:16:04-03:00
Thread 31500 terminou! 2025-05-18T12:16:05-03:00
Thread 31500 terminou! 2025-05-18T12:16:05-03:00
Thread 31500 terminou! 2025-05-18T12:16:05-03:00
Tempo total: 34.12 segundos
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nosso programa levou &lt;strong&gt;34,12&lt;/strong&gt; segundos para executar todas essas operações. Pode parecer rápido, mas ainda podemos melhorar. Nesse caso, usamos concorrência para dividir a carga em 4 &lt;em&gt;threads&lt;/em&gt;, que trabalham de forma concorrente, mas não em paralelo. Por que não em paralelo? Será que Ruby não escala?&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;MRI&lt;/strong&gt; utiliza um mecanismo chamado &lt;strong&gt;GIL&lt;/strong&gt; (&lt;em&gt;Global Interpreter Lock&lt;/em&gt;), que garante que apenas uma thread Ruby execute código Ruby por vez, mesmo em máquinas com múltiplos núcleos. Ou seja, o MRI não é &lt;em&gt;thread safe&lt;/em&gt; em nível interno para algumas operações. O GIL impede que múltiplas threads executem código Ruby simultaneamente, evitando problemas de concorrência no gerenciamento de memória e objetos.&lt;/p&gt;

&lt;p&gt;Agora, o que faremos é mudar o interpretador. Até aqui usamos o padrão MRI para os testes, mas vamos migrar para uma implementação que roda sobre a máquina virtual Java, o &lt;em&gt;&lt;strong&gt;JRuby&lt;/strong&gt;&lt;/em&gt;. Esse interpretador permite o paralelismo real entre threads, o que pode melhorar o desempenho nesse caso.&lt;/p&gt;

&lt;p&gt;Vamos mudar a versão do Ruby usando o gerenciador rbenv.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbenv global jruby-9.4.8.0
ruby --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jruby 9.4.8.0 (3.1.4) 2024-07-02 4d41e55a67 OpenJDK 64-Bit Server VM 11.0.26+4-post-Ubuntu-1ubuntu122.04 on 11.0.26+4-post-Ubuntu-1ubuntu122.04 +jit [x86_64-linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora que mudamos o interpretador para &lt;strong&gt;JRuby&lt;/strong&gt;, vamos executar o mesmo código novamente. E o resultado é bem diferente...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iniciando benchmark com 4 threads... em 2025-05-18T12:24:29-03:00
Thread trabalhando: 4004
Thread trabalhando: 4000
Thread trabalhando: 4008
Thread trabalhando: 4012
Thread 4012 terminou! 2025-05-18T12:24:43-03:00
Thread 4000 terminou! 2025-05-18T12:24:43-03:00
Thread 4008 terminou! 2025-05-18T12:24:43-03:00
Thread 4004 terminou! 2025-05-18T12:24:43-03:00
Tempo total: 13.48 segundos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nosso programa, interpretado pelo &lt;em&gt;JRuby&lt;/em&gt;, levou apenas &lt;strong&gt;13,48&lt;/strong&gt; segundos para realizar os 400 milhões de cálculos. Isso representa uma execução aproximadamente &lt;strong&gt;2,5 vezes&lt;/strong&gt; mais rápida, ou cerca de &lt;strong&gt;60,5% de ganho em desempenho&lt;/strong&gt; em comparação com o MRI. Essa diferença acontece porque o &lt;em&gt;JRuby&lt;/em&gt; consegue aproveitar os múltiplos núcleos do processador (A Java Virtual Machine é &lt;em&gt;thread safe&lt;/em&gt;), aplicando paralelismo real entre as threads do programa.&lt;/p&gt;

&lt;p&gt;Isso quer dizer que, para esse programa, precisamos mudar de interpretador para usar paralelismo? Não necessariamente. Podemos continuar usando o MRI e ainda assim aproveitar múltiplos núcleos através do uso de &lt;em&gt;fork&lt;/em&gt;. Isso porque o &lt;em&gt;fork&lt;/em&gt; cria um novo processo, independente do pai, com sua própria memória, seu próprio GIL e sua própria thread principal. Como cada processo é isolado, o sistema operacional pode executá-los em paralelo real, os distribuindo entre os núcleos da &lt;em&gt;CPU&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Vamos mudar para ruby padrão usando rbenv&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rbenv global 3.3.5
ruby --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos executar o programa usando fork&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;

&lt;span class="no"&gt;NUM_PROCESSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="no"&gt;WORK_PER_PROCESS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100_000_000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trabalho_pesado&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="no"&gt;WORK_PER_PROCESS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Iniciando benchmark com &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;NUM_PROCESSES&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; processos (fork)..."&lt;/span&gt;

&lt;span class="n"&gt;tempo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realtime&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="no"&gt;NUM_PROCESSES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fork&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"[Filho #&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] PID: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Pai: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ppid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;trabalho_pesado&lt;/span&gt;
      &lt;span class="nb"&gt;exit!&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;pids&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;pids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Tempo total: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;tempo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; segundos"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Filho #0] PID: 2881532, Pai: 2881489
[Filho #1] PID: 2881535, Pai: 2881489
[Filho #2] PID: 2881538, Pai: 2881489
[Filho #3] PID: 2881541, Pai: 2881489
Tempo total: 8.74 segundos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No final esse foi o resultado dos nossos testes&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Teste&lt;/th&gt;
&lt;th&gt;Interpretador&lt;/th&gt;
&lt;th&gt;Abordagem&lt;/th&gt;
&lt;th&gt;Tempo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Teste 1&lt;/td&gt;
&lt;td&gt;MRI&lt;/td&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;34.12 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Teste 2&lt;/td&gt;
&lt;td&gt;JRuby&lt;/td&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;13.48 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Teste 3&lt;/td&gt;
&lt;td&gt;MRI&lt;/td&gt;
&lt;td&gt;Fork&lt;/td&gt;
&lt;td&gt;8.74 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Teste 3 foi ~&lt;strong&gt;3,9 vezes&lt;/strong&gt; mais rápido e teve um ganho de ~&lt;strong&gt;74,4%&lt;/strong&gt; sobre o Teste 1 e ~&lt;strong&gt;1,5 vezes&lt;/strong&gt; mais rápido e teve um ganho de ~&lt;strong&gt;35,2%&lt;/strong&gt; sobre o Teste 2.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Race condition
&lt;/h2&gt;

&lt;p&gt;Agora vamos falar sobre um problema bastante comum quando tratamos de concorrência, o &lt;em&gt;race condition&lt;/em&gt;. Sabemos que as threads são executadas de forma concorrente, mas como não controlamos a ordem em que elas são executadas, isso fica a cargo do escalonador preemptivo do sistema operacional. Ou seja, quando executamos uma tarefa com múltiplas threads que dependem da ordem de execução, temos o risco de enfrentar uma race condition. Para exemplificar, vamos rodar o código abaixo usando o Ruby padrão (MRI) e o JRuby, assim como nos exemplos anteriores. O programa cria 10 threads e cada uma incrementa a variável contador 1000 vezes. Esperamos que o valor final seja 10000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;contador&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;contador&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"O valor final deve ser 10000"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Valor final do contador: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;contador&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída usando MRI (Versão do ruby 3.3.5)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O valor final deve ser 10000
Valor final do contador: 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saída usando JRuby (Versão jruby-9.4.8.0)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O valor final deve ser 10000
Valor final do contador: 9451
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que a saída com JRuby não retornou a contagem de 10000 e sim 9451, executando outra vez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O valor final deve ser 10000
Valor final do contador: 8800
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No MRI (CRuby), mesmo utilizando múltiplas threads nativas do sistema operacional, existe um bloqueio global chamado GVL (Global VM Lock) que garante que apenas uma thread execute código Ruby por vez na CPU, evitando assim condições de corrida (race conditions) em operações concorrentes sobre variáveis compartilhadas. Esse lock global limita o paralelismo de CPU, liberando-o apenas em operações de I/O ou chamadas de código nativo, o que explica por que o exemplo com múltiplas threads incrementando uma variável compartilhada sempre retorna o valor correto no MRI, ao contrário do que acontece em outras implementações como JRuby que não possuem esse bloqueio global.&lt;/p&gt;

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

&lt;p&gt;Ruby é realmente fantástico, não é? Ele conta com gems (Gem são as bibliotecas de ruby) especializadas para paralelismo, como a gem &lt;a href="https://rubygems.org/gems/parallel/versions/1.11.2" rel="noopener noreferrer"&gt;parallel&lt;/a&gt;, que facilita o uso de múltiplos processos. Além disso, Ruby oferece APIs nativas para a criação de threads, que funcionam muito bem em tarefas de I/O concorrente. É importante lembrar que criar múltiplas threads ou usar fork pode aumentar a complexidade no gerenciamento dos recursos e do fluxo do programa, exigindo cuidado na implementação. Ou seja, não existe uma bala de prata para todas as situações, pois a melhor abordagem depende do contexto e dos objetivos do projeto. Por aqui aprendemos como Ruby lida com processos forkados e threads, a comunicação entre processos e entendemos o risco das race conditions, que acontecem quando recursos compartilhados são acessados sem a devida sincronização.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://concorrencia101.leandronsp.com/" rel="noopener noreferrer"&gt;https://concorrencia101.leandronsp.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.akitaonrails.com/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3" rel="noopener noreferrer"&gt;https://www.akitaonrails.com/2019/03/13/akitando-43-concorrencia-e-paralelismo-parte-1-entendendo-back-end-para-iniciantes-parte-3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.toptal.com/ruby/ruby-concurrency-and-parallelism-a-practical-primer" rel="noopener noreferrer"&gt;https://www.toptal.com/ruby/ruby-concurrency-and-parallelism-a-practical-primer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apidock.com/ruby/Process/fork/class" rel="noopener noreferrer"&gt;https://apidock.com/ruby/Process/fork/class&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rubyguides.com/2015/07/ruby-threads/" rel="noopener noreferrer"&gt;https://www.rubyguides.com/2015/07/ruby-threads/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linuxjourney-com.translate.goog/lesson/monitor-processes-ps-command?_x_tr_sl=en&amp;amp;_x_tr_tl=pt&amp;amp;_x_tr_hl=pt&amp;amp;_x_tr_pto=tc" rel="noopener noreferrer"&gt;https://linuxjourney-com.translate.goog/lesson/monitor-processes-ps-command?_x_tr_sl=en&amp;amp;_x_tr_tl=pt&amp;amp;_x_tr_hl=pt&amp;amp;_x_tr_pto=tc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://man7.org/linux/man-pages/man2/syscalls.2.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man2/syscalls.2.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>programming</category>
      <category>performance</category>
      <category>firstpost</category>
    </item>
  </channel>
</rss>
