Saltar al contenido

Diseño de un sistema de edición colaborativa: OT vs CRDT, presencia y sincronización

Cómo funcionan los editores colaborativos en tiempo real: OT frente a CRDT, servidores de sesión por documento, presencia, persistencia y edición offline, con diagrama.

· Fundador y desarrollador

Publicado el

Dos clientes llegan por WebSocket, vía un balanceador, al servidor de sesión A, dueño de doc-123 según el directorio Redis. A registra las ediciones en Postgres, guarda snapshots en almacenamiento de objetos y valida el acceso con un proveedor de identidad. B posee otros documentos.

Dos personas escriben en el mismo párrafo en el mismo instante. En una fracción de segundo, ambas pantallas muestran el mismo texto, y no ha desaparecido ninguna palabra de nadie. Lograrlo exige resolver un problema concreto, las ediciones concurrentes sobre un estado compartido, más una serie de problemas de infraestructura corrientes: enrutamiento, persistencia, presencia y reconexión.

Este artículo diseña un editor colaborativo en tiempo real: el problema de concurrencia con un ejemplo resuelto paso a paso, la transformación operacional (OT) y los tipos de datos replicados sin conflictos (CRDT) comparados, el enfoque en el que manda el servidor que usa Figma y la arquitectura que los rodea, con un diagrama.

El problema central, en cinco caracteres

Empieza con el documento ABC. Las posiciones se cuentan desde cero.

  • Alice inserta X en la posición 1, y en su pantalla queda AXBC.
  • En el mismo instante, Bob borra la posición 2, la C, y en la suya queda AB.

Ahora cada cliente recibe la operación del otro y la aplica tal cual:

  • Bob aplica «insertar X en 1» a AB y obtiene AXB.
  • Alice aplica «borrar la posición 2» a AXBC, con lo que borra la B y obtiene AXC.

Los documentos han divergido, y la copia de Alice ha perdido un carácter que nadie borró. Todo editor colaborativo existe para evitar esto. Hay dos familias de soluciones.

Transformación operacional

La OT mantiene las operaciones como posiciones y transforma cada operación entrante frente a las operaciones concurrentes que no ha visto. El «borrar la posición 2» de Bob se hizo antes de la inserción de Alice en la posición 1, que desplaza todo lo que viene después un lugar a la derecha. Transformado frente a la inserción, pasa a ser «borrar la posición 3», y el AXBC de Alice se convierte en AXB, igual que el de Bob.

Los sistemas OT en producción usan un servidor central para ordenar las operaciones, un enfoque que se remonta al sistema Jupiter de 1995 y que Google Docs describió cuando reconstruyó su editor en 2010:

  1. Cada cliente envía sus operaciones etiquetadas con la revisión del servidor en la que se basaron.
  2. El servidor transforma cada operación frente a todo lo que aceptó desde esa revisión, asigna el siguiente número de revisión y difunde el resultado.
  3. Cada cliente transforma las operaciones entrantes frente a las suyas aún no confirmadas.

Los puntos fuertes de la OT son las operaciones compactas y una larga trayectoria con texto. Su punto débil son las propias funciones de transformación: con texto enriquecido, tablas y estructuras anidadas, acertar con cada par de tipos de operación es notoriamente difícil, y el diseño depende de un servidor que defina el orden.

CRDT

Un CRDT es un tipo de datos diseñado para que las actualizaciones concurrentes puedan aplicarse en cualquier orden y aun así todas las réplicas converjan; Shapiro y sus colegas lo formalizaron en 2011. En el texto, cada carácter insertado recibe un identificador único y ordenado, como un ID de cliente más un contador, y una inserción dice «después del carácter con tal ID» en lugar de «en la posición 1». Los borrados marcan los caracteres como eliminados y dejan tombstones. Como las operaciones apuntan a IDs estables y no a posiciones que se desplazan, no hay nada que transformar.

En el ejemplo, la inserción de Alice dice «X va después de A» y el borrado de Bob dice «elimina C». Aplicadas en cualquier orden, ambas réplicas terminan con AXB.

Bibliotecas CRDT como Yjs y Automerge lo hacen práctico. Sus puntos fuertes: no hace falta un orden central, así que la edición offline y la sincronización peer-to-peer funcionan de forma natural, y la fusión es posible en cualquier momento. Sus costes: metadatos extra por carácter, tombstones que hay que compactar y casos límite en los que el resultado fusionado es coherente pero no es lo que pretendía ninguno de los dos usuarios. Un servidor sigue siendo útil para el control de acceso, la persistencia y el fan-out; simplemente no tiene que decidir el orden.

El término medio en el que manda el servidor

