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

Los resultados: un orden de magnitud menos y cargas casi instantáneas

Frente a los CRDT existentes, Eg-walker consume un orden de magnitud menos de memoria en estado estable y carga documentos desde disco órdenes de magnitud más rápido, con un peor caso comparable al de un CRDT.

⏱ 18 min

Un algoritmo que cambia qué se persiste solo merece atención si los números acompañan, y aquí el interés está tanto en la magnitud de las mejoras como en su asimetría. El artículo de EuroSys 2025 mide tres cosas distintas y obtiene tres respuestas distintas: frente a los CRDT existentes, un orden de magnitud menos de memoria en estado estable y cargas desde disco órdenes de magnitud más rápidas; frente a la transformación operacional, fusiones de ramas divergentes órdenes de magnitud más rápidas; y en el peor caso, un rendimiento de fusión comparable al de los CRDT actuales. Esa última frase es la más importante de las tres y la que más se omite al citar el trabajo, porque es la que convierte el resultado en algo adoptable: no hay un caso en el que el algoritmo se derrumbe. Esta lección desglosa las tres mediciones, explica de dónde sale cada una y advierte de cómo se pueden leer mal.

🎯 Al terminar esta lección sabrás
  • Distinguir las tres mediciones del artículo y contra qué línea base se compara cada una.
  • Explicar mecánicamente por qué la memoria en estado estable baja un orden de magnitud.
  • Explicar por qué la carga desde disco mejora en órdenes de magnitud y no solo en un factor.
  • Interpretar correctamente la garantía de peor caso y qué hipótesis sobre las trazas la sostienen.

Qué se mide y contra qué

Antes de los resultados conviene fijar el método, porque en esta materia el método decide el número. Las evaluaciones serias de algoritmos de texto colaborativo no usan cargas sintéticas de inserciones aleatorias, sino trazas de edición reales: registros carácter a carácter de sesiones de escritura auténticas, algunas producidas por un solo autor a lo largo de meses, otras por varios autores editando a la vez, y otras reconstruidas a partir de historiales de control de versiones donde las ramas divergieron de verdad durante días.

Esa diversidad importa porque cada perfil ejercita una parte distinta del algoritmo. Una traza secuencial larga mide la codificación y el atajo de los tramos sin concurrencia. Una traza con edición simultánea mide el coste de retroceder y avanzar. Una traza con ramas de larga duración mide justo lo que la transformación operacional no soporta. Un resultado que solo presume de la primera no dice nada, y por eso las comparaciones caseras suelen ser inútiles.

Tres mediciones, tres lineas base, tres respuestas

  memoria en estado estable ....... frente a CRDT .... un orden de magnitud menos
  carga de documento desde disco .. frente a CRDT .... ordenes de magnitud mas rapido
  fusion de ramas divergentes ..... frente a OT ...... ordenes de magnitud mas rapido
  fusion en el peor caso .......... frente a CRDT .... comparable

La implementación medida es la optimizada en Rust, no la de referencia. Esa distinción no es un tecnicismo: buena parte de la ventaja viene de decisiones de representación —codificación por tramos, disposición de los datos en memoria contigua, estructuras de índice cuidadas— que una implementación directa del algoritmo no tiene. Existe también una implementación de referencia en TypeScript sin optimizar, cuyo propósito es que el algoritmo se pueda leer y entender, no que corra rápido.

Hay una asimetría en la lista anterior que conviene señalar antes de entrar en detalle, porque es la clave para leer el trabajo entero. Las dos primeras filas comparan contra los CRDT y ganan en el terreno donde los CRDT eran débiles; la tercera compara contra la transformación operacional y gana en el terreno donde esta era débil; y la cuarta declara que en el terreno donde los CRDT eran fuertes se queda a la par. Es decir, el algoritmo no intercambia una debilidad por otra, que es el patrón habitual en optimización de estructuras de datos, sino que recoge las dos fortalezas y no adquiere ninguna debilidad nueva. Esa es la afirmación fuerte del artículo, y es también la que más escrutinio merece.

📝
Rendimiento de fusión no es lo mismo que rendimiento de escritura local

