wandres.dev
EG-WALKER · guardar operaciones, no metadatos

Su lugar: entre transformación operacional y CRDT

Eg-walker toma de la transformación operacional qué persistir y de los CRDT cómo fusionar, se cree isomorfo a FugueMax en sus resultados y ya sostiene implementaciones reales como Diamond Types y Loro.

⏱ 19 min

El track lleva varios niveles presentando la transformación operacional y los CRDT como dos escuelas enfrentadas, con una historia de rivalidad académica que hizo que durante dos décadas se leyeran como alternativas excluyentes. Eg-walker desmonta ese encuadre de la forma más incómoda posible para ambas: tomando de cada una la mitad que funcionaba. De la transformación operacional toma la representación —operaciones originales con índices, guardadas tal cual y nunca reescritas— y de los CRDT toma el procedimiento de integración, que coloca las ediciones concurrentes por identidad y no por transformación sucesiva. El resultado no es un compromiso tibio sino una tercera posición con propiedades propias, cuya salida se cree idéntica a la de un CRDT conocido y cuya implementación ya está en producción. Esta lección sitúa el algoritmo en el mapa, explica su parentesco con Fugue y repasa quién lo usa hoy.

🎯 Al terminar esta lección sabrás
  • Situar a Eg-walker respecto a las dos escuelas separando la representación del procedimiento de fusión.
  • Entender qué es el entrelazado y por qué Fugue es la referencia de calidad de fusión en secuencias.
  • Interpretar correctamente la afirmación de isomorfismo con FugueMax y qué implica en la práctica.
  • Conocer el estado real de las implementaciones y qué usar según lo que se quiera hacer.

Por qué se le llama intermedio

La descripción de Eg-walker como punto intermedio no es una etiqueta cómoda: se puede justificar eje por eje, y hacerlo aclara qué heredó exactamente de cada tradición.

En el eje de la representación es puro linaje de transformación operacional. Lo que se persiste son operaciones con posiciones numéricas, medidas contra el estado que su autor tenía delante, sin ninguna identidad por carácter. Cualquiera que haya leído los trabajos clásicos de esta escuela reconoce el formato al instante, porque es literalmente el suyo.

En el eje de la integración es puro linaje de CRDT. Colocar una operación concurrente no se hace transformándola contra cada operación intermedia, que es el mecanismo que hace intratables las ramas largas, sino traduciéndola a identidades estables y aplicando una regla de desempate determinista. Es el procedimiento de una secuencia convergente, con la única particularidad de que las identidades son temporales.

Y en el eje de la persistencia no es ninguna de las dos, sino algo que ninguna de ellas contemplaba: el estado del documento se declara derivable y se recalcula bajo demanda. La transformación operacional también deriva el estado, pero lo hace transformando y por tanto reescribiendo lo que guarda; los CRDT persisten el estado ya integrado y no derivan nada. La tercera posición —persistir sin reescribir y derivar sin transformar— es la que no estaba ocupada.

Tres ejes, tres respuestas por escuela

  que se persiste
    OT ......... operaciones originales con indices
    CRDT ....... estructura con identidades y lapidas
    Eg-walker .. operaciones originales mas contexto causal

  como se integra lo concurrente
    OT ......... transformando contra las operaciones intermedias
    CRDT ....... por identidad y desempate determinista
    Eg-walker .. por identidad y desempate determinista

  donde vive el estado
    OT ......... derivado, pero la representacion se reescribe
    CRDT ....... materializado y permanente
    Eg-walker .. derivado, con andamio temporal que se descarta
flowchart TB
OT[transformacion operacional] -->|representacion: operaciones originales| EG[Eg walker]
CRDT[crdt de secuencia] -->|integracion: identidades y desempate| EG
EG --> P1[carga rapida y memoria baja en reposo]
EG --> P2[fusion de ramas largas sin coste cuadratico]
EG --> P3[sin servidor central y valido en pares]
style EG fill:#cba6f7,color:#11111b
style P1 fill:#a6e3a1,color:#11111b
style P2 fill:#a6e3a1,color:#11111b
style P3 fill:#a6e3a1,color:#11111b

