Conexão · Interrompida

Algo não carregou

Parte desta página não chegou até você. Recarregue para tentar novamente — se persistir, verifique sua conexão.

Pular para o conteúdo principal
Engineering16 min de leitura

O Servidor Agora É um Relay de Sincronização: Arquitetando em Torno do Estado que Pertence ao Cliente

Na QCon London 2026, Kleppmann descreveu o local-first como o melhor do Google Sheets e o melhor do Git, e neste ano os sync engines chegaram ao mercado. A partir da minha leitura do Electric, do Zero e do LiveStore lado a lado, estas são minhas anotações sobre o que muda quando a cópia do cliente se torna primária: onde os conflitos são resolvidos, o que o servidor ainda controla e a linha entre mesclar e recusar que decide quais aplicações nunca devem ser construídas dessa forma.

Todos os Posts
2/4

Todo sistema replicado tem uma resposta honesta para uma pergunta simples: quando duas cópias de um registro discordam, qual cópia vence? Durante a maior parte da história da web, a resposta foi o servidor. O banco de dados por trás da API guarda a cópia primária; tudo em um celular ou laptop é um cache que pode ser descartado e buscado novamente. Uma edição que não chegou ao servidor, para todos os efeitos práticos, não aconteceu.

O software local-first inverte essa resposta, e a inversão é toda a arquitetura. Na QCon London 2026, Martin Kleppmann apresentou o local-first como "o melhor do Google Sheets e o melhor do Git": colaboração em tempo real sobre dados ricos, combinada com uma cópia primária completa do seu trabalho na sua própria máquina. A nuvem não desaparece nesse cenário — ela é rebaixada. Sincronização e backup permanecem; a autoridade vai embora.

O que me levou a mapear isso adequadamente é que 2026 é o ano em que a ideia deixou de ser uma agenda de pesquisa. O ElectricSQL transmite dados do Postgres para os clientes em escala de CDN. O Zero, da Rocicorp, entrega escritas especulativas no cliente com rebase no servidor. O LiveStore coloca um banco de dados baseado em event sourcing dentro do navegador e transforma o servidor em um relay para o log. Três engines em produção, todos comercializados com o mesmo rótulo "local-first" — e, como descobri quando li a documentação de sincronização deles lado a lado, três respostas diferentes sobre onde a verdade reside. Este post é minha tentativa de registrar o que realmente muda na arquitetura de um sistema quando o cliente é dono do estado, o que o servidor ainda controla e a classe de aplicações que não devem ser construídas dessa forma.

Para onde a verdade realmente se move

O ensaio de 2019 da Ink & Switch que cunhou o termo é explícito sobre o mecanismo. Em uma aplicação em nuvem, o servidor guarda a cópia primária e autoritativa, e toda renderização no cliente é subordinada a ela. Em uma aplicação local-first, a cópia no dispositivo do usuário se torna a primária. "Os servidores ainda existem, mas guardam cópias secundárias dos seus dados." Os dispositivos são as réplicas oficiais; o data center guarda um espelho substituível.

O Git é a referência que torna isso concreto. Um clone local não é um cache do GitHub — é uma cópia primária completa, subordinada a nenhum servidor. O GitHub é um ponto de encontro: conveniente para troca, backup e descoberta, e substituível por qualquer outro host sem perda. O local-first pede essa propriedade em aplicações interativas, mais a única coisa que o Git nunca ofereceu: merge granular em tempo real, que é onde entram os CRDTs e bibliotecas como o Automerge. Já escrevi antes sobre por que as réplicas CRDT convergem — a função de merge, não a rede, carrega a garantia — então não vou repetir a mecânica. Este post é sobre o sistema ao redor do merge.

A inversão é mais fácil de ver como um fluxo de dados do que como uma definição. No diagrama abaixo, o lado esquerdo é a arquitetura que construí durante a maior parte da minha carreira; o lado direito é como fica uma implantação local-first do mesmo produto.

