2 pontos por GN⁺ 2024-10-15 | 1 comentários | Compartilhar no WhatsApp
  • O Durable Object da Cloudflare mudou de um armazenamento de chave/valor para um armazenamento relacional baseado em SQLite, criando uma estrutura em que a lógica da aplicação e os dados são executados no mesmo host físico
  • A escalabilidade é feita dividindo os Durable Objects por unidade lógica de estado, como documentos, usuários ou shards de banco de dados, em vez de aumentar um único objeto, já que um objeto individual tem o limite de uma única máquina e uma única thread
  • Em sistemas com unidades de estado bem definidas, como reservas de voos, é possível ter um Durable Object dedicado e seu próprio banco SQLite para cada voo, o que pode gerar milhares de novos bancos de dados por dia para cada companhia aérea
  • A durabilidade é composta por streaming de entradas WAL, armazenamento em object storage e replicação para data centers próximos, com lotes a cada 16 MB ou 10 segundos e suporte a recuperação pontual de até 30 dias
  • A API JavaScript adotou um modelo bloqueante para operações persistentes rápidas em thread única, e a localização do Durable Object permanece fixa após a criação, embora uma realocação dinâmica esteja planejada para o futuro

Design do Durable Object reformulado com SQLite

  • O Durable Object da Cloudflare foi atualizado de um armazenamento de chave/valor para um sistema relacional baseado em SQLite
  • A ideia central é colocar a lógica da aplicação no mesmo lugar que os dados manipulados por essa lógica
    • Um Durable Object consiste em código executado no mesmo host físico que o banco de dados SQLite usado por ele
    • A proposta é processar leituras e escritas sem ida e volta de rede, o que facilita reduzir a latência
  • O processamento em larga escala é tratado criando mais objetos, em vez de aumentar a capacidade de um único objeto
    • Um único objeto roda em uma única thread de uma única máquina, então há um limite intrínseco de throughput
    • Quando objetos separados assumem diferentes unidades lógicas de estado, como documentos, usuários ou shards de banco de dados, a escalabilidade fica mais simples
  • Em um sistema de reservas de voos, cada voo pode ser mapeado para um Durable Object dedicado com seu próprio banco de dados SQLite
    • Nessa estrutura, milhares de novos bancos de dados podem ser criados por dia para cada companhia aérea
  • Cada Durable Object tem um nome único, e a rede da Cloudflare encaminha as requisições para o local onde esse objeto está dentro de sua rede global

Durabilidade garantida com streaming de WAL e replicação

  • Um sistema baseado em Durable Objects, inspirado no Litestream, faz streaming contínuo da sequência de entradas WAL de cada objeto para object storage
    • Os lotes são enviados a cada 16 MB ou a cada 10 segundos
    • Reproduzindo as transações registradas, é possível fazer recuperação pontual por até 30 dias
  • Para reforçar a durabilidade dentro da janela de 10 segundos, as escritas são enviadas logo após o commit para 5 réplicas em data centers próximos e distintos
    • Destas, 3 precisam confirmar para que a resposta de escrita seja aprovada
  • O sistema subjacente dos Durable Objects é o Storage Relay Service, que já opera o sistema separado D1 SQLite system da Cloudflare há mais de um ano

API e comportamento da localização dos objetos

  • A API JavaScript usa um modelo bloqueante, e não async
    • O objetivo do design é oferecer operações persistentes rápidas em thread única
    • O código de exemplo usa deliberadamente o padrão de consultas N+1, consultando a lista de documentos e depois buscando o nome do autor de cada documento em uma consulta separada
    • O texto relaciona isso ao fato de o SQLite se adequar bem a esse padrão
  • Atualmente, um Durable Object não muda de localização depois de ser criado
    • Por padrão, ele é instanciado no data center próximo ao local em que a primeira chamada get() foi feita
    • Para criá-lo manualmente em outro local, é possível fornecer o parâmetro opcional locationHint em get()
    • A realocação dinâmica de Durable Objects existentes está planejada para o futuro
  • where.durableobjects.live é um site que rastreia onde novos Durable Objects são criados na rede da Cloudflare
    • Em uma visita de exemplo, ao carregar a página em Half Moon Bay, foi mostrado que o worker de San Jose criou o Durable Object em San Jose