Hay una tercera herencia que suele pasarse por alto y que es la que decide su encaje en local-first: Eg-walker se puede usar en todos los sitios donde se usa un CRDT, incluidos los sistemas entre pares sin servidor central. Esa propiedad no era obvia de antemano, porque la representación heredada de la transformación operacional viene asociada históricamente a arquitecturas con un servidor que ordena. Aquí la referencia causal explícita sustituye a ese servidor, y por eso la herencia de formato no arrastra la herencia de topología.

📝
La rivalidad de las dos escuelas era en parte un artefacto de época

La transformación operacional nació en un mundo de sesiones cortas y servidor presente, y su representación compacta era una virtud en ese contexto. Los CRDT nacieron para la replicación sin coordinación, y su coste en metadatos era el precio razonable de esa libertad. Cada escuela optimizó para su mundo y luego defendió su elección como si fuera universal. Al reformular el problema en términos de qué persistir frente a cómo fusionar, resulta que las dos decisiones eran independientes y nadie había probado la combinación cruzada.

El parentesco con Fugue y el problema del entrelazado

Para valorar la calidad de la fusión hace falta un criterio que no sea la convergencia, porque converger es fácil y no dice nada sobre si el resultado es aceptable para una persona. El criterio que la comunidad ha consolidado es el entrelazado, y conviene enunciarlo con un ejemplo. Dos autores escriben a la vez en el mismo punto de un documento vacío: uno teclea la palabra hola y el otro teclea la palabra adios. Todos los algoritmos convergen; la pregunta es a qué. Un resultado aceptable es hola seguido de adios, o adios seguido de hola. Un resultado inaceptable es haodliaos, con las letras de ambos intercaladas.

El entrelazado no es una curiosidad teórica: es un fallo visible que destruye texto y que aparece en la práctica cuando dos personas escriben en el mismo párrafo. Varias familias clásicas de secuencia lo sufren en algún escenario, y durante años se trató como una rareza aceptable. El trabajo que cambió esa conversación fue Fugue, de Matthew Weidner, Joseph Gentle y Martin Kleppmann, que caracterizó el fenómeno con precisión y construyó una familia de algoritmos que lo minimiza de forma demostrable. FugueMax es la variante que alcanza la garantía más fuerte de no entrelazado.

Dos autores insertan a la vez en el mismo hueco

  entrelazado ....... h a o d l i a o s   destruye ambas aportaciones
  no entrelazado .... h o l a a d i o s   una despues de otra, legible
  no entrelazado .... a d i o s h o l a   el orden lo decide el desempate

La afirmación relevante para este nivel es que se cree que Eg-walker es isomorfo a FugueMax, es decir, que ante las mismas operaciones produce el mismo resultado. Conviene leer las dos partes de la frase con cuidado. La primera parte es una buena noticia sustancial: significa que el algoritmo no compra su rendimiento a costa de la calidad de la fusión, sino que hereda la mejor garantía disponible frente al entrelazado. La segunda parte es el matiz honesto: se cree, y esa formulación prudente refleja que el enunciado se apoya en el análisis del comportamiento de ambos y no en una demostración formal cerrada de equivalencia.

Que la relación se pueda plantear siquiera dice algo importante sobre la naturaleza del trabajo. Si dos algoritmos con representaciones internas radicalmente distintas —uno que guarda un árbol de identidades permanente y otro que no guarda ninguna— producen la misma salida ante las mismas entradas, entonces lo que define un algoritmo de secuencia no es su estructura de datos sino su función de fusión, es decir, la regla que decide dónde va cada inserción concurrente. La estructura es implementación; la regla es semántica. Esa separación es la que permite que el nivel entero exista, porque autoriza a cambiar por completo lo primero sin tocar lo segundo.

Tiene además una consecuencia práctica inmediata y muy útil al implementar: se pueden usar los casos de prueba de una familia para validar la otra. Si construyes tu propia versión del algoritmo, la forma más rápida de ganar confianza es generar historias aleatorias con concurrencia, ejecutarlas contra una implementación de FugueMax y contra la tuya, y comprobar que el texto resultante coincide carácter a carácter. Es una prueba diferencial barata que detecta exactamente los errores que las pruebas de convergencia no ven, porque converger a un texto entrelazado también es converger.

