Knowledge

CRDTs em produção: construindo sistemas de colaboração em tempo real sem conflitos

Por que Google Docs, Figma e Notion migraram de Transformação Operacional para CRDTs na colaboração em tempo real.

09/03/20267 min de leituraKnowledge
CRDTs em produção: construindo sistemas de colaboração em tempo real sem conflitos

Resumo executivo

Por que Google Docs, Figma e Notion migraram de Transformação Operacional para CRDTs na colaboração em tempo real.

Ultima atualizacao: 09/03/2026

O problema de colaboração que quebrou a Transformação Operacional

Por anos, a Transformação Operacional (OT - Operational Transformation) foi o algoritmo padrão que impulsionava a edição colaborativa em tempo real em aplicações como Google Docs e Microsoft Office 365. A ideia central era elegante: quando dois usuários editam o mesmo texto simultaneamente, transforma suas operações para alcançar convergência.

Na teoria, OT funciona perfeitamente. Em produção, ela entra em colapso sob casos extremos que se tornam inevitáveis em escala.

A falha fundamental é que OT requer coordenação centralizada. Cada operação deve passar por um servidor central que a transforma contra todas as operações concorrentes. Isso cria um ponto único de falha, introduz latência proporcional à distância do servidor e torna a operação offline quase impossível. Quando partições de rede ocorrem, sistemas OT divergem — exigindo resolução de conflitos de merge complexa e propensa a erros.

A solução da indústria são os Conflict-Free Replicated Data Types (CRDTs).

O que torna os CRDTs diferentes

CRDTs são estruturas de dados matemáticas projetadas para garantir consistência eventual sem exigir coordenação. Quando duas réplicas de um CRDT divergem e depois se reconectam, elas convergirão automaticamente para o mesmo estado — nenhuma resolução de conflito necessária.

Isso é alcançado através de duas propriedades chave:

  1. Comutatividade: A ordem das operações não importa para o estado final.
  2. Idempotência: Aplicar a mesma operação duas vezes não tem efeito adicional.

Se Alice insere "Olá" e Bob insere "Mundo" simultaneamente, um CRDT garante que ambos terminem com "Olá Mundo" independentemente de qual mensagem chegou primeiro ou se eles se comunicaram diretamente.

Tipos de CRDTs: baseado em operação vs baseado em estado

CRDTs baseados em estado (CvRDTs)

CRDTs baseados em estado (também chamados CvRDTs, ou Convergent Replicated Data Types) funcionam trocando snapshots completos de estado. Cada réplica compara seu estado com o estado recebido e faz merge usando uma função de merge determinística:

typescriptinterface CvRDT<T> {
  state: T;
  merge(otherState: T): void;  // merge deve ser comutativo, associativo, idempotente
}

function merge(replicaA: CvRDT, replicaB: CvRDT) {
  const mergedState = mergeFunction(replicaA.state, replicaB.state);
  replicaA.state = mergedState;
  replicaB.state = mergedState;
}

A função de merge é o coração do CvRDT. Ela deve ser:

  • Comutativa: merge(A, B) === merge(B, A)
  • Associativa: merge(merge(A, B), C) === merge(A, merge(B, C))
  • Idempotente: merge(A, A) === A

Implementações comuns de CvRDTs incluem G-Counters (contadores que só crescem) e G-Sets (conjuntos que só crescem).

CRDTs baseados em operação (CmRDTs)

CRDTs baseados em operação (CmRDTs, ou Convergent Replicated Data Types) trocam operações em vez de estado completo. Cada operação é broadcast para todas as réplicas e aplicada exatamente uma vez:

typescriptinterface Op {
  id: string;        // identificador único da operação
  timestamp: number; // relógio Lamport ou relógio lógico híbrido
  payload: unknown;  // dados específicos da operação
}

interface CmRDT {
  apply(op: Op): void;
}

function broadcast(replica: CmRDT, op: Op) {
  // Envia operação para todas as réplicas
  allReplicas.forEach(r => r.apply(op));
}

CmRDTs são mais eficientes em banda, mas exigem semânticas de entrega exatamente-uma vez. Operações duplicadas devem ser detectadas e ignoradas usando IDs de operação.

CRDTs práticos para edição colaborativa

RGA: Replicated Growable Array

O Replicated Growable Array (RGA) é o CRDT de trabalho para edição de texto colaborativa. Ele representa texto como uma lista encadeada onde cada nó de caractere contém:

typescriptinterface RGANode {
  id: { replicaId: string; counter: number };  // único entre réplicas
  value: string;                                // o caractere
  leftId: string | null;                         // ponteiro para nó anterior
  deleted: boolean;                             // flag de tombstone
}

Quando Alice insere "A" na posição 5, seu nó é linkado após o nó atualmente na posição 5. Se Bob insere simultaneamente "B" na posição 5, ambos os nós linkam após a posição 5. A ordem final é determinada por um desempate determinístico — tipicamente o ID da réplica e o contador.

Isso garante que ambas as réplicas convergem para a mesma sequência sem exigir coordenação.

LWW-Element-Set: Conjunto de Elementos Last-Writer-Wins

Para aplicações onde ordenação exata é menos crítica que convergência, LWW-Element-Set oferece uma alternativa mais simples. Cada elemento é pareado com um timestamp, e semânticas de merge resolvem conflitos mantendo o elemento com o timestamp maior:

typescriptinterface LWWElement<T> {
  value: T;
  addedAt: number;
  removedAt: number;
}

function merge(a: LWWElement[], b: LWWElement[]): LWWElement[] {
  // Elemento está presente se addedAt > removedAt
  // Conflitos resolvidos por max(addedAt)
  // Elementos duplicados resolvidos por max(timestamp)
}