Quatro consequências surgem ao mover a cópia primária, e cada uma reescreve parte da descrição de trabalho do backend.

Primeira, as leituras deixam de tocar a rede. Toda query roda contra um armazenamento local, então a latência de interação é limitada por disco e memória, não por um round trip. O p99 de uma leitura deixa de ser um SLO do backend — passa a ser uma propriedade do dispositivo.

Segunda, a aceitação de escrita deixa de ser uma decisão do servidor. Uma escrita é confirmada localmente e de forma durável antes que qualquer parte remota saiba dela. Não há momento em que um servidor possa rejeitar a escrita e fingir que ela nunca aconteceu; quando o relay a vê, o usuário já seguiu em frente.

Terceira, e decorrente diretamente da segunda: o tratamento de conflitos passa do momento da escrita para o momento da sincronização. Um sistema com autoridade no servidor resolve a contenção ordenando as escritas através de um único banco de dados e rejeitando os perdedores imediatamente. Um sistema local-first aceita tudo na borda e reconcilia depois — o que significa que o modelo de dados precisa ser projetado de modo que a reconciliação tenha uma resposta aceitável para cada interleaving possível.

Quarta, o provedor se torna substituível. Como o relay guarda cópias secundárias, trocar de host é um redirecionamento, não uma migração. Esse era o núcleo político da keynote de Kleppmann: com AWS, Azure e Google Cloud detendo cerca de setenta por cento do mercado europeu, e incidentes como o do procurador do TPI que perdeu o acesso ao seu e-mail hospedado na Microsoft sob sanções dos EUA, ele enquadrou o servidor rebaixado como mitigação de risco — o impacto de um bloqueio repentino de provedor cai de existencial para inconveniente.

Três engines, três respostas sobre quem é dono da verdade

A parte que mais me surpreendeu na leitura é o quão diferente os sync engines atuais posicionam a autoridade, considerando que os três são descritos com o mesmo rótulo.

O ElectricSQL não move a autoridade de jeito nenhum. O Electric é, pela própria documentação, um sync engine de caminho de leitura para o Postgres: consome o stream de replicação lógica, distribui linhas em "shapes" — réplicas parciais de uma tabela filtradas por uma cláusula WHERE — e as entrega aos clientes por HTTP puro para que CDNs possam lidar com o fan-out. Escritas explicitamente não são responsabilidade dele. Elas trafegam pela sua API existente até o Postgres, que permanece a fonte da verdade. O que o Electric oferece é comportamento de leitura local-first — queries instantâneas, atualizações ao vivo, leituras offline — sobreposto a um backend classicamente autoritativo. Se a pergunta do produto é "meu dashboard consegue parecer instantâneo", esta é a resposta menos invasiva.

O Zero, da Rocicorp, mantém a autoridade no servidor mas deixa o cliente tomá-la emprestada por um instante. Toda escrita é um mutator nomeado implementado duas vezes: uma versão no cliente que roda instantaneamente contra o armazenamento local, e uma versão no servidor que é autoritativa. O resultado do cliente é especulativo — quando o resultado do servidor chega, os efeitos locais pendentes são revertidos e reaplicados por cima, um rebase em tudo menos no nome, e o resultado especulativo é descartado. As duas implementações têm permissão para divergir, e esse é justamente o ponto: a cópia do servidor pode impor permissões e validações que nenhum cliente é confiável para executar. A documentação do Zero chama a técnica de server reconciliation e credita os jogos multiplayer, que a utilizam há décadas. Arquiteturalmente, isso é a UI otimista formalizada no protocolo de sincronização: o cliente renderiza uma previsão; o servidor ainda decide.