💡
Separar convergencia de calidad es el hábito que hay que adquirir

Cuando evalúes una biblioteca de texto colaborativo, la pregunta de si converge tiene siempre la misma respuesta y no discrimina nada. Las preguntas útiles son otras: qué hace ante dos inserciones simultáneas en el mismo punto, qué hace cuando alguien pega un párrafo donde otro está borrando, y si el resultado que produce es texto que una persona reconocería como suyo. Reproduce esos tres escenarios a mano antes de elegir, porque ninguna documentación los describe y todos ocurren la primera semana de uso real.

Quién lo implementa y qué usar para qué

El estado de las implementaciones es más maduro de lo que sugiere la fecha de publicación, porque el trabajo de ingeniería precedió al artículo en lugar de seguirlo. Es un orden poco habitual y explica varias cosas: el algoritmo se depuró contra trazas reales antes de tener nombre, y el artículo describe algo que ya funcionaba en lugar de proponer algo que habría que construir.

📖

Implementación de referencia

Escrita en TypeScript y deliberadamente sin optimizar, en el repositorio josephg de eg-walker-reference. Su propósito es que el algoritmo se pueda leer entero y entender, no que corra rápido.

🦀

Diamond Types

La implementación optimizada en Rust, de Joseph Gentle. Es la que sostiene las mediciones del artículo y donde viven las decisiones de representación que producen la ventaja práctica.

🧩

Loro

Biblioteca de CRDT de propósito general que adopta el algoritmo para su tipo de texto, lo que lo pone al alcance de aplicaciones que necesitan además mapas, listas y árboles.

🔬

Cuál usar para qué

La de referencia para aprender y para validar tu propia implementación; la de Rust cuando el rendimiento es el objetivo; Loro cuando quieres un documento con varios tipos de datos.

Que exista una implementación de referencia legible junto a una optimizada no es un detalle menor y merece un comentario, porque marca una diferencia de calidad frente a buena parte de la literatura de esta materia. Los algoritmos de texto colaborativo tienen una historia larga de resultados publicados cuyas implementaciones eran inaccesibles o cuyas descripciones omitían el detalle que hacía falta para reproducirlas, y esa opacidad es la razón de que se hayan publicado varias correcciones de algoritmos clásicos años después de su aparición. Poder leer el algoritmo completo en un lenguaje corriente, sin la ofuscación que introduce cualquier optimización seria, es lo que permite auditarlo de verdad.

// Lo que cambia al adoptarlo no es la API, es el modelo de persistencia
guardar(documento);        // antes: volcar la estructura completa del CRDT
anexar(eventosNuevos);     // ahora: anadir al final del registro de eventos

// Y lo que aparece nuevo es una pregunta de producto
// cuanta historia conservas, y donde la cortas si decides cortarla

La adopción, por tanto, no se decide comparando funciones sino comparando lo que se guarda. La superficie de uso de estas bibliotecas es muy parecida entre sí: insertar, borrar, obtener texto, aplicar un cambio remoto, suscribirse a los cambios. Lo que cambia por debajo es el formato en disco y su curva de crecimiento, y esa es exactamente la parte que resulta cara de migrar más adelante. Elegir aquí es elegir con qué vas a vivir dentro de tres años.

⚠️
Cambiar de algoritmo de secuencia es una migración de datos, no de dependencia

Los documentos ya existentes están escritos en el formato de la biblioteca que los creó, y ese formato codifica la estructura interna del algoritmo. Migrar exige, en el mejor caso, reproducir la historia en el formato nuevo, y en el peor, aceptar que solo se conserva el texto final y se pierde la historia. Antes de adoptar cualquier opción, escribe el procedimiento de salida hacia otra y comprueba qué se pierde por el camino, porque hacerlo después es mucho más caro.

Qué deja este nivel más allá del algoritmo