Las tres primeras filas hablan de momentos distintos de la vida del documento y suelen confundirse en las discusiones. La escritura local es la latencia entre pulsar una tecla y ver el carácter, y en todas las opciones serias es despreciable porque solo implica anexar. La fusión es lo que ocurre al integrar cambios ajenos, y solo es cara cuando esos cambios divergieron durante mucho tiempo. La carga es lo que ocurre al abrir. Un usuario percibe las tres de forma distinta y tolera cosas muy distintas en cada una, así que mezclarlas en una sola cifra de rendimiento no informa de nada.

ℹ️
Comparar algoritmos comparando bibliotecas es una trampa frecuente

Cuando se enfrenta una implementación muy trabajada contra bibliotecas de madurez desigual, parte de la diferencia medida es de ingeniería y no de algoritmo. Los autores lo saben y por eso el artículo argumenta cada mejora con el mecanismo que la produce, no solo con la cifra. La lectura honesta de estos números es que la ventaja estructural existe y es grande, y que su magnitud exacta en tu caso dependerá de tu perfil de edición y de la calidad de ambas implementaciones.

La memoria en estado estable

Antes de entrar en el mecanismo conviene fijar bien el término, porque se usa con ligereza y aquí tiene un significado técnico preciso. Estado estable no quiere decir documento cerrado ni documento inactivo: quiere decir que el grafo no tiene ramas pendientes de integrar, que es la situación en la que el andamio no hace falta.

Estado estable significa el caso normal: el documento está abierto, alguien escribe, no hay ninguna fusión en curso. Es la situación en la que una aplicación pasa el noventa y nueve por ciento de su tiempo, y es donde el CRDT convencional paga sin recibir nada a cambio, porque mantiene vivo un andamio que en ese momento no está resolviendo ningún conflicto.

El mecanismo de la mejora ya está montado en las lecciones anteriores y aquí solo hay que sumarlo. En reposo, lo que Eg-walker tiene en memoria es el texto visible más la lista de eventos, y esa lista está comprimida por tramos y no se fragmenta con el uso porque nadie edita el pasado del grafo. No hay identificadores por carácter, no hay punteros a vecinos y no hay lápidas del andamio, porque el andamio se vació en la última versión crítica.

🧊

Sin andamio en reposo

Las identidades por carácter solo existen durante una fusión, así que fuera de ella el coste por carácter desaparece.

📦

Eventos, no elementos

La identidad se reparte por operación y no por carácter, y una ráfaga de tecleo es un solo tramo.

🪦

Lápidas sin residuo

El borrado es un evento con longitud, no un elemento marcado que sobreviva indefinidamente en la estructura viva.

🧵

Sin fragmentación

Editar en medio del texto parte los tramos de un CRDT; en un registro solo-añadir no parte nada, porque nada se reescribe.

La cuarta tarjeta es la que explica por qué la ventaja crece con la edad del documento en lugar de diluirse. Un CRDT recién construido a partir de escritura secuencial está bien comprimido; el mismo CRDT después de dos años de revisiones tiene sus tramos rotos en fragmentos pequeños, porque cada intervención en medio partió uno. El registro de eventos no sufre esa degradación: las revisiones se anexan al final como tramos nuevos y los tramos antiguos permanecen intactos.

Como evoluciona la compresion con la edad del documento

  CRDT
    recien escrito ..... tramos largos, buena compresion
    tras mil revisiones  tramos partidos una y otra vez, compresion pobre

  Eg-walker
    recien escrito ..... tramos largos de eventos
    tras mil revisiones  tramos largos de eventos, mas mil tramos nuevos al final

Ese contraste tiene una lectura que va más allá del número: el CRDT paga peor cuanto mejor se ha trabajado el documento, porque revisar es exactamente lo que fragmenta su estructura. El registro de eventos es indiferente a la revisión, porque una revisión no es una modificación de lo guardado sino una adición. Un documento muy pulido y uno escrito de un tirón tienen, en este esquema, el mismo perfil de compresión por unidad de trabajo realizado.

💡
El estado estable es también el que decide la experiencia percibida

