1 pontos por GN⁺ 2024-08-11 | 1 comentários | Compartilhar no WhatsApp
  • O Screenshotbot usa o estado em RAM do processo do servidor web como armazenamento de dados, sem um DB separado, e recupera o estado após falhas com snapshots e logs de transações
  • Reduz a serialização SQL, idas e voltas ao DB e serviços separados de tarefas em segundo plano para simplificar a experiência de desenvolvimento em um único processo, aproveitando índices em memória e processamento baseado em threads
  • A alta disponibilidade é obtida replicando o log de transações em 3 servidores com o algoritmo de consenso Raft; se o líder falhar, um novo líder assume as requisições em poucos segundos
  • A implementação real combina Common Lisp, bknr.datastore, o bknr.cluster open source, Baidu Braft e EFS; reinicializações do servidor normalmente acontecem apenas uma vez a cada 1–2 meses
  • Mesmo quando o CI de um cliente enterprise conhecido chama a API centenas de vezes a cada commit e Pull Request, o uso de CPU em uma máquina de 4 cores e 16 GB chega no máximo a cerca de 20%, principalmente por processamento de imagens

Uma estrutura que trata o estado em RAM como banco de dados

  • Serviços web tradicionais geralmente escolhem um framework web como Rails, Django ou Node junto com um banco de dados como MySQL, PostgreSQL ou MongoDB
  • Nos últimos 10 anos, surgiram mudanças de ambiente que tornam essa abordagem digna de ser reavaliada
    • Discos ficaram mais rápidos com NVMe
    • Discos como EBS/EFS ficaram mais robustos
    • RAM ficou mais barata, permitindo que os dados da maioria das startups caibam em RAM
    • É possível alugar máquinas com centenas de cores
    • Em 2014, o algoritmo de consenso Raft foi publicado, e aumentou o número de implementações robustas
  • Esta arquitetura combina o serviço web e a instância de banco de dados, usando o estado de memória do processo como se fosse um banco de dados
  • Diferentemente do SQLite, ela não serializa os dados para armazená-los; lida diretamente com objetos e campos em RAM como estado da aplicação

Explore: como simplificar a iteração inicial do produto

  • Quando todos os dados estão em RAM, não é preciso serializá-los por meio de consultas SQL, e também se reduz a arquitetura em que vários servidores front-end se conectam a um único DB
  • Se a carga aumentar, é possível responder usando um servidor maior, com mais RAM e CPU
  • Para índices, em vez de B-trees ajustadas à latência de disco, podem ser usadas estruturas como tabelas hash em memória
    • No Screenshotbot, índices usando functional collections foram importantes para a escalabilidade
  • Como a leitura de dados é uma leitura em RAM, não são necessárias estruturas especiais nem Async-IO para reduzir idas e voltas ao DB
  • Tarefas em segundo plano se tornam threads executadas dentro do mesmo processo grande, e a concorrência pode ser resolvida em grande parte com mutexes em memória e condition variables

Recuperação de falhas: snapshots e logs de transações

  • Para lidar com crashes do processo, todo o estado em RAM é salvo periodicamente como snapshot
  • Mudanças ocorridas após o último snapshot são primeiro registradas em disco como log de transações, e só então o estado em memória é alterado
    • foo.setBar(2) registra uma transação dizendo que o campo bar de foo mudou para 2 e depois altera o campo real
    • new Foo() registra uma transação dizendo que um objeto Foo foi criado e depois retorna o novo objeto
  • Ao reiniciar, o sistema primeiro lê o snapshot e depois reproduz o log de transações para recuperar o estado
  • Alterações de índices não precisam entrar no log de transações
    • Por exemplo, se houver um índice no campo bar de Foo, setBar atualiza o índice, e essa atualização também ocorre novamente durante o carregamento do snapshot ou a reprodução das transações