O LiveStore é a inversão completa. O estado vive em um armazenamento baseado em event sourcing no cliente: um eventlog append-only é a fonte da verdade, e as tabelas SQLite que uma aplicação consulta são views materializadas, reconstruídas a partir dos eventos por funções materializadoras. A sincronização é push/pull no sentido do Git — um cliente precisa dar pull nos eventos upstream mais recentes e rebase nos seus eventos não enviados antes de poder dar push. O papel do servidor colapsa para ordenar e retransmitir o log. A consequência que considero mais subestimada: o schema relacional deixa de ser o contrato durável, porque as tabelas podem ser reconstruídas a qualquer momento. O schema de eventos é o contrato agora, e ele herda todo o problema de compatibilidade eterna que os backends baseados em event sourcing já conhecem.

Alinhados dessa forma, "adotar local-first" acaba se revelando um requisito subespecificado. O Electric mantém a verdade no servidor e sincroniza leituras. O Zero mantém a verdade no servidor e simula a verdade no cliente pela duração de um round trip ao servidor. O LiveStore e a linhagem do Automerge tornam a verdade no cliente real. Não são três fornecedores competindo no mesmo design — são três posicionamentos diferentes da cópia autoritativa, e o posicionamento, não o fornecedor, é a decisão. Também não é uma decisão por aplicação: nada impede um sistema de sincronizar seus documentos ao estilo LiveStore enquanto sua tabela de billing permanece atrás de uma API request/response. Decidir por coleção é, na minha avaliação, a única versão disso que sobrevive ao contato com um produto real.

O que o servidor ainda controla após o rebaixamento

Rebaixamento não é eliminação. Os autores da Ink & Switch tiveram o cuidado de dizer que a diferença é uma mudança nas responsabilidades do servidor, não a sua ausência, e a lista deles de 2019 ainda soa como um checklist de implantação para os engines de 2026.

O primeiro trabalho é o relay store-and-forward. Dois colaboradores raramente estão online no mesmo instante; a sincronização peer-to-peer morre no laptop fechado. Um pequeno serviço sempre ativo que aceita mudanças criptografadas e as guarda até o outro lado reconectar é o que torna a colaboração assíncrona possível — é exatamente o backend de sincronização que o Electric, o Zero e o LiveStore exigem, cada um à sua maneira.

O segundo é o backup durável. Celulares têm armazenamento limitado e laptops morrem; uma cópia de arquivo do log ou dos documentos pertence a um data center, com a propriedade crucial de ser uma cópia secundária — restaurável a partir de qualquer cliente, substituível por qualquer host concorrente.

O terceiro é o trabalho de fronteira: identidade, descoberta e autorização na camada de sincronização. O relay autentica quem pode assinar qual partição de dados, que é também onde vive o modelo prático de permissões. O Electric delimita o acesso por definição de shape; um mutator de servidor do Zero pode rejeitar uma escrita de imediato. O que nenhum engine consegue fazer é impor revogação retroativamente — os bytes já sincronizados no disco de um peer são os bytes daquele peer. O ensaio de 2019 apontou o controle de acesso granular em uma estrutura de dados convergente como uma questão de pesquisa em aberto, e, pelo que consigo perceber lendo a documentação atual dos engines, ainda é: o padrão implantável hoje é grosseiro — particione seus dados de modo que o escopo de sincronização seja igual ao escopo de permissão, e trate qualquer coisa mais fina como não resolvida.

O quarto é ser a ponte: chamar APIs externas, enviar e-mail, rodar a computação em rajada que um cliente não consegue. Em uma implantação com Zero, isso cai naturalmente nos mutators de servidor; em um sistema com verdade no log, torna-se um consumidor que reage aos eventos sincronizados.

Há também um trabalho sobre o qual o ensaio de 2019 não se deteve e que eu orçaria explicitamente: o caminho de leitura entre usuários. Uma vez que os dados são particionados por usuário por razões de escopo de sincronização, o console de administração e a query de analytics agregada deixam de ser uma cláusula WHERE em um banco de dados e passam a ser um modelo de leitura separado do lado do servidor, alimentado a partir do relay. As cópias pertencentes ao cliente são a verdade, mas nenhum cliente detém todas elas; qualquer um que precise da visão global volta a construir uma projeção no servidor.