Vale la pena traducir la mejora a consecuencias que un usuario nota. Menos memoria residente significa que el sistema operativo no descarta la pestaña mientras se atiende una llamada, que se pueden tener varios documentos abiertos a la vez y que un dispositivo modesto se comporta igual que uno caro. Ninguna de esas tres cosas aparece en un gráfico de barras, y las tres son la diferencia entre una aplicación que la gente adopta y una que abandona a los tres meses.

Cargar el documento desde disco

La segunda medición es la más espectacular y también la más fácil de explicar, y conviene entender por qué es de órdenes de magnitud y no de un factor moderado: no se está acelerando el mismo trabajo, se está eliminando trabajo.

Abrir un documento en un CRDT convencional implica leer el formato serializado, reconstruir todos los elementos con sus identidades, rehacer las estructuras de índice que permiten localizar posiciones y dejar todo eso disponible en memoria. Es una reconstrucción proporcional al número de elementos que existieron alguna vez, incluidas todas las lápidas. Abrir un documento en Eg-walker es leer el texto y, si hace falta, la lista de eventos; no hay ninguna estructura de identidades que reconstruir porque no la hay en estado estable.

flowchart LR
A[archivo en disco] --> B[que hay que reconstruir al abrir]
B -->|CRDT| C[todos los elementos con id y vecinos]
C --> D[reconstruir indices sobre la historia completa]
D --> E[abrir tarda proporcional a la historia]
B -->|Eg walker| F[texto mas lista de eventos comprimida]
F --> G[abrir tarda proporcional al texto]
style E fill:#f38ba8,color:#11111b
style G fill:#a6e3a1,color:#11111b

La consecuencia arquitectónica de esta rama verde es mayor que la propia cifra, y conecta con todo lo que el track lleva construido sobre almacenamiento en el cliente. Un documento que se abre en milisegundos ya no necesita estrategias de carga diferida, ni pantallas de progreso, ni el patrón de mostrar una vista previa mientras la estructura real termina de levantarse. Desaparece una capa entera de complejidad accidental que las aplicaciones basadas en CRDT habían aprendido a considerar inevitable.

Lo que se elimina, no lo que se acelera

  CRDT     leer bytes -> materializar elementos -> reindexar -> listo
  Eg-walker leer bytes -> listo
             el andamio solo se construye si llega una fusion concurrente

Hay un matiz que no conviene esconder: si al abrir el documento llega inmediatamente una sincronización con ramas divergentes, habrá que recorrer el grafo y levantar el andamio, y eso cuesta. Pero es un coste condicionado a que exista concurrencia real que integrar, mientras que la reconstrucción del CRDT se paga siempre, incluso al abrir un documento que nadie más ha tocado.

Y hay un caso que merece mención propia porque es el más frecuente de todos y el que peor trataba el enfoque anterior: abrir un documento para leerlo. Consultar una nota, revisar un informe, buscar una cita. En esas sesiones no se edita nada y no hay nada que fusionar, de modo que el algoritmo no llega a construir andamio en ningún momento. Un CRDT convencional, en cambio, paga la reconstrucción íntegra antes de mostrar la primera línea, porque su representación del texto y su representación de la fusión son la misma cosa y no se pueden pedir por separado. La tercera fila de las mediciones se explica en buena parte por ahí.

El peor caso y cómo leer estos números

Queda la afirmación más sobria del artículo y la que sostiene su credibilidad: en el peor caso, el rendimiento de fusión de Eg-walker es comparable al de los CRDT existentes. No mejor. Comparable. Es exactamente lo que uno esperaría del razonamiento de la lección anterior: cuando el grafo es masivamente concurrente y no hay versiones críticas donde vaciar, el algoritmo acaba manteniendo un andamio del tamaño del que un CRDT mantiene siempre, y hace además el trabajo extra de retroceder y avanzar.

La comparación con la transformación operacional funciona en el sentido inverso y por la misma razón estructural. Fusionar dos ramas que llevan semanas separadas es el escenario que la transformación operacional resuelve peor, porque cada operación entrante debe transformarse contra todas las intermedias y el trabajo crece como el producto de las dos ramas. Aquí no hay transformaciones encadenadas: hay un recorrido que coloca cada operación por identidad, y su coste depende de cuánta concurrencia haya que reconciliar, no del cuadrado de la longitud de las ramas. De ahí el resultado de órdenes de magnitud en ese eje concreto.