LWW-Element-Set é ideal para indicadores de presença colaborativa, cursores e outros metadados onde comportamento ocasional de "último-escritor-ganha" é aceitável.

Frameworks CRDT em produção

Yjs: O padrão de fato para colaboração baseada em web

Yjs emergiu como o framework CRDT dominante para aplicações web devido à sua:

  • Design TypeScript-first com suporte forte de tipagem
  • Ecossistema extensivo de bindings (React, Vue, Svelte, Solid)
  • Codificação binária para transmissão de rede eficiente
  • Suporte a desfazer/refazer embutido no modelo de dados
typescriptimport * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

const doc = new Y.Doc();
const ytext = doc.getText('codemirror');

// Conecta ao servidor de colaboração
const wsProvider = new WebsocketProvider(
  'wss://your-collab-server.com',
  'document-room-123',
  doc
);

// Mudanças locais sincronizam automaticamente
ytext.insert(0, 'Olá Mundo!');

// Observa mudanças remotas
ytext.observe((event, transaction) => {
  event.changes.delta.forEach(op => {
    if (op.insert) console.log('Inserido:', op.insert);
    if (op.delete) console.log('Deletado', op.delete, 'caracteres');
  });
});

Automerge: CRDTs compatíveis com JSON

Automerge foca em fazer CRDTs parecerem objetos JavaScript comuns:

typescriptimport { Automerge } from '@automerge/automerge';

let doc = Automerge.init();
doc = Automerge.change(doc, doc => {
  doc.text = new Automerge.Text();
  doc.text.insertAt(0, ...'Olá');
});

// Cria um fork
let doc2 = Automerge.clone(doc);
doc2 = Automerge.change(doc2, doc => {
  doc.text.insertAt(5, ...' Mundo');
});

// Faz merge dos forks sem conflitos
let merged = Automerge.merge(doc, doc2);
// merged.text === "Olá Mundo"

Automerge é particularmente adequado para aplicações que precisam persistir estado CRDT em bancos de dados tradicionais, pois fornece serialização binária compacta.

Considerações de infraestrutura para deployments CRDT

WebSocket vs WebRTC transport

Sistemas CRDT exigem comunicação bidirecional contínua. A escolha entre WebSocket e WebRTC depende da sua arquitetura de deployment:

WebSocket (via Y-websocket ou similar):

  • Requer um servidor de sinalização centralizado
  • Melhor para aplicações web com clientes browser
  • Mais fácil de implementar autenticação e autorização
  • Servidor pode mediar e registrar todas as mudanças

WebRTC (via Y-webrtc ou similar):

  • Topologia mesh peer-to-peer
  • Melhor para aplicações desktop/mobile com clientes nativos
  • Menor latência pois os dados fluem diretamente entre peers
  • Deployment serverless possível

Estratégias de persistência

Estado CRDT deve ser persistido para:

  1. Reconexão: Quando clientes se reconectam, precisam receber operações perdidas
  2. Novos participantes: Participantes tardios precisam do estado completo do documento
  3. Backup e recuperação: Restauração de documentos após perda de dados

Padrões de persistência recomendados:

typescript// Estratégia de snapshot para documentos de longa duração
async function saveSnapshot(doc: Y.Doc, docId: string) {
  const state = Y.encodeStateAsUpdateV2(doc);
  const compressed = await compress(state);
  await db.save(`snapshot:${docId}`, compressed);
}

// Log de operações para recuperação point-in-time
async function appendToLog(op: Uint8Array, docId: string) {
  await db.lpush(`ops:${docId}`, op);
  await db.expire(`ops:${docId}`, 60 * 60 * 24 * 7); // 7 dias
}

Abordagens híbridas funcionam bem: mantenha logs de operações frequentes para mudanças recentes e snapshots completos periódicos para armazenamento de longo prazo.

Quando CRDTs são excessivos

CRDTs são poderosos mas vêm com trade-offs:

  1. Overhead de memória: Manter histórico completo de edição com tombstones consome memória significativa. Um documento de 100KB pode exigir 500KB+ de estado CRDT.
  1. Complexidade: Entender e depurar comportamento CRDT exige conhecimento especializado. Problemas em produção são mais difíceis de diagnosticar.
  1. Banda: Sincronização inicial requer enviar o estado completo CRDT, que pode ser grande para documentos de longa duração.
  1. Overhead para casos simples: Para aplicações com um único editor ativo ou workloads pesados em leitura, uma estratégia mais simples de locking otimista pode ser suficiente.

CRDTs são justificados quando:

  • Você precisa de colaboração multiusuário em tempo real com 3+ editores simultâneos
  • Comportamento offline-first é um requisito
  • Convergência sem conflitos é mais importante que ordem exata de edição
  • Latência é crítica e round-trips de servidor são inaceitáveis

Conclusão

CRDTs transformaram colaboração em tempo real de um problema frágil e pesado em coordenação para uma arquitetura matematicamente sólida e tolerante a falhas. Aproveitando frameworks como Yjs e Automerge, equipes de engenharia podem construir aplicações colaborativas que escalam sem a complexidade operacional que atormentava sistemas de Transformação Operacional.

O investimento em arquitetura CRDT paga dividendos em experiência do usuário: seus usuários obtêm colaboração estilo Google Docs sem diálogos de "mudanças conflitantes", suporte offline que realmente funciona, e responsividade instantânea independentemente da localização do servidor.


Construindo um produto colaborativo em tempo real? Fale com especialistas técnicos da Imperialis para projetar e implementar uma arquitetura baseada em CRDT que escale com seus usuários.

Fontes

Leituras relacionadas