1 comentários

 
GN⁺ 2024-10-15
Comentários do Hacker News
  • Há mais alguns pontos interessantes: a API de escrita é síncrona, mas há uma espera assíncrona oculta, então, se a escrita falhar ao emitir a próxima resposta, o runtime transforma a resposta em uma falha HTTP
    Assim, sem que o usuário precise tratar explicitamente erros ou esperas, o runtime pode fazer batching automático das escritas e assumir sucesso de forma otimista
    Não há transações de leitura, mas elas provavelmente seriam úteis para obter um ponteiro para um snapshot de um determinado momento
    Cada instância do runtime é limitada a 128 MB de RAM
    WebSockets podem entrar em hibernação e não são cobrados enquanto estão dormindo, então é possível manter clientes conectados mesmo quando o DO está dormindo
    Também há algo parecido com RPC automático, que permite tratar outro DO ou Worker como uma chamada JS comum, embora na prática ele possa chamar outro datacenter, e a serialização e o parsing fiquem a cargo do runtime

    • A parte de RPC é bem interessante. Há mais detalhes aqui: https://blog.cloudflare.com/javascript-native-rpc/
    • A espera assíncrona oculta da API de escrita lembra o commit_delay do PostgreSQL. O princípio não é exatamente o mesmo, mas a sensação é parecida: https://www.postgresql.org/docs/current/runtime-config-wal.h...
      O Litestream mencionado no texto também propõe uma técnica semelhante
    • Fico curioso se há algum caso de uso concreto aqui que exija transações de leitura no nível do banco de dados
      No SQLite comum, vários processos podem acessar o mesmo banco de dados ao mesmo tempo, então transações de leitura são úteis
      Aqui, como apenas um único processo pode acessar o banco, é possível obter o mesmo efeito fazendo todas as leituras dentro de uma função síncrona ou implementando manualmente um bloqueio em nível de processo
  • O fato de as entradas do WAL serem transmitidas em lote para o armazenamento de objetos a cada 16 MB ou a cada 10 segundos parece significar que pode levar até 10 segundos para que uma escrita seja lida de forma confiável globalmente
    Não entendo bem como isso poderia substituir clusters de bancos de dados regionais que respondem em milissegundos em escala continental
    Entendo que eles usam streams, mas o destino são apenas 5 seguidores, e a Cloudflare tem centenas de datacenters
    Se todas as instâncias do SQLite não estiverem sempre conectadas, é fisicamente impossível garantir leituras em escala de segundos; e, mesmo conectadas, a latência de pacotes pode causar problemas

    • Pelo que sei, escritas e leituras acontecem apenas no mesmo processo, então o armazenamento de longo prazo só entra em ação quando o processo atual está em hibernação
      Se você escreve e lê logo em seguida, o resultado aparece imediatamente. Isso porque a escrita também atualiza o estado em memória do processo atual
      Se outro processo, por exemplo outro DO ou Worker, quiser acessar os dados, ele precisa passar pelo DO que possui os dados, então enviará uma chamada RPC ou uma requisição HTTP e receberá as informações mais recentes
      Como a hibernação ocorre depois de certo tempo de inatividade, ao contrário do que se poderia imaginar, os casos em que uma escrita fica indisponível parecem se limitar mais ou menos a situações em que o DO ou Worker trava logo após a escrita
    • As escritas são transmitidas quase em tempo real para 5 seguidores e confirmadas quase imediatamente
      O post do blog da Cloudflare trata dessa parte com mais detalhes. Assim, eles mantêm as escritas rápidas e ainda garantem durabilidade
    • Acho que houve um mal-entendido sobre como Durable Objects funcionam. Todo o tráfego destinado ao mesmo objeto é roteado para a única máquina onde esse objeto existe
      Essa máquina sempre tem uma visão consistente do seu próprio banco de dados SQLite
      É possível criar bilhões de objetos, mas cada objeto tem seu próprio banco de dados separado
      Não há como ler esse banco de dados diretamente a partir de uma máquina diferente daquela em que o DO está rodando
    • As entradas do WAL transmitidas para o armazenamento de objetos provavelmente são para backup
      Cada DO é único globalmente, e o DO de um determinado ID roda em apenas um lugar por vez, usando SQLite no armazenamento local daquele datacenter
  • O que ainda não entendo sobre Durable Objects é a localização física
    Fico curioso se eles ficam localizados na região que hospedou a chamada de API que causou sua criação inicial
    Se for assim, também fico curioso se existe algum mecanismo para mover automaticamente um DO para outro local quando fica evidente que ele foi criado na América do Norte, mas depois todo o tráfego de leitura/escrita vem da Austrália

    • Por padrão, ele fica na região em que foi criado, mas, como alternativa, é possível especificar locationHint. Para a Austrália, use "oc": https://developers.cloudflare.com/durable-objects/reference/...
      Também vale observar que “a realocação dinâmica de Durable Objects existentes está planejada para o futuro”
    • https://where.durableobjects.live é um bom site que mostra onde um DO está localizado
      Apenas cerca de 10~11% dos PoPs da Cloudflare hospedam Durable Objects
      Uma solicitação que chega por outro PoP para criar um DO é encaminhada para um dos PoPs próximos que o hospedam
    • Atualmente, Durable Objects não mudam de localização depois de criados, e a realocação dinâmica de Durable Objects existentes está planejada para o futuro: https://developers.cloudflare.com/durable-objects/reference/...
      Pelo que me lembro, Orleans(https://www.microsoft.com/en-us/research/wp-content/uploads/...) consegue mover atores entre máquinas, e esse modelo parece combinar bem com uma abordagem de mover DOs entre locais
    • Durable Objects têm armazenamento de longo prazo e são hidratados a partir dele, então, nesse sentido, podem ser movidos para qualquer data center da Cloudflare
      Mas não há uma chamada de API para mover um Durable Object
      Não deve haver conexões, e ele é recriado no data center mais próximo da próxima conexão, ou da primeira
      Nesse momento, a memória desaparece, mas o armazenamento permanece
      Recentemente surgiram recursos detalhados relacionados à hibernação, então essa explicação pode estar um pouco desatualizada
    • Não é uma resposta à pergunta, mas recomendo https://where.durableobjects.live/
  • Fico curioso se mais alguém acha difícil entender essas novas tecnologias de nuvem
    Tenho mais de 15 anos de experiência em desenvolvimento web e usei a stack Laravel / Postgres / Redis, mas, quando leio textos assim, simplesmente penso que isso não é para mim

    • Segundo o texto, como contexto útil para a primeira versão de Durable Objects, recomenda-se ler Cloudflare’s durable multiplayer moat, de Paul Butler, em que ele aborda por que isso é popular para criar aplicações colaborativas em tempo real baseadas em WebSocket
      Os apps colaborativos em tempo real que vêm imediatamente à mente são coisas como Google Docs/Sheets, Notion, Miro e Figma
      Todos são apps colaborativos em escala global, e não tenho certeza se a stack Laravel conseguiria dar suporte a esses casos de uso
      O Google provavelmente teve de implementar a maior parte por conta própria e deve ter sido pioneiro no uso de CRDTs
      Mas, à medida que os padrões ficam claros e os componentes viram SaaS, a colaboração em tempo real em grande escala deixa de ser um enorme problema de engenharia e possibilita produtos mais interessantes
  • Gosto muito do design dos Durable Objects. Especialmente por ser fácil entender como funcionam por dentro
    Ao contrário de muitas outras soluções projetadas para dados em tempo real, Durable Objects têm a simplicidade do Redis e da comida italiana. Dá para ver todos os ingredientes
    Com tempo, recursos e data centers suficientes, parece que um programador competente poderia ler a documentação dos DOs e reimplementar algo parecido
    Por isso, é fácil avaliar os trade-offs envolvidos
    No entanto, tenho a preocupação de que DOs sejam bons para criar experiências em tempo real rápidas e com baixo overhead, como cinco pessoas editando um documento em tempo real, mas possam tornar análises e visões gerais muito difíceis
    Perguntas como quais documentos determinado grupo editou na semana passada, e isso pode ficar ainda mais difícil se os dados forem colocados dentro do SQLite
    Afinal, seria preciso consultar de algum modo inúmeras pequenas instâncias SQLite e depois mesclar os resultados; fico curioso se os DOs têm algo para isso
    É por isso que acabo sempre voltando para o Postgres. Porque dá para usá-lo tanto para funcionalidades centrais do app quanto para visões gerais, BI etc.

  • É um design realmente interessante, mas esses sistemas inteligentes sempre ficam, para mim, no vale da estranheza
    Há exatamente dois casos em que você precisa disso: quando precisa escalar de forma esperta um sistema de carga muito alta, ou quando está fazendo um projeto de brinquedo por diversão
    Se for um projeto de brinquedo, qualquer coisa serve
    Mas se for produção ou uso profissional, você precisa de algo comprovado
    Se você não sabe que precisa disso, então não precisa; pode usar algo como um banco de dados Postgres sem graça e uma VM
    Por outro lado, se você sabe que precisa, a situação fica complicada. Ainda é novo e não está maduro o suficiente, então é bem provável que você encontre muitos edge cases estranhos, e não vai querer depurá-los nem simplesmente aceitá-los
    No fim, não sei para quem são esses sistemas
    São nichados demais para amadurecerem facilmente com muitos players sérios usando, e são complexos demais, com concessões demais, para 99,9% das empresas
    O único público-alvo claro parecem ser desenvolvedores que veem algo brilhante e montam uma empresa — ou, pior, montam a empresa de outra pessoa em cima disso — para logo se arrependerem e migrarem para algo mais sem graça

    • Pelo que vejo, o killer app de Durable Objects é multiplayer
      Se você quer criar outro Figma ou Google Docs, o modelo de programação de Durable Objects é muito conveniente
      Este texto aborda isso em mais detalhes: https://digest.browsertech.com/archive/browsertech-digest-cl...
      Este texto antigo também é bastante relacionado: http://ithare.com/scaling-stateful-objects/
      Para quem leu o texto sobre multiplayer do Figma e pensou “é mais ou menos disso que eu preciso”, Durable Objects parece se encaixar bem: https://www.figma.com/blog/rust-in-production-at-figma/
      Há outras abordagens também. Há algum tempo experimentei CRDT sobre WebRTC, e aquilo realmente parecia tecnologia do futuro
      Mas é muito mais complexo do que a base de um WebSocket em algum lugar da nuvem e uma única instância de classe
    • Bancos de dados, assim como linguagens de programação, são uma área em que a maturidade avança de forma extremamente lenta; nem tudo que se afasta do Postgres é tecnologia hipster brilhante
      Colocar dados e comportamento no mesmo lugar de fato reduz a complexidade de forma quantificável
      Isso elimina preocupações com latência e largura de banda, reduzindo tanto preocupações operacionais quanto de desenvolvimento. O impacto do famoso problema N+1 também diminui bastante
      Você pode argumentar que um Postgres conectado pela rede é melhor por outros motivos, e talvez esteja certo
      Mas o SQLite está entre as opções mais sem graça e previsíveis, e seus pontos fortes conhecidos são claros
      Por isso, ele também vem ganhando popularidade no servidor
      Porém, não gosto muito da abordagem de criar muitos bancos de dados pequenos, como Durable Objects propõe
      Isso lembra pesadelos de NoSQL e pode quebrar invariantes importantes de bancos de dados relacionais
      Vejo o SQLite como muito melhor quando usado como um banco de dados monolítico, como no produto D1 da Cloudflare
    • Se você se encaixa no caso 1, basta conversar com a Cloudflare
      A Cloudflare precisa de bons casos de sucesso de clientes, e é bem provável que ótimos engenheiros ajudem você a entender como isso funciona e ajuda, e como lidar com bugs, em troca de obter um caso de sucesso
      Depois de validado, isso viraria uma relação de serviço, mas no começo seria mais próximo de uma parceria
    • Vejo a distinção aqui pelo eixo de capacidade de depuração e observabilidade interna
      Há muitos serviços, como ferramentas internas, que não precisam de tuning de performance nem de observabilidade interna profunda
      Nesses casos, frameworks serverless se encaixam bem porque reduzem bastante o tempo gasto com deploy
      Ser rápido é bom, mas raramente é um requisito central
      Em geral, o requisito central é conseguir construir rápido e ter pouca manutenção
      A Cloudflare pode oferecer uma boa experiência de desenvolvedor aqui, mas esse não é o principal ponto de marketing dela, e há muitos serviços concorrentes que também prometem acelerar esse tipo de desenvolvimento
      Por outro lado, quando é necessária alta capacidade de depuração e observabilidade interna, não acho que esse tipo de serviço se encaixe bem
      Importa saber quais métricas é possível obter, se há informações para entender por que alguns Durable Objects estão lentos, se dá para corrigir, como é o logging e quanto custa
      Esse tipo de serviço pode ser bom para uma startup criar desde o primeiro dia um sistema distribuído inteligente e adiar preocupações de escala
      Mas, se for uma empresa em fase de scale-up, acho que ela vai querer migrar para algo que permita enxergar mais a fundo, e essa transição será difícil
    • Esta análise é muito perspicaz. Acho que a maioria das pessoas deveria fazer exatamente esse tipo de análise antes de projetar um sistema
      Como outros disseram, o caso de uso é multiplayer, porque para o app parecer bom todo mundo precisa ver as alterações o mais rápido possível
      De forma mais ampla, o setor de armazenamento há muito tempo tenta criar algo consistente, de baixa latência e adequado a múltiplos usuários
      Mesmo fisicamente, normalmente há um trade-off entre consistência e latência, o que torna isso muito difícil
      Por isso, vários modelos estão sendo testados, e boa parte desses experimentos está acontecendo em torno do SQLite. Não todos; há exceções como Yugabyte e Cockroach
  • Continuo impressionado com o design dos DOs
    É fácil ter uma reação reflexa de que há algo errado com essa abordagem, mas acho que, na prática, muitos produtos reais são estruturados implicitamente assim
    Para cada unidade atômica que precisa de consistência transacional, muitas operações complexas são executadas em uma escala muito pequena
    Olhando para trás, o que criamos na Framer para o projeto de suporte a multiplayer também era uma versão mais aplicada do que os DOs fazem hoje
    As edições eram replicadas a 60 FPS e aplicadas na ordem correta em todos os clientes, e acabamos criando também algo parecido com um WAL para edição de objetos JSON
    Assim, mesmo que uma instância de projeto caísse, o backup podia assumir como se nada tivesse acontecido
    Mesmo que não houvesse tempo para fazer commit de patches JSON no enorme objeto de dados do projeto, era processado a cada N atualizações ou a cada M segundos, como descrito aqui

  • Pode ser uma pergunta boba, mas fico curioso sobre como migrações de esquema são tratadas nesse tipo de configuração
    Pelo que entendi, parece que o objetivo é ter um banco de dados por tenant, ou uma estrutura ainda mais fragmentada do que isso
    Fico me perguntando se existe uma forma sensata de lidar com migrações de esquema, ou se a expectativa é que esses bancos de dados sejam mais temporários e que seja preciso dar suporte a várias versões de DB/DO até que sejam excluídos
    Na minha cabeça, seria interessante criar um serviço de bookmarks com um DO por usuário
    Mas, no momento em que você quisesse adicionar um novo campo a uma tabela existente, encontraria um problema bem complicado: aplicar essa mudança a cada DO individual
    Talvez esse exemplo envolva dados que duram tempo demais, e este design seja mais voltado a usos mais efêmeros
    Gostaria de saber como quem já fez isso na prática está lidando com a questão

    • As migrações provavelmente terão de ser implementadas por você mesmo
      Tenho uma versão para SQLite escrita em Python, mas não sei se ela rodaria em Durable Objects. Talvez seja possível via WASM e Pyodide
      Ou então teria de ser portada para JavaScript
      https://github.com/simonw/sqlite-migrate
  • Ultimamente parece que a Cloudflare está incentivando os desenvolvedores a usar DO em todos os lugares, mais do que Worker
    As conexões WebSocket em Workers também expiram depois de cerca de 30 segundos, e a abordagem recomendada é usar DO

    • Acho que, no Cloudflare Workers, Durable Objects sempre foram a abordagem recomendada para conexões WebSocket. Pelo menos é assim que eu me lembro
      A demo original de chat também já usava DO e WebSocket desde 2020: https://github.com/cloudflare/workers-chat-demo
  • Isso quer dizer que o SQLite para DO pode perder até 10 segundos de dados em situações em que o DO falha?

    • Há um trecho dizendo: “Para garantir durabilidade além da janela de 10 segundos, assim que uma escrita é confirmada, ela é enviada para 5 réplicas em data centers separados próximos, e 3 delas precisam confirmar para que a escrita seja aprovada”
      Aqui, parece que Simon queria dizer within, não “beyond”