El origen: la investigación que precede a la biblioteca
Automerge no nace de una urgencia de producto sino de la investigación académica sobre CRDT, y esa genealogía explica sus dos prioridades fundacionales: la corrección demostrable y el documento con historia completa.
Casi todas las bibliotecas de sincronización que has visto hasta aquí nacieron de una necesidad de producto: alguien tenía una aplicación que fallaba al reconectar y escribió el código que hacía falta para que dejara de fallar. Automerge nació al revés. Primero hubo una pregunta de investigación —si es posible definir un tipo de dato JSON replicado cuya fusión converja de forma demostrable sea cual sea el orden en que lleguen los cambios— y solo después, cuando la respuesta estuvo escrita y revisada, alguien se sentó a diseñar una API. Ese orden de los factores no es una anécdota biográfica: es la causa directa de todo lo que la biblioteca hace bien y de casi todo lo que hace mal. Explica por qué guarda la historia entera aunque cueste, por qué su modelo de datos es deliberadamente pobre, por qué tardó años en ser rápida y por qué, cuando por fin lo fue, no tuvo que romper el formato. Esta lección reconstruye esa genealogía y extrae de ella las prioridades que gobiernan el resto del nivel.
- Situar Automerge en la línea de investigación sobre CRDT y no en la de las bibliotecas de sincronización comerciales.
- Explicar qué significa exactamente corrección demostrable y en qué se distingue de estar bien probado.
- Justificar por qué la historia completa es una decisión fundacional y no una funcionalidad añadida.
- Anticipar qué costes impone esa genealogía y por qué los niveles siguientes giran en torno a ellos.
La genealogía de una biblioteca
La pregunta que da origen al proyecto es más estrecha y más ambiciosa de lo que parece. No es «cómo sincronizo dos clientes», que es un problema de ingeniería con mil respuestas parciales, sino «cómo defino un tipo de dato con la forma de un objeto JSON —mapas anidados, listas, texto, contadores— tal que dos réplicas que hayan recibido el mismo conjunto de operaciones estén garantizadamente en el mismo estado, sin coordinación previa, sin árbitro y sin que importe el orden de llegada». Es una pregunta de teoría de sistemas distribuidos, y su respuesta es un teorema, no un programa.
Ese linaje académico —ligado a la investigación de Martin Kleppmann y al trabajo posterior del laboratorio Ink and Switch, el mismo entorno del que salió el término local-first que abrió este track— deja una huella reconocible. La implementación de referencia existió antes que cualquier caso de uso serio, y durante bastante tiempo fue lenta y consumía una barbaridad de memoria. En un proyecto nacido de un producto, eso lo habría matado; aquí no, porque el criterio de éxito no era la latencia sino la propiedad. Una biblioteca que va rápido pero diverge está rota de una forma que no se arregla, mientras que una biblioteca que converge pero va lenta está incompleta de una forma que sí se arregla, y la historia posterior del proyecto es exactamente la crónica de ese arreglo.
Conviene además situar el término CRDT con algo más de precisión que la habitual, porque nombra una familia y no una técnica. Un tipo de dato replicado sin conflicto es cualquier estructura cuya operación de combinación cumple ciertas propiedades algebraicas, y hay dos maneras clásicas de conseguirlo: transmitir estados y combinarlos con una función que sea un supremo, o transmitir operaciones y garantizar que su aplicación es conmutativa. Automerge pertenece de lleno a la segunda familia y lleva esa elección hasta el final, porque transmitir el estado completo de un documento con historia sería absurdo. La consecuencia es que todo el diseño gira en torno a identificar operaciones y a saber cuáles ocurrieron antes que cuáles, que es justo lo que ocupará las lecciones siguientes.
flowchart TD A[pregunta de investigacion] --> B[definir un tipo de dato JSON replicado] B --> C[demostrar convergencia para todo orden de llegada] C --> D[implementacion de referencia lenta] D --> E[reescritura en Rust y compilacion a WebAssembly] E --> F[la ergonomia de la API llega al final] style C fill:#a6e3a1,color:#11111b style F fill:#89b4fa,color:#11111b
Conviene señalar la asimetría de esa cadena porque es la lección transferible. Las propiedades que se demuestran al principio son gratis para siempre: ninguna optimización posterior las puso en riesgo, y por eso la versión 3 pudo reescribir la representación interna entera sin romper el formato de archivo ni la semántica de fusión. Las propiedades que se dejan para después —velocidad, memoria, tamaño— son negociables y se pueden comprar con trabajo de ingeniería. Elegir en qué lado de esa línea pones cada requisito es, en la práctica, la decisión de arquitectura más consecuente que se toma en un proyecto de este tipo.
El entorno también condiciona el resultado. Un laboratorio que estudia herramientas de creación —cuadernos, editores, lienzos— piensa naturalmente en términos de documentos que una persona abre, edita durante meses y quiere conservar. No piensa en términos de filas de una tabla que un servicio consulta por millones. Ese sesgo hacia el documento está grabado en cada decisión de Automerge y es lo que lo hace excelente para cierta clase de aplicaciones e inadecuado para otra, como veremos en la última lección del nivel.
Qué significa corrección demostrable
El término se usa con ligereza, así que conviene fijarlo. Una biblioteca bien probada te dice que en los casos que alguien pensó en probar no se observó divergencia. Una biblioteca con corrección demostrable te dice algo categóricamente distinto: que existe un argumento matemático de que la divergencia es imposible para todo el espacio de entradas, incluidas las que nadie ha imaginado todavía.
La propiedad concreta se llama consistencia eventual fuerte y tiene dos mitades que se citan por separado con demasiada frecuencia. La primera es que si dos réplicas han recibido el mismo conjunto de cambios, sus estados son idénticos. La segunda, más silenciosa, es que la fusión no puede fallar: no hay ninguna combinación de cambios que produzca un error, un bloqueo o una petición de intervención humana. Juntas garantizan que el sistema siempre tiene una respuesta y que esa respuesta es la misma en todas partes.
Esa segunda mitad es la que ahorra más código del que nadie estima. En una arquitectura de sincronización artesanal, buena parte del trabajo no está en fusionar sino en gestionar los casos en que la fusión no se puede resolver: colas de reintento, estados de conflicto pendientes, pantallas donde se le pide al usuario que elija, marcas de registro roto que alguien tendrá que limpiar a mano. Toda esa maquinaria existe porque la operación de combinación es parcial, es decir, porque hay entradas para las que no está definida. Cuando la operación es total, la maquinaria entera desaparece de golpe, y con ella desaparece también la clase de incidentes en producción más difícil de reproducir: los que dependen de un orden de llegada concreto que nadie sabe recrear.
// Las tres propiedades algebraicas que sostienen la garantia.
// Se enuncian sobre la operacion de fusion de dos documentos.
// 1. Conmutativa: el orden de llegada es irrelevante.
merge(a, b) === merge(b, a)
// 2. Asociativa: el agrupamiento de los cambios es irrelevante.
merge(merge(a, b), c) === merge(a, merge(b, c))
// 3. Idempotente: recibir dos veces lo mismo no cambia nada.
merge(a, a) === a
Esas tres líneas son el motivo por el que un sistema construido sobre Automerge no necesita un servidor autoritativo, ni un protocolo de consenso, ni relojes sincronizados. Si la fusión es conmutativa, asociativa e idempotente, entonces cualquier topología de red vale: estrella con servidor, malla entre pares, sincronización por correo electrónico o por una memoria USB que alguien lleva andando de una oficina a otra. La red deja de ser parte del argumento de corrección y pasa a ser un simple detalle de transporte, que es justamente lo que un track de local-first necesita.
Vale la pena insistir en lo poco frecuente que es esta situación. En la inmensa mayoría de arquitecturas distribuidas, la corrección depende de propiedades del transporte: que los mensajes lleguen, que lleguen una sola vez, que lleguen en orden, o que exista un canal fiable hacia un nodo concreto. Cada una de esas suposiciones es una fuente de incidentes cuando la red se comporta como se comporta de verdad. Aquí ninguna de ellas aparece en el argumento: el transporte puede duplicar, reordenar, perder y retrasar cuanto quiera, y lo único que eso afecta es cuándo converge el sistema, nunca si converge. Esa separación limpia entre garantías de seguridad y garantías de progreso es la marca de un diseño que ha pasado por una demostración.
Aquí es donde se rompen la mayoría de las intuiciones, y merece la pena ser tajante. El teorema garantiza que todas las réplicas llegan al mismo estado; no garantiza que ese estado sea el que los usuarios querían. Si dos personas editan a la vez el campo titulo, la biblioteca elegirá determinísticamente uno de los dos valores y todas las réplicas verán el mismo, pero la elección es arbitraria desde el punto de vista humano. La convergencia es una propiedad de seguridad de la estructura de datos, no de la semántica de tu dominio. Modelar los datos para que los conflictos que importan no se puedan expresar sigue siendo trabajo tuyo, y ninguna biblioteca de esta familia te lo va a quitar.
La historia completa como decisión fundacional
La segunda herencia es la que más consecuencias prácticas tiene. Para que la fusión funcione sin coordinación, cada réplica necesita poder situar cualquier cambio entrante respecto de los que ya conoce, y eso exige conservar la identidad y las dependencias causales de cada operación. No es una elección de diseño que se pueda revocar: es un requisito del mecanismo. Guardar la historia no es una funcionalidad de Automerge, es su condición de posibilidad.
La consecuencia es la analogía que la propia documentación usa y que conviene tomarse en serio: un documento de Automerge es, a la vez, un objeto JSON y un repositorio de control de versiones. Tiene un valor actual que lees como leerías cualquier estructura de datos, y tiene un historial de commits con sus padres causales que puedes recorrer. Ambas cosas viven en el mismo archivo binario y son inseparables.
// Un documento es a la vez un valor que se lee...
console.log(doc.titulo, doc.tareas.length);
// ...y un grafo de cambios que se recorre.
// Cada cambio conoce a sus padres, igual que un commit.
//
// raiz ---- cambio de ana ----\
// \ >--- fusion --- cabeza
// \--- cambio de luis ----/
//
// Ese grafo es lo que permite fusionar sin arbitro:
// la relacion de dependencia entre cambios ya codifica
// que ocurrio antes que que, sin necesidad de reloj alguno.
La analogía con el control de versiones es literal en un punto que conviene subrayar: la identidad de un cambio es el hash de su contenido y de sus padres, exactamente igual que en un repositorio de código. Eso trae de regalo dos propiedades muy útiles. La primera es la deduplicación automática, porque el mismo cambio producido dos veces da el mismo hash y la segunda copia se ignora sola. La segunda es la verificación de integridad, porque no se puede alterar un cambio del pasado sin invalidar el hash de todos sus descendientes.
Es una buena analogía y conviene saber dónde deja de serlo. En un repositorio de código, fusionar dos ramas que tocaron las mismas líneas produce un conflicto que una persona resuelve a mano, y ese es el comportamiento deseado porque nadie quiere que una máquina decida qué versión de una función es la correcta. Aquí no hay conflicto que resolver: la fusión siempre produce un resultado, sin intervención. Esa diferencia es la razón de ser de todo el aparato teórico, y también el motivo por el que hay que modelar los datos con cuidado, porque lo que un sistema de versiones te obligaría a mirar, este lo resuelve en silencio.
La identidad es del cambio
Cada cambio tiene un hash criptográfico que lo identifica y una lista de hashes de sus padres, exactamente como un commit.
El pasado es direccionable
Cualquier punto de la historia se puede reconstruir, porque todo lo necesario para hacerlo sigue en el documento.
Sin servidor de por medio
Al no depender de un árbitro para ordenar, la topología de red se vuelve libre y el trabajo sin conexión es el caso normal.
El coste se paga siempre
Metadatos por cada pulsación de tecla que no se pueden tirar sin perder la capacidad de fusionar con quien lleve tiempo desconectado.
La cuarta tarjeta es la que define la agenda de los tres niveles siguientes. Un documento que registra cada pulsación acumula metadatos sin techo, y durante la serie 2.x esa acumulación era un problema real: documentos grandes que consumían cientos de megabytes al abrirse, y algunos que sencillamente no llegaban a abrirse. La lección cuarta cuenta cómo la versión 3 desarma ese problema sin renunciar a nada de lo anterior, y por qué eso solo fue posible gracias a que la corrección estaba demostrada de antemano.
Lo que hay que llevarse de esta lección no es quién escribió qué artículo, sino un principio de diseño que se aplica muchísimo más allá de los CRDT y que la trayectoria de Automerge ilustra con una limpieza poco común. En cualquier sistema conviven dos clases de propiedades y la diferencia entre ellas es de naturaleza, no de grado. Están las propiedades estructurales —converge, no pierde datos, no puede fallar al fusionar, el formato es legible dentro de diez años— que se establecen en el momento en que defines el modelo y que, si no las tienes desde el principio, no las vas a añadir después: intentarlo significa reescribir el sistema entero, porque cada línea de código escrita sobre un modelo que no converge asume implícitamente que no converge. Y están las propiedades cuantitativas —cuánta memoria, cuántos milisegundos, cuántos bytes— que son negociables, medibles, y que se compran con trabajo. La tentación permanente de la industria es la inversa: empezar por lo cuantitativo, porque es lo que se demuestra en una reunión, y dejar lo estructural para cuando haya tiempo. Automerge es la demostración empírica de que el orden correcto es el otro, y la prueba está en un detalle que suele pasar desapercibido: cuando la versión 3 rehízo por completo la representación interna para recortar la memoria en más de un orden de magnitud, no tuvo que cambiar el formato de archivo ni la semántica de fusión. Los documentos de la versión 2 se siguen abriendo. Eso solo es posible porque lo que se optimizó era la capa negociable y lo que se dejó intacto era la capa demostrada, y esa separación estaba trazada desde el primer artículo. Si inviertes el orden, cada optimización te obliga a renegociar la corrección, y acabas con un sistema donde nadie se atreve a tocar el núcleo porque nadie sabe ya qué garantiza. El corolario operativo, y es el que deberías aplicar mañana: cuando evalúes cualquier tecnología de sincronización, la pregunta útil no es cuánto tarda, sino qué demuestra y qué se limita a probar. Lo primero te dice de qué no vas a tener que preocuparte nunca; lo segundo, solo que todavía no ha explotado.
Lo que se hereda y lo que se paga
Resumir la herencia en una tabla mental ayuda a leer el resto del nivel con las expectativas calibradas. Del lado del activo: fusión garantizada sin servidor, historia completa y direccionable, formato binario estable con compatibilidad hacia atrás entre versiones mayores, y un modelo de datos con semántica precisa en lugar de convenciones. Del lado del pasivo: metadatos que crecen con cada operación, un modelo de documento que no sabe de consultas ni de índices, y una unidad de sincronización —el documento— que hay que dimensionar bien desde el principio porque después es caro cambiarla.
Hay una asimetría importante entre esas dos listas y conviene enunciarla antes de seguir. Los activos son propiedades cerradas: no van a mejorar mucho más porque ya son lo que son. Los pasivos son problemas abiertos sobre los que el proyecto trabaja de forma visible, y la versión 3 es precisamente el episodio en el que uno de ellos —el consumo de memoria— pasa de ser una objeción decisiva a ser un detalle. Evaluar la biblioteca por el estado de sus pasivos en 2023 es un error frecuente y costoso.
Es tentador leer el consumo de metadatos como una torpeza que alguien acabará puliendo del todo, y no lo es. Guardar identidad y dependencias por operación es lo que hace posible fusionar sin árbitro; una implementación que dejara de hacerlo no sería un Automerge más eficiente sino otra cosa distinta con otras garantías. Lo que sí se puede optimizar sin límite es cómo se representan esos metadatos, y ahí es donde el proyecto ha ganado sus batallas. Distinguir entre reducir el coste de una información y dejar de guardarla es la diferencia entre una optimización y un cambio de producto.
Esa distinción también explica por qué la comparación con otras bibliotecas de la familia suele estar mal planteada. Muchas de las alternativas que aparecen en las tablas comparativas consumen menos, y consumen menos precisamente porque descartan la historia una vez fusionada. No están ganando la misma carrera con mejor técnica: están corriendo una carrera más corta. La comparación honesta exige fijar primero qué se quiere conservar, y solo después mirar cuánto cuesta conservarlo, que es el orden en el que la última lección del nivel plantea la decisión.
Queda una advertencia de método para cerrar. La adopción de Automerge ronda las ochenta y cinco mil descargas semanales, una cifra que en el ecosistema JavaScript es modesta en términos absolutos y muy significativa en términos de categoría: no es una dependencia que arrastran los andamiajes por defecto, sino una elegida deliberadamente por equipos que sabían lo que buscaban. Interpretar esa cifra como señal de madurez o de inmadurez, sin mirar quién la usa y para qué, no informa de nada.
Con eso queda montado el andamiaje del nivel. La lección siguiente baja al modelo de programación y explica qué significa exactamente que el documento sea un valor inmutable. La tercera abre la historia y muestra qué se puede preguntar sobre ella. La cuarta cuenta cómo la versión 3 desactivó el pasivo más grave sin tocar nada de lo demostrado. Y la quinta cierra con la única pregunta que de verdad decide una arquitectura: si el dato del que hablas tiene o no tiene una autoridad legítima detrás.
- Escribe la pregunta de investigación de Automerge con tus palabras y sin usar la palabra sincronización.
- Enuncia las dos mitades de la consistencia eventual fuerte y pon un ejemplo de sistema que cumpla la primera y no la segunda.
- Localiza en tu aplicación actual dos campos donde converger al valor equivocado sería un incidente y explica por qué.
- Clasifica los requisitos de tu proyecto en estructurales y cuantitativos, y comprueba cuáles estás dejando para después.
- Estima cuántas operaciones por sesión generaría tu caso de uso si cada pulsación fuera un cambio registrado.
- Anota qué tendría que ser cierto sobre el coste de esa historia para que la decisión te compensara, y contrástalo al terminar el nivel.