Last-Write-Wins Falha em Apps Colaborativos: Como o Zero Resolve Conflitos em Tempo Real
Colaboração em tempo real parece, à primeira vista, uma camada de interface: dois usuários editam, e o sistema “dá conta”. Mas, na prática, o problema costuma começar muito antes da UI. Quando múltiplas pessoas mexem no mesmo estado ao mesmo tempo, a pergunta central deixa de ser “como mostrar isso?” e passa a ser “como evitar que um usuário apague o trabalho do outro sem perceber?”. Foi exatamente esse tipo de dor que levou a Suga a abandonar o modelo inicial de last-write-wins no canvas e adotar o Zero, um sync engine da Rocicorp, para sincronização em tempo real e colaboração multiplayer.
A mudança é mais importante do que parece. No modelo anterior, alterações concorrentes podiam se sobrepor e destruir informação útil. Em produtos de construção visual, isso é particularmente perigoso: mover um nó, editar uma propriedade ou disparar uma operação de snapshot são ações com significados muito diferentes. Tratar tudo como “o último valor vence” simplifica a implementação, mas empobrece o comportamento do sistema. A Suga resolveu isso com uma arquitetura orientada por semântica do dado, usando mutators granulares para separar tipos de mudança e controlar conflito de forma mais precisa.
O ponto de partida foi entender que nem toda alteração merece o mesmo tratamento. Em vez de tentar forçar um único mecanismo para tudo, a equipe passou a classificar mudanças em três grandes grupos. Primeiro, as alterações pontuais e facilmente sobreponíveis, como a posição de nós no canvas. Depois, mudanças na infraestrutura, que exigem um comportamento de read-modify-write para preservar ajustes concorrentes feitos em partes distintas do estado. Por fim, operações de snapshot, como undo, deploy e discard, que fazem sentido como substituição total do estado, sem tentativa de merge fino.
Esse desenho é valioso porque reflete uma verdade importante da colaboração multiplayer: conflito não é um problema único. Há conflitos que podem ser fundidos, conflitos que devem ser serializados logicamente e conflitos em que a versão mais recente deve simplesmente vencer. O erro de muitos sistemas é tentar aplicar a mesma regra a tudo. A Suga, ao contrário, escolheu adaptar o mecanismo ao tipo de dado e à intenção da ação.
Na base de dados, o sistema continua apoiado em PostgreSQL. O estado colaborativo é armazenado em JSONB, enquanto o schema é derivado do Drizzle. Isso simplifica a estrutura e facilita a evolução do modelo, mas também traz um efeito colateral relevante: o JSONB vira a unidade de replicação, e o Zero não faz diffs internos finos automaticamente nesse nível. Na prática, isso significa que o merge precisa ser decidido manualmente dentro do mutator, no momento em que a mudança chega ao servidor.
Essa é uma diferença importante em relação a abordagens baseadas em CRDT genérico. Em vez de esperar que a estrutura resolva todos os conflitos sozinha, o sistema assume que parte da inteligência precisa estar no domínio da aplicação. O lado positivo é o controle: o time pode decidir exatamente o que deve ser mergeado, o que deve ser sobrescrito e o que deve ser tratado como snapshot. O lado negativo é que esse poder exige disciplina e atenção, porque edge cases inevitavelmente aparecem quando o estado começa a crescer em complexidade.
Na prática, a implementação funcionou bem localmente. Isso é um ótimo sinal técnico, mas também um lembrete de que o caminho entre “funciona na minha máquina” e “funciona em produção” costuma esconder as maiores armadilhas. No caso da Suga, os problemas surgiram principalmente em preview e staging, onde a infraestrutura de deployment protegida e a autenticação baseada em cookies introduziram atritos que não apareciam no ambiente local.
Esse detalhe operacional merece atenção porque é muito comum subestimar o impacto do ambiente na sincronização em tempo real. Domínios temporários e URLs efêmeras tornam cookies frágeis, especialmente quando há proteção de deployment no caminho. A solução encontrada foi mais robusta: em vez de depender exclusivamente de cookies em todos os contextos, a equipe passou a passar o token de autenticação via prop nos ambientes local e staging, reservando o modelo baseado em cookies para produção. É uma mudança pequena na implementação, mas enorme na estabilidade operacional.
Outro ponto importante é o risco de schema drift. Como o schema é derivado do Drizzle, esquecer de regenerar após uma migração pode fazer o cliente esperar colunas que ainda não existem, sem um erro explícito e imediato. Em sistemas distribuídos e com sincronização, esse tipo de falha é especialmente traiçoeira porque pode parecer intermitente, afetar apenas algumas rotas e ser confundida com problema de rede ou cache. A lição aqui é simples: em infra de sync, consistência de schema não é detalhe de build, é parte do contrato de colaboração.
Também vale notar a escolha de não transformar todo o sistema em um modelo de locking tradicional. Locks resolvem concorrência, mas podem matar a sensação de fluidez que aplicações colaborativas exigem. Já CRDTs genéricos podem ser elegantes em teoria, mas nem sempre se adaptam bem a dados relacionais, estruturas híbridas e fluxos que misturam JSON com persistência em banco relacional. A opção por um sync engine com controle explícito de mutators mostra um meio-termo pragmático: menos abstração universal, mais adequação ao problema real.
Esse tipo de decisão está ganhando espaço no mercado por um motivo claro: times querem colaboração em tempo real sem reconstruir todo o stack em torno de um novo paradigma. Ferramentas como Zero, ElectricSQL e PowerSync representam uma nova camada de infraestrutura, pensada para produtos que precisam de sync local-first, consistência razoável e integração com PostgreSQL. A diferença competitiva, em muitos casos, não está apenas em “ter sync”, mas em como o sync trata conflito, estado relacional e ambiente de execução.
O caso da Suga é especialmente útil porque mostra maturidade de produto e de engenharia ao mesmo tempo. De um lado, revela a evolução de uma experiência que precisava ser realmente multiplayer, e não apenas “quase simultânea”. De outro, expõe os custos concretos dessa evolução: classificação semântica de mutações, tratamento manual de merge em JSONB, regeneração cuidadosa de schema e adaptações de auth para ambientes efêmeros.
Há também uma lição estratégica. Quando um produto cresce e passa a depender de edição simultânea, o risco não é apenas técnico — é de experiência. Perder alterações sem aviso destrói confiança rapidamente. Um sistema de sincronização bem desenhado não serve só para evitar bugs; ele sustenta a sensação de que várias pessoas podem construir juntas sem pisar no trabalho umas das outras. Em ferramentas visuais, esse sentimento é parte do próprio valor do produto.
No fim, a transição da Suga para o Zero mostra que colaboração em tempo real não se resolve com um “recurso de multiplayer” genérico. Ela exige um desenho cuidadoso do que é concorrente, do que é fundível e do que deve ser substituído por completo. Exige também pensar além do banco e da UI, olhando para autenticação, ambientes de preview, publicação de schema e comportamento operacional. É um caso prático e valioso porque deixa claro que, em infraestrutura de desenvolvimento, a diferença entre uma boa experiência colaborativa e uma experiência frustrante está nos detalhes invisíveis.