O tipo de código viabilizado por um único processo

  • Como todas as requisições são processadas no mesmo processo, é possível armazenar closures na memória do servidor e usá-las no tratamento de requisições de página
  • URLs do Screenshotbot no formato https://screenshotbot.io/n/nnnnnnn são mapeadas para closures internas
  • Como a closure mantém referências a objetos, há menos necessidade de passar IDs de objetos ou serializar objetos a cada transição de página
  • Depuração, profiling e monitoramento também ficam restritos a um único serviço
    • Desaparecem pontos de observação separados de DB, como o MySQL slow query log
    • Se um único serviço cair, o site também cai, mas a visão é que, quando há apenas um serviço e um servidor, a probabilidade de falha também é menor
    • Se o servidor morrer, a AWS pode subir um novo servidor em poucos minutos
  • O código de testes também fica mais simples, pois não é necessário fazer mock do banco de dados

Expand: obtendo alta disponibilidade com Raft

  • Quando clientes de alto risco exigem 99,999% de disponibilidade, uma arquitetura de servidor único não é suficiente
    • Em caso de falha do servidor, podem ser necessários alguns minutos até a AWS subi-lo novamente
    • O processo também pode levar alguns minutos para restaurar o snapshot em disco
    • Durante uma nova implantação, a reinicialização do serviço pode deixar o servidor fora do ar por vários minutos
  • O algoritmo de consenso Raft replica o log de transações do servidor web/banco de dados, que é uma máquina de estados finita
  • Com replicação em 3 máquinas, se o líder falhar, um novo líder é eleito em poucos segundos e continua processando requisições
  • Sem mudar muito a forma como o desenvolvedor escreve código, é possível transformar um serviço simples em algo parecido com um banco de dados de alta disponibilidade
  • Em uma configuração baseada em Raft, também é possível fazer deploy rolling sem derrubar os servidores

Extract: sharding e gargalos previstos

  • Na etapa de atender mais grandes clientes, é possível aplicar sharding, como grandes empresas fazem em bancos de dados
  • Cada shard pode se tornar seu próprio cluster
  • O Screenshotbot já oferece um cluster dedicado para cada cliente enterprise
  • A Meta migrou para Raft para lidar com replicação de clusters MySQL, e o Screenshotbot usa uma abordagem semelhante sem um banco de dados separado
  • O principal candidato a gargalo é a escalabilidade da commit-thread
    • Threads de leitura paralelizam bem
    • Uma única commit-thread aplica as transações uma por uma
    • Como o Raft faz commit de várias transações juntas no disco, a latência de disco não é importante
    • O ponto de preocupação é quando o custo de CPU para aplicar transações ultrapassa o desempenho de um único core
    • Nesse caso, é possível fazer profiling do custo de commit e mover parte do trabalho para fora da thread de transação, ou considerar sharding

A stack real do Screenshotbot

  • O Screenshotbot usa Common Lisp
  • A implementação inicial usava MySQL, mas ele foi substituído por bknr.datastore porque era difícil lidar com concorrência no MySQL
  • bknr.datastore é uma biblioteca que oferece para Common Lisp a estrutura descrita na etapa Explore
  • Nessa estrutura, threads dentro de um único processo tratam as requisições web, portanto multithreading forte é importante
    • Por causa desse requisito, Ruby e Python são considerados inadequados
  • Como closures são mantidas na memória do servidor, é difícil reiniciar o servidor com frequência
    • Ao reiniciar, as closures são perdidas
    • Em vez disso, o código é atualizado com hot reloading no processo em execução
    • Common Lisp oferece recursos como reinitialize-instance, um padrão para atualizar objetos existentes quando definições de classes mudam

Cluster, armazenamento de arquivos e escala operacional

  • Atualmente, reinicializações do servidor normalmente ocorrem cerca de uma vez a cada 1–2 meses
  • Quando uma reinicialização é necessária, é feito um rolling restart no cluster Raft
  • Cada instalação usa um cluster de 3 servidores, configuração que tolera a queda de 1 servidor
  • Kubernetes não é usado atualmente, e ainda não é considerado necessário
  • A implementação de Raft é uma biblioteca própria criada sobre o bknr.datastore
    • bknr.cluster foi lançado como open source
    • Internamente, ele usa o Braft da Baidu
    • O Braft lida com snapshots em segundo plano, permitindo que o servidor continue processando requisições durante a criação do snapshot
  • Arquivos de imagem ou blobs que não devem entrar no datastore são armazenados em EFS, compartilhado pelos três servidores
    • EFS é NFS de alta disponibilidade
    • É considerado mais fácil de trabalhar que S3, pois não exige tratar condições de erro separadamente
    • Como escreve em disco sem interagir com servidores externos, a testabilidade também é maior