No todos los editores editan texto largo. Figma no eligió ni la OT clásica ni CRDT de verdad: su servidor define el orden de los eventos, los documentos son árboles de objetos con propiedades y, cuando dos personas cambian la misma propiedad del mismo objeto, gana el último valor que recibe el servidor. Sus servidores ejecutan un proceso independiente para cada documento multijugador, y todos los que editan ese documento se conectan a él. Para herramientas de diseño, hojas de cálculo y pizarras, donde los conflictos son por propiedad, esto es más sencillo que cualquiera de las alternativas.

Transformación operacional CRDT Ordenado por el servidor, gana el último en escribir por propiedad
Quién ordena las ediciones Servidor central Nadie; las fusiones conmutan Servidor central
Edición offline Limitada Natural Limitada
Sobrecarga de metadatos Baja Mayor, por elemento Baja
Parte difícil Las funciones de transformación La compactación y la intención Elegir la granularidad de los conflictos
Encaja mejor en Texto enriquecido con servidor Apps offline-first y peer-to-peer Objetos con propiedades: herramientas de diseño, pizarras

La arquitectura

Elijas el algoritmo que elijas, el sistema que lo rodea se parece:

  1. Los clientes se conectan por WebSocket a un servicio de colaboración y mantienen la conexión abierta mientras el documento está abierto.
  2. Un único servidor de sesión es dueño de cada documento. Todos los clientes de doc-123 deben llegar al mismo servidor para que pueda ordenar o fusionar las ediciones en memoria. Un directorio, por ejemplo en Redis, asigna documentos a servidores con un lease, y el balanceador de carga o una capa de enrutamiento envía allí las conexiones. El hashing consistente sobre el ID del documento también sirve, siempre que dos servidores nunca puedan creer a la vez que un documento es suyo.
  3. El servidor de sesión mantiene el documento vivo en memoria, aplica cada edición entrante y la difunde a los demás clientes.
  4. Cada edición se añade a un log de operaciones duradero antes de que el servidor la confirme. Un snapshot periódico del documento completo va al almacenamiento de objetos, así que cargar un documento significa leer el snapshot más reciente y las operaciones posteriores.
  5. Los permisos se comprueban al conectar y en cada operación; la edición de un lector se rechaza, no solo se oculta.

Los comentarios, el historial de versiones, la búsqueda y la exportación son servicios aparte que consumen el log de operaciones; no pertenecen a la ruta de tiempo real.

La presencia es otro tipo de dato

Los cursores, las selecciones y el «quién está aquí» cambian constantemente, solo importan ahora mismo y no valen nada un minuto después. Trata la presencia como efímera: difúndela a través del mismo servidor de sesión, limítala a unas pocas actualizaciones por segundo y usuario, descártala cuando un cliente se desconecte y nunca la escribas en el log de operaciones. Yjs, por ejemplo, guarda la presencia en un canal de awareness separado, junto al estado del documento.

Ediciones offline y reconexión

Las conexiones se caen todo el rato, en trenes y en ascensores. El cliente guarda localmente las operaciones no confirmadas y las reenvía al reconectarse:

  • Con OT, el cliente envía sus operaciones pendientes junto con la última revisión que vio; el servidor las transforma frente a todo lo ocurrido desde entonces.
  • Con CRDT, cliente y servidor intercambian resúmenes compactos de lo que ha visto cada uno, a menudo llamados vectores de estado (state vectors), y se envían solo las actualizaciones que faltan.

Los periodos offline largos son donde brillan los CRDT. Con OT, un cliente que estuvo un día desconectado puede reconectarse igualmente, pero el servidor tiene que transformar sus operaciones frente a un día entero de operaciones.

Escalado y fallos

  • Documentos calientes. Un documento con miles de lectores y unos pocos editores no debería obligar al servidor de sesión a enviar él mismo cada pulsación a miles de sockets; reparte a los lectores mediante una capa pub/sub y agrupa las actualizaciones.
  • Caída del servidor de sesión. Los clientes se reconectan, el lease del directorio caduca, un servidor nuevo carga el snapshot y el log de operaciones, y los clientes reenvían sus operaciones pendientes. Confirmar solo las ediciones registradas de forma duradera es lo que hace que no se pierda nada.
  • Split brain. Dos servidores dueños del mismo documento aceptarían órdenes contradictorios. Protege la propiedad con el lease (fencing) y rechaza las escrituras de un servidor cuyo lease haya caducado.
  • Deshacer debe deshacer tu último cambio, no el de tu colaborador, así que se convierte en una nueva operación inversa, transformada o fusionada como cualquier otra.

El diagrama de colaboración

Dos clientes llegan por WebSocket, vía un balanceador, al servidor de sesión A, dueño de doc-123 según el directorio Redis. A registra las ediciones en Postgres, guarda snapshots en almacenamiento de objetos y valida el acceso con un proveedor de identidad. B posee otros documentos.