Antes del cierre conviene resumir qué queda en pie de los cinco temas del nivel, porque el conjunto forma un argumento y no una colección de datos. El diagnóstico fue que un CRDT de secuencia guarda un metadato por cada carácter que existió, incluidos los borrados, y que ese metadato domina la memoria y el tiempo de carga. La idea fue guardar en su lugar una lista inmutable y solo-añadir de las operaciones originales, con su contexto causal, y tratar el texto como algo derivable. El mecanismo fue recorrer el grafo levantando un andamio temporal, retrocediendo y avanzando eventos según la rama, y vaciándolo por completo en cada versión crítica. Los resultados fueron un orden de magnitud menos de memoria en reposo, cargas desde disco órdenes de magnitud más rápidas y un peor caso comparable. Y el lugar es una tercera posición que combina la representación de una escuela con la integración de la otra.

Queda pendiente una pregunta que este nivel no responde y que el track aborda más adelante: qué hacer cuando el registro de eventos, que crece de forma monótona, se vuelve demasiado grande por sí solo. Eg-walker traslada el problema del tamaño desde la estructura viva hasta el archivo histórico, y ese archivo se comprime mucho mejor y se lee de forma secuencial, pero no es infinito. La compactación, la poda de historia y el direccionamiento por contenido son las herramientas de esa conversación, y tienen su propio bloque de niveles.

Cuando dos escuelas llevan veinte años enfrentadas, sospecha que discuten sobre ejes distintos

La lección duradera de este nivel no es Eg-walker, que dentro de una década será una entrada más en la genealogía de los algoritmos de secuencia, sino el movimiento intelectual que lo produjo, porque ese movimiento es repetible y casi nadie lo hace a tiempo. Durante dos décadas, transformación operacional y CRDT se debatieron como si fueran opciones excluyentes de una misma pregunta, y esa forma de plantearlo obligaba a heredar cada tradición en bloque: si elegías la representación compacta de una, te llevabas su procedimiento de fusión intratable en ramas largas; si elegías la integración robusta de la otra, te llevabas su coste permanente de metadatos. El paquete parecía indivisible porque nadie había nombrado la costura. Lo que hicieron Gentle y Kleppmann fue, antes que cualquier optimización, un trabajo de análisis conceptual: separar la pregunta de qué se persiste de la pregunta de cómo se integra, comprobar que eran independientes y probar la combinación que nadie había ensayado porque no cabía en el marco anterior. Todo el resultado técnico —el grafo de eventos, el andamio efímero, las versiones críticas— es consecuencia de esa separación, no al revés. Y la razón de que la combinación cruzada estuviera libre después de veinte años de trabajo intenso no es que fuera difícil de imaginar, sino que cada comunidad optimizaba dentro de su marco y el marco era precisamente lo que había que revisar. Esto sugiere un método, y merece la pena enunciarlo como tal porque se aplica muy lejos de aquí. Cuando encuentres un debate técnico que lleva mucho tiempo sin resolverse y en el que ambos bandos tienen argumentos sólidos, la hipótesis más productiva no es que uno tenga razón, sino que la pregunta está mal descompuesta y cada bando está optimizando un eje distinto mientras cree discutir del mismo. La tarea entonces no es elegir bando ni buscar un punto medio, que suele ser lo peor de ambos, sino identificar los ejes independientes que el debate ha fundido en uno solo y comprobar cuáles de sus combinaciones nunca se probaron. Suele haber alguna libre, suele estar libre por razones sociológicas y no técnicas, y suele ser mejor que las dos posiciones que llevaban veinte años defendiéndose. El resto de este track está lleno de debates con esa forma: estado frente a operaciones, poda frente a historia completa, entre pares frente a servidor. Ninguno está tan cerrado como parece.

⚔️ Sitúa el algoritmo en tu propio mapa
  1. Escribe en una tabla propia qué persiste y cómo integra cada una de las tres opciones, sin copiar la de la lección.
  2. Reproduce el escenario de entrelazado en la biblioteca que uses hoy y guarda el resultado exacto que produce.
  3. Lee la implementación de referencia en TypeScript hasta encontrar el punto donde se vacía el estado interno.
  4. Compara el formato en disco de tu biblioteca actual con un registro de eventos y estima el coste de migrar.
  5. Escribe el procedimiento de salida de tu biblioteca actual hacia otra y anota qué información se perdería.
  6. Elige un debate cerrado de tu propio dominio y busca los dos ejes que ambos bandos están confundiendo en uno.