E o histórico cresce sem limite. Uma réplica que ficou offline por seis meses ainda precisa conseguir sincronizar, o que significa que o log não pode ser truncado com segurança — o ensaio de 2019 nomeou o histórico de mudanças ilimitado como um problema de desempenho, e um armazenamento de cliente baseado em event sourcing o herda diretamente. O armazenamento para o log, e o tempo para a sincronização inicial de um dispositivo novo, são ambas linhas de orçamento que um sistema com autoridade no servidor simplesmente não tem.

A linha divisória: escritas que você pode mesclar e escritas que você precisa recusar

Tudo acima é mecânica. A decisão sobre se uma aplicação pode ser local-first afinal se resume a uma propriedade de suas regras de negócio, e não encontrei forma mais limpa de enunciá-la do que esta: algumas regras definem um merge correto, e algumas regras definem uma recusa.

Uma regra convergente diz que qualquer interleaving de escritas concorrentes tem um resultado combinado aceitável. Duas pessoas editam um documento; ambas as edições pertencem ao resultado. Uma checklist recebe um item de cada celular; a lista mesclada guarda os dois. Os CRDTs industrializam exatamente isso: a equipe da Ink & Switch relatou que, em seus protótipos com Automerge, a semântica de merge padrão foi suficiente em todas as aplicações que construíram, sendo o único caso irresolúvel a atribuição concorrente à mesma propriedade do mesmo objeto — e mesmo aí a estrutura preserva ambos os valores e deixa a aplicação escolher. O merge dá conta, sem coordenador necessário.

Uma regra de recusa é diferente em natureza. Um nome de usuário por handle. Um passageiro no assento 14C. Um saldo que não pode ficar abaixo de zero. Estoque que não pode ser vendido duas vezes. A correção é definida por uma das duas escritas concorrentes perdendo — sendo rejeitada antes de se tornar durável. Uma função de merge não consegue expressar isso: ela é total, precisa produzir um resultado a partir de ambas as entradas, e não tem como dizer não. A recusa exige uma autoridade que observe as escritas em uma ordem e possa barrar uma delas enquanto o autor ainda está lá para ouvir. É isso que o caminho de escrita com autoridade no servidor é.

Essa não é uma lacuna que CRDTs melhores estão prestes a fechar; ela está sendo atacada pela direção oposta. O trabalho "Consistent Local-First Software", publicado na IEEE TSE e apresentado na FSE 2025, identifica a incapacidade de impor invariantes da aplicação entre réplicas como o bloqueador central para bancos de dados local-first, e seu remédio é a reintrodução direcionada de sincronização precisamente para as operações que carregam invariantes, deixando todo o resto livre de coordenação. O que é, quando você aperta os olhos, a arquitetura híbrida enunciada como um resultado de pesquisa. Kleppmann traçou a mesma fronteira em sua keynote pelo lado do praticante: o local-first serve para edição de arquivos e ferramentas de produtividade, e não serve para sistemas que gerenciam recursos físicos — seu exemplo foi contas bancárias — onde uma fonte centralizada e autoritativa permanece o design apropriado.

Então o artefato concreto que eu produziria antes de adotar qualquer sync engine é uma tabela com uma linha por coleção no modelo de dados e uma coluna: mesclar ou recusar. Documentos, rascunhos, preferências, presença, anotações — mesclar. Dinheiro, estoque, assentos, nomes de usuário, cotas — recusar. As linhas de mesclar podem migrar para a verdade no cliente e ganhar escritas offline, leituras instantâneas e independência de provedor. As linhas de recusar mantêm um caminho de escrita com autoridade no servidor — possivelmente envolto em otimismo ao estilo Zero para que a UI continue instantânea, mas com a palavra final do servidor. A maioria das aplicações que fazem demo como "local-first" entrega exatamente essa divisão; as demos só nunca mostram a página de billing.