11 componentes · 9 conexiones

Abrir en Archboard
Contenido del diagrama
  • Alice: editor
  • Bob: editor
  • WebSocket entry
  • Collaboration service
  • Directory: doc-123 → A (lease)
  • Session server A
  • Session server B
  • Op log
  • Snapshots
  • Identity provider
  • edits + presence
  • doc-123 sessions
  • who owns doc-123?
  • renew lease
  • append, then ack
  • snapshot
  • check access on connect
  • other documents
Un backend de colaboración en tiempo real. Todos los clientes de un documento se conectan por WebSocket al único servidor de sesión que lo posee; ese servidor ordena o fusiona las ediciones, las difunde junto con la presencia y persiste un log de operaciones y snapshots periódicos.

Cómo leerlo:

  • Dos clientes se conectan por WebSocket a través del balanceador de carga.
  • El directorio de Redis indica qué servidor de sesión es dueño de doc-123; ambas conexiones llegan al servidor A.
  • El servidor A ordena o fusiona las ediciones en memoria y difunde ediciones y presencia de vuelta a los dos clientes.
  • Durabilidad: cada edición va al log de operaciones en Postgres antes de confirmarse; los snapshots van al almacenamiento de objetos.
  • El proveedor de identidad comprueba el acceso cuando los clientes se conectan; el servidor B es dueño de otros documentos y muestra cómo escala horizontalmente el servicio.
  • El bloque Markdown compara OT y CRDT en tres líneas.

Dibujar sistemas de tiempo real con claridad

  • Muestra la propiedad. El hecho más importante aquí es que un servidor es dueño de un documento; dibuja el directorio y el enrutamiento.
  • Separa los datos duraderos de los efímeros. Dibuja el log de operaciones y los snapshots, y etiqueta la presencia como no persistida.
  • Dibuja al menos dos clientes. La concurrencia es todo el problema; un solo cliente la oculta.
  • Marca el punto de confirmación: una edición está a salvo en cuanto entra en el log de operaciones.

Para conexiones de larga duración y fan-out, el diagrama de arquitectura de una app de chat cubre un terreno parecido. Enrutar documentos a servidores aplica las ideas del diagrama de balanceador de carga, y el método general está en el ejemplo de diagrama de diseño de sistemas.

Constrúyelo en Archboard

  1. Inicia sesión con Google o GitHub y crea un diagrama.
  2. Añade dos nodos Dispositivo del pack Red para los clientes y un Balanceador de carga en la nube del pack Nube como entrada WebSocket.
  3. Añade un Marco del pack Básico para el servicio de colaboración, con dos nodos Instancia de cómputo dentro: el servidor de sesión A, dueño de doc-123, y el servidor de sesión B.
  4. Añade un nodo Redis del pack Datos para el directorio de documentos, un nodo Postgres para el log de operaciones y un nodo Almacenamiento de objetos del pack Nube para los snapshots.
  5. Añade un Proveedor de identidad del pack Seguridad y conecta todo con aristas etiquetadas: WebSocket, ediciones y presencia, append, snapshot, lease.
  6. Añade un bloque Markdown del pack Básico con el resumen de OT frente a CRDT.

El ejemplo usa 11 nodos de diagrama, dentro del plan Free.

Preguntas frecuentes

¿Qué diferencia hay entre OT y CRDT? La OT transforma las operaciones concurrentes unas frente a otras, normalmente con un servidor que define el orden. Los CRDT dan a cada elemento una identidad estable, de modo que las operaciones conmutan y las réplicas convergen en cualquier orden, sin un orden central.

¿Google Docs usa OT o CRDT? Google describió la reescritura de su editor en 2010 como basada en transformación operacional, con un servidor que ordena los cambios. Muchos editores más recientes usan bibliotecas CRDT como Yjs o Automerge.

¿Por qué todos los editores de un documento deben conectarse al mismo servidor? Ese servidor mantiene el documento vivo y decide el orden de las ediciones. Si dos servidores aceptaran ediciones de un mismo documento de forma independiente, tendrían que coordinarse en cada pulsación.

¿Cómo se almacena la presencia? En general, no se almacena. Los cursores y las selecciones se difunden en tiempo real, con un límite de frecuencia, y se descartan cuando un cliente se desconecta. Persistirlos añade carga y ningún valor.

¿Puede funcionar offline un editor colaborativo? Sí. Los clientes encolan las operaciones localmente y sincronizan al reconectarse. Los CRDT llevan mejor los periodos offline largos; la OT también funciona, pero transforma más operaciones al volver.

Empieza gratis — un puesto, sin tarjeta de crédito.

Artículos relacionados

← Volver al blog

Abre un lienzo en un minuto

Inicia sesión con Google o GitHub. Gratis para un asiento. Mejora el plan cuando se una el equipo.