Desempenho atual e escopo de aplicação

  • O Screenshotbot atende alguns grandes clientes enterprise e, em especial, um cliente bastante conhecido
  • Ele roda no CI desse cliente e recebe centenas de requisições de API a cada commit e Pull Request
  • Mesmo com essa carga, uma máquina de 4 cores e 16 GB é suficiente para processar as requisições
    • Servidores de réplica também usam máquinas semelhantes e ficam ociosos na maior parte do tempo
    • O uso de CPU chega no máximo a cerca de 20%
    • A maior parte do uso de CPU vem do processamento de imagens
  • É importante não projetar assumindo uma escala maior do que a necessária
  • Se você escolher Common Lisp, a configuração relacionada está disponível no Screenshotbot OSS

1 comentários

 
GN⁺ 2024-08-11
Opiniões no Hacker News
  • Essa arquitetura é mais ou menos parecida com a forma como o Nomad, o Consul e o Vault da HashiCorp foram criados (sou um dos mantenedores do Nomad). Com certeza é uma arquitetura peculiar, mas, depois que você se acostuma, a experiência de desenvolvimento é bem boa
    Como o estado em memória pode ficar no formato que você quiser, dá para criar diretamente funções de indexação e consulta sob medida para a aplicação. Você até pode usar SQLite :memory: na FSM do Raft, mas, se conseguir criar ou encontrar um armazenamento transacional em memória (nós usamos nosso próprio go-memdb), a leitura de estado vira simplesmente uma chamada de função. Também é simples evitar leituras obsoletas ou viés de escrita. Como todo objeto escrito tem um índice Raft, dá para criar uma API do tipo “consulte o objeto foo em um follower, mas espere pelo menos até o índice 123”. Isso remove muita da “mágica” que normalmente é empurrada para um RDBMS ou armazenamento externo
    Dito isso, eu teria cautela ao escolher essa arquitetura para uma startup nova fora da área de “infraestrutura”. Na prática, você acaba criando seu próprio banco de dados. É preciso escolher bem ou escrever por conta própria componentes básicos como RPC entre nós, persistência em disco e armazenamento transacional de estado em memória. Upgrades são especialmente difíceis, e o código novo pode tentar escrever no log do Raft entidades que nós em versões antigas não entendem. Pior ainda: a forma de processamento pode mudar e fazer com que nós antigos interpretem algo de maneira errada. Não existe almoço grátis

    • A parte “você até pode usar SQLite :memory: na FSM do Raft” era o desenho básico que o rqlite[1] usou nos primeiros cerca de 7 anos. Mas o rqlite migrou para SQLite baseado em disco, e, usando modo WAL e PRAGMA synchronous=OFF[2], ficou tão rápido quanto escrever na RAM, ou perto o suficiente. Com isso, também foi possível evitar as limitações de um banco SQLite :memory:, entre elas o limite máximo de 2 GB. Deveríamos ter usado o modo em disco desde o início, mas só percebemos agora
      Como o rqlite usa a mesma biblioteca Raft[3] que o Nomad, talvez você já saiba parte disso
      O problema de upgrades existe de verdade. Fico curioso se no Nomad vocês o encontram com frequência em campo. Em 10 anos de desenvolvimento do rqlite, introduzir um novo tipo de Raft Entry foi algo muito raro, e usuários reais só se depararam com isso uma vez. Uma forma de lidar com isso é primeiro distribuir uma versão que entende o novo tipo, mas nunca o escreve, e, depois que essa versão estiver totalmente instalada, atualizar para uma versão que de fato usa o novo tipo. Mas nunca fizemos isso na prática, e também exige disciplina dos usuários finais
      [1] https://www.rqlite.io
      [2] Pode soar perigoso, mas, no desenho atual do rqlite, na inicialização o banco SQLite principal é totalmente reconstruído a partir do log do Raft (o log do Raft recebe fsync a cada escrita). Então, mesmo que o banco SQLite seja corrompido por uma queda de energia ou algo parecido, isso não tem grande importância, já que o banco SQLite não é o armazenamento de dados autoritativo do rqlite
      [3] https://github.com/hashicorp/raft
    • Comparado a outras respostas aqui, também estou mais aberto à ideia de manter os dados em memória. Quando vi no texto que eles usam Common Lisp e hot reloading, pensei: “essa equipe pode fazer o que quiser. Mas nem todo mundo trabalha nessa equipe”
    • A afirmação de que “upgrades são especialmente difíceis” está certíssima. Mas isso não se aplica a startups na fase Explore, que não precisam de replicação, e nós também ficamos muito tempo assim. É justamente nessa fase que essa arquitetura é mais útil para iterar no produto
      Dito isso, quando se começa a usar replicação na fase Expand, surgem desafios de engenharia, sim. Ainda assim, são problemas solucionáveis. No Common Lisp, o fato de poder fazer hot reload do código também ajuda, porque torna algumas migrações muito mais fáceis
  • Décadas atrás, PG escreveu que não usava banco de dados na Viaweb, e que parecia estranho que apps web virassem uma espécie de frontend de banco de dados, quando apps desktop não eram assim[0]. O HN também não usa banco de dados
    Mas, como apps desktop e móveis modernos passaram a usar bancos de dados com frequência, geralmente SQLite, isso já não é uma afirmação tão válida. Ficou claro que armazenamento e consultas relacionais de dados são bastante úteis em uma gama muito ampla de aplicações
    [0] https://www.paulgraham.com/vwfaq.html

    • Lendo o link, parece que “banco de dados” não significa a mesma coisa para todo mundo
      O vwfaq ainda diz que os dados são lidos do disco, e também fala em “iniciar um processo para responder a uma requisição HTTP”. Aqui, “banco de dados” parece significar um servidor separado que persiste os dados, e uma arquitetura em que é preciso se comunicar com outro servidor para buscá-los
      Por essa definição, claro que SQLite fica fora da categoria de banco de dados. E, se você já está lendo dados do disco, então está usando um banco de dados ou implementou uma camada de persistência própria ad hoc. A questão é se ler dados do SQLite na inicialização do app ainda conta como usar um banco de dados
      O problema dessa linha de raciocínio é que ela ignora que o ponto central de um banco de dados é armazenar e recuperar dados de forma conveniente, sem precisar se preocupar com detalhes de baixo nível. Armazenar dados em um banco de dados não quer dizer subir uma instância do Postgres em algum lugar e buscar os dados pela web. Se você mantém todos os dados em memória e tem um processo que salva snapshots em disco com uma estrutura de dados baseada em log, parabéns: você acabou de criar seu próprio banco de dados
    • É claro que houve influência do texto do PG. Nós também usamos Common Lisp, e é difícil escapar do PG nessa área. Dito isso, parece que a Viaweb não usava um log de transações como o bknr.datastore, e isso tornou o processo de desenvolvimento muito mais fluido
    • Quando PG escreveu a Viaweb, o próprio SQLite também não era tão onipresente quanto hoje. Corrigindo: naquela época, o SQLite nem existia. Sem SQLite, e com opções basicamente limitadas a stores chave-valor, na maioria dos casos provavelmente bastaria usar o sistema de arquivos
      Em segundo lugar, nos últimos 20 anos as consultas em RDBMS ficaram muito mais simples. Surgiram todos os tipos de ORMs e mapeadores de linhas que reduzem código repetitivo
      Também surgiram recursos avançados, como busca de texto completo, úteis para apps desktop e móveis. Hoje, usar um RDBMS em um app desktop é uma boa escolha
    • Como assim o HN não usa banco de dados? Pode explicar melhor? É bem surpreendente
    • Naquela época, o contexto era outro. Pelo que sei, a Viaweb era composta por várias instâncias de Common Lisp, e todo o estado das sessões dos usuários ficava na memória de máquinas individuais. Lembro de ter lido em algum lugar que eles corrigiam bugs em tempo real, falando com usuários no telefone, em produção
      A web ficou muito maior, e muitas dessas práticas provavelmente não funcionariam hoje. Se alguém empurrasse uma correção ao vivo para uma máquina de produção, com o nível atual de exigência de testes, enquanto fala com um cliente no telefone, muitos aqui questionariam meu estado mental
  • Entendo a vontade de experimentar algo interessante, mas fazer isso para evitar aprender o básico de MySQL ou Postgres parece uma enorme perda de tempo. É só construir em cima disso e pronto, especialmente se estiver rodando em nuvem pública
    Não compro os argumentos sobre aumento de latência de ida e volta ou problemas de concorrência. Para o segundo caso, há soluções simples, como tunagem básica ou isolar clientes barulhentos. Em outro post do blog deles, eles falam da possibilidade de adicionar 10 milhões de linhas por dia e de desafios de indexação, mas isso literalmente não é nada. Mesmo 10 vezes isso, na minha opinião, ainda não justificaria fazer engenharia de uma solução sob medida
    Até que isso seja realmente necessário, “worse is better” está certo; e, quando chegar a hora, você saberá com clareza. Nesse ponto, também saberá qual é o gargalo, e poderá reagir de forma mais inteligente do que superdimensionar tudo desde o início

    • Se for um mecanismo de banco de dados baseado em servidor, ainda dá para argumentar que se reduz chamadas de rede. É duvidoso, mas possível
      Mas é desconcertante o blogueiro dizer que não escolhe SQLite porque ele pode ter recursos desnecessários. Isso é absurdo e não justifica nada
      O texto soa como se partisse de uma solução ruim para um problema hipotético e depois fizesse uma tentativa desesperada de empilhar argumentos forçados para rejeitar a solução óbvia
    • O que me pergunto é se o negócio teria dado certo caso eles não tivessem gasto tempo reinventando a roda. Só o fato de construir em público e escrever no blog já divulga o produto e demonstra competência técnica. Se tivessem usado tecnologias chatas que funcionam bem quando combinadas, haveria menos coisa para contar e, portanto, menos efeito de divulgação?
      Fico pensando se estou errado, ou se esse esforço extra, controverso mas aparentemente desnecessário, faz parte do produto e é uma forma de vencer nesse espaço
  • Começa com “não estou falando de algo como SQLite, em que os dados ainda são serializados”, mas acaba em um log de transações próprio que precisa de serialização e também de replicação, o que é estranho. Replicação de banco de dados já funciona assim mesmo.
    Se toda a carga vai para um único servidor, basta rodar o banco de dados nesse servidor e esquecer coisas como “uma arquitetura especial para reduzir idas e vindas ao banco de dados”. Se todos os dados cabem na RAM, se quiser use um ramdisk para o banco de dados e replique para armazenamento persistente com ferramentas padrão. Isso é de fato simples.

    • No geral, a sensação é que se resume a “criamos nosso próprio SQLite + replicação Raft”. Só que sem a confiabilidade comprovada do SQLite nem a capacidade de transferir memória para disco de forma eficiente.
      Então, basicamente, parece https://litestream.io/. Graças à configuração explícita com Raft, o failover talvez seja mais rápido. Não sou usuário do Litestream, então não sei as diferenças sutis, mas soa muito parecido.
      Apesar dessa simplificação excessiva, gosto bastante da ideia em si, e acho que o texto defende os conceitos razoavelmente bem. Em muitos sistemas, mesmo que tenham um sucesso anormal, isso escalaria o suficiente para sustentar a maior parte ou até todo o negócio, e o desempenho seria absurdamente bom comparado a praticamente qualquer alternativa.
    • Acho que nem ramdisk é necessário. Bancos de dados já fazem cache de tudo em memória, e só as escritas chegam ao disco.
      Basta dar um cold start no banco de dados e executar um select bem grande duas vezes.
    • É importante entender que toda startup passa por três fases: Explore, Expand, Extract. O que é simples em uma fase não é simples em outra.
      Bancos de dados transacionais são simples nas fases Expand e Extract, mas na fase Explore viram uma carga extra, porque fazem você se concentrar em problemas de infraestrutura em vez do produto. Na fase Explore, não há clientes, então não há dados, e a confiabilidade dos dados não é importante.
      A abordagem de manter tudo em memória com bknr.datastore (sem replicação) é simples na fase Explore. Mas, ao entrar na fase Expand, surge a carga operacional de garantir consistência dos dados.
      Ainda assim, quando você chega à fase Expand, já validou o produto e escreveu bastante código. Reescrever tudo para um banco de dados transacional não faz sentido; é mais fácil adicionar replicação Raft por cima.
    • Concordo. Reinventar o WAL significa reinventar, ou ignorar, todos os problemas que vêm junto com ele. Parece que recuperar a partir do log leva bastante tempo, então provavelmente nem chegaram direito ao checkpointing de log ainda.
    • Sistemas de trading colocam tudo, descaradamente, em estruturas pré-alocadas na RAM. Tudo depende de quais trade-offs você escolhe.
  • A lógica deste texto é desconcertante. Isso seria uma forma de tornar aplicações com estado mais simples e mais rápidas?
    As premissas são fracas e as afirmações, exageradas. O autor exagera a dificuldade da serialização para fazer um argumento fraco parecer mais forte.

    • O post deixa uma forte impressão de “vejam como somos inteligentes”.
      Pessoas assim geralmente são difíceis de trabalhar. Que bom que encontrei uma startup para afundar, de modo que eu não precise lidar com elas.
    • E então implementa serialização para escrever transações no log e replicá-las para outros nós.
  • Ao iniciar um novo projeto, a estrutura de dados costuma ser uma “lista de itens com atributos”. Por exemplo, estou escrevendo agora um app de fitness, e os dados são uma lista de exercícios; cada exercício tem título, descrição, URL de vídeo e outros atributos.
    Normalmente começo colocando esses itens em arquivos YAML no diretório data. Na prática, é um dialeto YAML customizado, sem as esquisitices do YAML original. Cada valor é uma string, sem conversões mágicas de tipo. Criar um novo item é só fazer vim crunches.yaml e inserir os dados; editar e excluir também é muito fácil nessa estrutura de dados.
    Quando o projeto cresce, normalmente crio um schema de DB e movo os itens para MariaDB ou SQLite.
    Desta vez, pretendo mover os itens (exercícios) para uma coluna JSON em um DB SQLite. Vou armazenar todos os atributos de um item em um único campo JSON e escrever um pequeno explorador de DB que permita editar campos JSON como se fossem YAML. A ideia é manter a conveniência de editar dados legíveis por humanos.
    Escrever o explorador de DB parece bem direto. Com um pouco de ncurses, dá para navegar pelas tabelas, selecionar uma, navegar pelas linhas e inserir ou excluir linhas. Ao editar um campo, ele abre o Vim. Se o campo for JSON, converte para YAML antes de enviar ao Vim e, quando o usuário sai do Vim, converte de volta para JSON.

  • O que foi descrito no começo do texto era basicamente parecido com a forma como máquinas NUMA funcionavam (por exemplo, SGI Altix ou UV). Além disso, as vantagens que eles alegavam eram baixa latência e a capacidade de paralelizar o trabalho com multithreading em uma grande RAM. Clustering surgiu como uma alternativa de baixo custo a máquinas de mais de 1 milhão de dólares. Também há semelhanças com a persistência do AS/400, em que, se o app simplesmente escreve na memória, isso é mapeado de forma transparente para o disco.
    Agora estamos meio que voltando no tempo, com hardware barato, para as vantagens de máquinas NUMA em cluster. Houve melhorias nesse processo, e o texto foi uma leitura interessante.
    Outra técnica antiga era remover a pilha TCP/IP dentro do cluster, eliminando os problemas associados. Soluções como Active Messages eram uma camada fina sobre o hardware. Também existem projetos de roteadores de rede com consistência forte embutida. Há bastante coisa que eles podem fazer.
    Se crescerem, há também oportunidades de hardware. No lado de CPU, a SGI fez duas coisas. As máquinas NUMA aumentaram o número de CPUs e a quantidade de RAM em um único sistema, e permitiram plugar FPGAs diretamente no barramento de memória para usá-los como aceleradores customizados. Por fim, alguns artigos de ciência da computação modificaram o conjunto de instruções do processador, a rede interna do chip etc. para eliminar ou reduzir gargalos de multithreading. Chips como o OpenPiton aumentam o número de núcleos com cores abertos e customizáveis (por exemplo, 32).

  • “Imagine as coisas incríveis que poderíamos construir se não precisássemos serializar dados em consultas SQL” já existe em implementações suficientemente maduras do modelo de atores[0], por exemplo no Akka Event Sourcing[1]. Também aborda o seguinte problema:
    “Mas a parte importante é como se recuperar quando um processo sofre crash. A resposta é fácil: basta tirar snapshots periódicos de toda a RAM”
    Isso é resolvido essencialmente sem precisar criar uma “nova arquitetura para desenvolvimento web”. Também há tentativas open source de explorar o protocolo RAFT usando atores aqui[2] e aqui[3]
    0 - https://en.wikipedia.org/wiki/History_of_the_Actor_model
    1 - https://doc.akka.io/docs/akka/current/typed/persistence.html
    2 - https://github.com/Michael-Dratch/RAFT_Implementation
    3 - https://github.com/invkrh/akka-raft

  • Meu primeiro pensamento foi: “Ah, é o jeito que eu fazia quando escrevia em Common Lisp; que divertido alguém ter redescoberto essa técnica”
    Mas não era isso; havia simplesmente mais gente de Lisp

  • “RAM é muito barata” talvez seja um dos maiores equívocos dos desenvolvedores
    SSDs melhoraram de 100 a 10000 vezes em throughput ou IOPS, e o desempenho por dólar das vCPUs também subiu de 20 a 50 vezes. Saímos de 45/32 nm e chegamos agora a 5 nm/3 nm, e o número de instruções por ciclo também aumentou muito
    Mas o preço da RAM quase não caiu tanto quanto CPU ou SSD. Ela pode ter ficado muito mais rápida, é possível instalar muito mais memória com chips de maior densidade, e os canais podem ter aumentado de dual para 8 ou 12. Mas, olhando os preços spot de DRAM de 2008 a 2022, o preço mínimo da DRAM ficou três vezes no mesmo patamar, cerca de US$ 2,80/GB. No mesmo período, dentro dos ciclos, oscilou entre US$ 6 e US$ 8/GB. Ou seja, se você comprou DRAM nos pontos mínimos ou máximos dos últimos cerca de 15 anos, ignorando a inflação, o preço provavelmente foi parecido, na faixa de aproximadamente ±10–20%
    Só em meados de 2022 a barreira de US$ 2,80/GB foi rompida, caindo para perto de US$ 1/GB, antes de se estabilizar em cerca de US$ 2/GB para DDR5
    Hoje dá para colocar 4 TB de RAM em um servidor. Isso não significa que DRAM seja absurdamente barata. O desenvolvedor médio, ou o desenvolvedor de big tech, passou a ganhar muito mais do que em 2010, então sente que RAM ficou muito mais acessível. Na prática, mesmo no ponto mais baixo dos últimos 15 anos, a queda no preço da DRAM foi, no máximo, pouco mais de 2 vezes. E há uma grande chance de os preços da DRAM dispararem de novo em 1 ou 2 anos

    • Também há outra interpretação possível. A capacidade máxima de RAM de nós individuais aumentou drasticamente nas últimas décadas
      Como exemplo simples, se há 20 anos um determinado nó fosse limitado a 16 GB de RAM, seriam necessários 256 nós para obter 4 TB de RAM no sistema (excluindo o overhead de cada SO)
      Hoje, um único nó consegue colocar esses 4 TB inteiros em um só chassi
      O custo total dos próprios chips de RAM pode não ter mudado, mas o custo de realmente usar essa RAM em um sistema físico caiu drasticamente
    • Ao mesmo tempo, muitos desenvolvedores recorrem cedo demais a sistemas distribuídos, mesmo quando bastaria comprar mais RAM. Talvez fosse isso que o autor quisesse dizer
    • Obrigado pelos dados. Concordo com a outra resposta. Acho que eu deveria ter dito que ficou mais fácil obter máquinas com centenas de GB de RAM, em vez de dizer que é “barata”