Conviene además fijar qué hipótesis empírica sostiene la ventaja, porque es la parte falsable del argumento. La hipótesis es que en documentos escritos por personas los participantes se sincronizan con suficiente frecuencia como para que el grafo se estreche a menudo, y que por tanto las versiones críticas abundan. Es una afirmación sobre comportamiento humano y no sobre matemáticas, y es verificable en tus propias trazas contando cuántas aparecen por hora de uso. Si en tu sistema no aparece casi ninguna, ya sabes de antemano que los números del artículo no se van a reproducir.

⚠️
Si tu carga de trabajo es concurrencia permanente, este algoritmo no es tu palanca

Hay perfiles donde la hipótesis del horizonte temporal corto no se cumple: sistemas donde cientos de agentes escriben simultáneamente sin sincronizarse nunca del todo, o donde el grafo se mantiene abierto en ramas por diseño. Ahí las versiones críticas escasean, el andamio no se vacía y la ventaja se evapora. No es un fallo del algoritmo sino su condición de aplicabilidad, y reconocerla antes de adoptar ahorra una migración que no habría servido de nada.

La forma de la curva importa más que su altura, y aquí la forma cambió de eje

Lo que hace que estos números merezcan un nivel entero no es su tamaño sino un cambio cualitativo que las cifras esconden y que conviene explicitar. En un CRDT de secuencia, tanto la memoria residente como el tiempo de apertura son funciones crecientes de la historia total del documento: cuanto más tiempo lleva vivo, más caro es, y ese crecimiento es monótono e irreversible porque nada de lo acumulado se puede soltar. Es una curva sin techo, y su consecuencia práctica es una fecha de caducidad implícita en cada documento, un punto futuro en el que la aplicación dejará de ser usable para ese archivo concreto. Los equipos que construyen sobre CRDT conocen esa fecha, la temen y la gestionan con instantáneas, compactaciones y truncados de historia, es decir, tirando información real a cambio de seguir funcionando. Lo que Eg-walker cambia no es la altura de la curva sino su eje: la memoria en estado estable y el tiempo de apertura pasan a depender del texto visible y del tramo concurrente más ancho, dos magnitudes que no crecen con el tiempo sino que oscilan alrededor de un valor que fija el propio uso. Un documento de cinco años y uno de cinco días con el mismo texto visible cuestan aproximadamente lo mismo en reposo. Eso elimina la fecha de caducidad, y eliminarla vale mucho más que cualquier factor constante, porque un factor constante se compra con hardware y una curva sin techo no. La segunda mitad de la observación es la que hay que llevarse al evaluar cualquier trabajo de este tipo: el resultado que importa no es el mejor caso ni el promedio, sino la garantía en el peor, y por eso la frase más valiosa del artículo es la más modesta. Un algoritmo que mejora diez veces el caso normal y empeora cien veces el caso raro es intratable en producción, porque los casos raros ocurren y ocurren en el peor momento. Uno que mejora un orden de magnitud el caso normal y queda a la par en el peor es, sencillamente, mejor sin condiciones, y esa es una propiedad rarísima en optimización de estructuras de datos. Cuando encuentres un resultado así, lo que hay que auditar no es la mejora sino la afirmación sobre el peor caso, porque ahí es donde se esconden las hipótesis que decidirán si funciona con tus datos.

⚔️ Reproduce las mediciones con tus propias trazas
  1. Captura una traza de edición real de tu aplicación, con marcas de tiempo y autoría, no un guion sintético.
  2. Mide la memoria residente en estado estable de tu biblioteca actual con esa traza cargada y sin editar nada.
  3. Mide el tiempo de apertura desde disco frío y sepáralo del tiempo de renderizar el texto en pantalla.
  4. Construye una traza con dos ramas que diverjan durante mil operaciones cada una y mide la fusión.
  5. Repite las tres mediciones con una implementación de Eg-walker y anota qué factor obtienes en cada una.
  6. Busca en tus trazas cuántas versiones críticas hay por hora de uso y decide si tu perfil cumple la hipótesis.