As aplicações para as quais isso quebra

Enunciando a classe de falha diretamente, porque os defensores do padrão raramente o fazem:

  • Estado adversarial. Se um usuário se beneficia ao forjar estado — pagamentos, anúncios em marketplace, rankings, cotas de uso — o cliente não pode deter a cópia autoritativa, porque a cópia autoritativa é precisamente aquilo que o usuário não pode controlar. Os mutators de dois lados do Zero existem por essa razão.
  • Recursos físicos disputados. Assentos, estoque, fundos, horários de agendamento: as regras de recusa dominam o modelo de dados, e uma versão local-first é um gerador de reservas duplicadas com excelente latência.
  • Produtos centrados em agregação. Se o produto é a visão global — analytics, consoles de moderação, dashboards de frota — as partições pertencentes ao cliente obrigam você a reconstruir uma projeção do lado do servidor de qualquer forma, e o rebaixamento comprou pouco.
  • Superfícies de sessão curta e grande volume de dados. Uma página de checkout ou um portal de suporte visitado duas vezes por ano não deveria pagar a sincronização inicial de uma réplica; os sync engines justificam seu custo em dados aos quais um usuário retorna diariamente.
  • Exclusão orientada por compliance. Obrigações de apagamento convivem mal com logs imutáveis e peers que detêm réplicas que não podem ser revogadas; se a exclusão legal é um requisito rígido, a fronteira do que sincroniza precisa ser desenhada em torno disso desde o primeiro dia.

Onde a inversão se paga é na classe de software de espaço de trabalho: documentos, notas, telas de design, quadros de planejamento, qualquer coisa a que uma pessoa ou um pequeno grupo retorna através de dispositivos e condições de rede. Ali a coluna de mesclar cobre quase todo o modelo de dados, as leituras são críticas para a interação, as janelas offline são reais, e a saída do provedor é um recurso que pode ser vendido aos usuários.

O que eu decidiria antes de adotar um sync engine

  • Classifique cada coleção como mesclar ou recusar antes de avaliar qualquer fornecedor. A classificação, não o fornecedor, é a arquitetura.
  • Posicione a autoridade por coleção: verdade no servidor com leituras sincronizadas (estilo Electric), verdade no servidor com escritas especulativas no cliente (estilo Zero), ou verdade no cliente com um relay (estilo LiveStore/Automerge). Misturar posicionamentos em um produto é normal, não um sintoma de problema.
  • Mantenha toda invariante que carrega recusa atrás de um caminho de escrita do lado do servidor, mesmo dentro de uma aplicação que de resto é local-first.
  • Trate permissões como design de partição na fronteira de sincronização, e aceite que a revogação de dados já sincronizados está fora de questão.
  • Faça o orçamento para o log: crescimento ilimitado do histórico, tempo de sincronização inicial em dispositivos novos, e um modelo de leitura do lado do servidor para qualquer coisa entre usuários.

O resumo que guardo para mim: recorra ao estado pertencente ao cliente quando os dados forem um espaço de trabalho ao qual alguém retorna — colaborativo, multi-dispositivo, propenso a offline, amigável a merge — e a propriedade de saída-do-provedor tiver valor de produto. Evite-o sempre que uma escrita precise ser recusável: dinheiro, estoque, identidade, qualquer coisa adversarial ou disputada. E quando um produto precisa de ambos, isso não é um comprometimento do local-first — a autoridade por coleção é a arquitetura para a qual tanto a pesquisa quanto os engines em produção convergiram.

Leitura adicional

Continue lendo

Curtindo? Talvez goste disso aqui.

Nada parecido — quer tentar outro ângulo?

Isso foi útil?

Deixe uma avaliação ou uma nota rápida — me ajuda a melhorar.