Qué hacen las librerías reales
Las bibliotecas de CRDT ofrecen piezas parciales de compactación y dejan la política entera al producto, y esa carga explica por qué tantos sistemas local-first acaban poniendo un servidor de por medio.
Queda comprobar qué parte de todo esto viene resuelta cuando eliges una biblioteca, y la respuesta tiene una forma muy consistente en todo el ecosistema: te dan el mecanismo y no te dan la política. Codificaciones binarias excelentes, instantáneas, fusión de tramos borrados, formatos de intercambio incrementales y, en las más recientes, alguna forma explícita de recortar historia. Lo que ninguna te da es el censo de réplicas, el criterio para declarar un corte ni la decisión de qué pasa con quien vuelve tarde, porque las tres cosas son propiedades de tu producto y no del tipo de dato. Ese reparto no es una carencia de las bibliotecas; es la frontera natural entre lo que se puede resolver con un algoritmo y lo que exige saber a quién estás dispuesto a dejar atrás. Y explica, mejor que ningún otro argumento, por qué la mayoría de los sistemas local-first que llegan a producción acaban teniendo un servidor aunque su tesis fuese no necesitarlo.
- Distinguir las dos filosofías frente a la historia y qué compromete cada una.
- Saber qué ofrece hoy una biblioteca típica en materia de compactación y qué sigue dejando fuera.
- Entender por qué la compactación es uno de los motivos por los que aparece un servidor.
- Formular las preguntas concretas que hay que hacerle a una biblioteca antes de comprometerse.
- Aplicar la prueba que distingue un sistema local-first real de una aplicación en la nube con caché optimista.
Dos filosofías frente a la historia
El ecosistema se divide con bastante nitidez en dos posturas que no son mejores ni peores sino respuestas a productos distintos, y conviene identificar en cuál cae la que estés valorando porque determina qué problemas de este nivel vas a tener.
Una advertencia antes de entrar en detalles concretos. Este es el terreno del track que envejece más deprisa: las cifras cambian con cada versión mayor, las capacidades aparecen y las opciones se renombran. Lo que sigue vale como mapa de posturas y no como referencia de funcionalidades, y cualquier decisión real debería contrastarse con la documentación vigente el día que se tome. Lo que no envejece, y es lo que conviene retener, es el reparto de responsabilidades: qué clase de cosas puede resolver una biblioteca y qué clase de cosas no puede resolver ninguna, por razones que no dependen de su calidad.
Hay un tercer grupo que conviene situar aparte porque compite en otra categoría: las capas de sincronización completas, que no son bibliotecas de tipos de datos sino productos que incluyen el servidor, el protocolo, el almacenamiento y a menudo el modelo de permisos. Su respuesta a este nivel es sencilla y honesta: la compactación la hacen ellas, con su servidor como testigo, y tú no la ves. Es una respuesta perfectamente válida y el precio está claro desde el principio, que es haber trasladado una parte de tu arquitectura a un componente que no controlas. Quien elige por ahí no tiene los problemas de este nivel; tiene los de la lección quinta del nivel 5.
La primera postura considera que la historia no es contenido y que el documento es su estado actual. Yjs es el ejemplo canónico: su prioridad declarada es que un editor colaborativo sea rápido y ocupe poco, y para eso descarta agresivamente lo que no hace falta para seguir editando. Con la recolección activada, el contenido de los elementos borrados desaparece y solo se conserva el rango de identificadores, de modo que un párrafo eliminado deja un intervalo y no mil registros. El conjunto de borrados se representa como rangos y se fusiona. El resultado es una estructura notablemente compacta a cambio de que la información sobre qué decía ese párrafo ya no exista en el documento.
La segunda postura considera que la historia sí es contenido. Automerge es el ejemplo claro: conserva el registro completo de cambios con sus dependencias, lo que permite reconstruir cualquier estado anterior, atribuir autoría por carácter y ofrecer un deshacer que sobrevive a las sesiones. El precio es exactamente el del primer capítulo, y el trabajo de sus últimas versiones ha ido en la dirección de comprimirlo mucho mejor con codificación columnar y representaciones más densas, con mejoras de memoria de un orden de magnitud, sin cambiar la premisa de que nada se tira.
La diferencia entre ambas no es de calidad de ingeniería sino de qué consideran que es el objeto que están replicando, y esa es una decisión de dominio. Un editor de texto en el que nadie va a auditar quién escribió qué no gana nada conservando la historia y paga por ella todos los días. Un sistema de notas clínicas, un registro contable o una herramienta de escritura colaborativa con revisión editorial tienen la obligación de conservarla, y para ellos la primera postura no es una optimización sino una pérdida de una función obligatoria. Preguntar cuál de las dos bibliotecas es mejor es una pregunta mal formulada; la buena es qué clase de objeto estás construyendo.
Hay un matiz que conviene añadir sobre la primera postura para no describirla injustamente. Descartar el contenido de lo borrado no equivale a descartar su rastro: los identificadores de los elementos retirados siguen conservándose, porque son necesarios para no resucitarlos, y lo que desaparece es únicamente lo que decían. Es decir, incluso la postura más agresiva del ecosistema sigue pagando el residuo del nivel 28 y sigue creciendo con el número de retiradas, solo que con una constante mucho menor. La compactación que este nivel describe le hace falta igual; simplemente le hace falta más tarde.
Existe una tercera postura, más reciente y todavía minoritaria, que consiste en hacer la elección reversible en el tiempo en lugar de fijarla en el diseño: conservar la historia completa durante una ventana reciente y recortarla más allá, de modo que las funciones que dependen del camino existan pero con alcance limitado. Es la solución que casi todo el mundo quiere cuando se le explican las dos primeras, y su dificultad no está en implementar el recorte sino en que la ventana vuelve a ser el corte de la lección segunda, con todos sus problemas intactos. Que el ecosistema esté moviéndose hacia ahí es una buena noticia y no cambia dónde está la parte difícil.
// Yjs: la recoleccion se decide al crear el documento
const conGc = new Y.Doc({ gc: true }); // compacto, sin instantaneas historicas
const sinGc = new Y.Doc({ gc: false }); // permite comparar versiones, crece mas
// Automerge: guardar produce una representacion densa de toda la historia
const bytes = Automerge.save(doc); // comprime, no poda
// Las bibliotecas mas recientes exponen un recorte explicito de historia
// doc.export({ mode: "shallow-snapshot", frontiers }); // estilo clon superficial
Merece la pena subrayar que en la primera familia la decisión no es un ajuste que se pueda revisar más adelante. Activar la recolección afecta a lo que se persiste desde el primer día, y un documento creado con ella no puede recuperar después una información que nunca guardó. Eso significa que la pregunta ¿vamos a querer alguna vez comparar versiones, atribuir autoría o deshacer más allá de la sesión? hay que responderla antes de escribir código, con la incomodidad de tener que anticipar funciones de producto que todavía no existen. La respuesta prudente parece ser desactivar la recolección por si acaso, y suele ser un error caro: se paga crecimiento continuo durante años por una función que a menudo nunca se construye. La decisión correcta no es la conservadora sino la explícita, y conviene dejarla escrita junto a los motivos, porque quien la reabra dentro de dos años no tendrá el contexto.
Lo que ninguna te resuelve
Por debajo de las diferencias, el reparto de responsabilidades es idéntico en todas y conviene tenerlo claro antes de evaluar ninguna. Las piezas que sí vienen hechas son las locales y verificables: codificación, instantánea, formato incremental, fusión de tramos. Las que no vienen hechas son las que dependen de saber quién más existe.
Ese reparto se detecta fácilmente leyendo la documentación con un criterio simple: todo lo que la biblioteca resuelve viene descrito como una función con entradas y salidas, y todo lo que no resuelve viene descrito como una recomendación en prosa. Cuando encuentras una sección que explica consideraciones a tener en cuenta al decidir cuándo compactar, sin ninguna función asociada, has encontrado exactamente el límite de lo que te han dado. No es un defecto de la documentación; es la señal honesta de que ahí empieza tu trabajo.
Te dan: codificación
Formatos binarios densos, fusión de tramos borrados y representaciones columnares. Es donde ha ido casi todo el esfuerzo de los últimos años.
Te dan: instantánea
Serializar el estado en un artefacto que se carga sin reconstruir. Casi todas lo ofrecen y suele ser tu primera mejora medible.
No te dan: el censo
Ninguna sabe cuántas réplicas tiene tu producto ni cuáles siguen importando. Sin eso no hay frontera de estabilidad calculable.
No te dan: la política
Cuándo cortar, a quién expulsar y qué hacer con el que vuelve. Son decisiones sobre usuarios y ninguna biblioteca puede tomarlas.
Conviene mirar las cuatro tarjetas como una línea divisoria y no como un inventario, porque lo que las separa es una propiedad muy concreta: las dos primeras se pueden verificar dentro de un proceso, con una prueba que compara una entrada con una salida, y las dos últimas no. No hay forma de escribir una prueba automática que confirme que tu censo está completo, porque su corrección depende de dispositivos que por definición no están presentes cuando la prueba corre. Una biblioteca solo puede garantizar aquello que puede verificar, y esa restricción, y no la falta de ambición de sus autores, es lo que dibuja la frontera.
El motivo de ese reparto no es comodidad de los autores sino una frontera real que conviene reconocer, porque se repite en toda la infraestructura. Una biblioteca de tipos de datos replicados es, por definición, una pieza que no sabe nada del despliegue en el que va a vivir: no sabe si hay dos dispositivos o dos mil, si hay servidor, cuánto tiempo pasa un usuario sin abrir la aplicación ni qué se le puede decir cuando algo se pierde. Todas las decisiones que faltan requieren exactamente esa información. Pedirle a la biblioteca que las tome sería pedirle que adivine el producto, y la única forma en que podría hacerlo es exponiendo un parámetro que tendrías que rellenar tú, que es lo que ya hace.
// La forma que adopta el reparto en cualquier biblioteca
const artefacto = exportarSuperficial(doc, corte);
// la biblioteca sabe: como serializar, que descartar dado un corte, como verificar
// tu tienes que saber: cual es el corte, quien queda fuera y que le dices
if (!puedeIntegrar(entrante, artefacto.corte)) {
aplicarPolitica(entrante); // rechazar, reconstruir o rescatar
}
Las bibliotecas más recientes han empezado a ofrecer una cuarta pieza que se sitúa justo en la frontera: la instantánea superficial, análoga al clon superficial de un sistema de control de versiones. Consiste en exportar un documento a partir de un punto dado, sin la historia anterior, produciendo un artefacto que sirve perfectamente para arrancar un dispositivo nuevo y que no sirve para reconciliar con uno antiguo. Es exactamente la poda de la lección tercera, empaquetada como operación de exportación, y su aparición es la señal de que el ecosistema ha reconocido el problema. Fíjate en lo que sigue sin resolver: la biblioteca te da el corte como parámetro. Elegirlo sigue siendo tuyo.
Que la analogía elegida sea el clon superficial no es casual y merece explotarse, porque un sistema de control de versiones resolvió hace años el mismo conjunto de problemas con un vocabulario que ya conoces. Tiene objetos direccionados por contenido, tiene empaquetado y recolección, tiene clones sin historia completa y tiene un comportamiento definido para cuando alguien intenta empujar cambios basados en algo que ya no existe. La diferencia importante, y la que impide copiar la solución entera, es que allí hay un repositorio de referencia con el que todos comparan y aquí no lo hay, lo cual devuelve el problema a la lección segunda por el mismo camino que todo lo demás en este nivel.
Por qué acaba apareciendo un servidor
Aquí se junta todo lo anterior y se explica un fenómeno que desconcierta a quien llega al ecosistema por el discurso: casi todos los productos local-first serios tienen un componente de servidor, y sus autores lo defienden sin considerarlo una traición. La compactación es una de las razones principales, y probablemente la menos citada frente a la autenticación o los permisos.
El razonamiento es corto. Compactar con seguridad exige un censo de réplicas y noticias recientes de todas ellas. Un servicio siempre encendido proporciona las dos cosas sin esfuerzo: registra quién se ha unido, recuerda hasta dónde ha leído cada uno y puede afirmar con autoridad que un tramo del pasado ya lo ha visto todo el mundo que cuenta. No decide el contenido, no ordena las escrituras y no está en la ruta crítica de la edición, que sigue siendo local e instantánea. Lo único que hace es ser el testigo que el modelo puro no puede tener, y con ese testigo la frontera de estabilidad pasa de incalculable a trivial.
Merece la pena notar que este servidor resuelve de golpe varias cosas que se habían presentado como problemas independientes a lo largo del track, y esa coincidencia explica por qué aparece tan pronto en la vida de cualquier producto. Es el censo que la compactación necesita, es el punto de encuentro que evita que dos dispositivos tengan que estar encendidos a la vez para sincronizar, es el archivo que permite recuperar un dispositivo perdido y es el sitio natural donde poner los permisos. Cuatro necesidades distintas convergen en la misma pieza, de modo que la pregunta práctica en la mayoría de los equipos no es si tendrán servidor sino cuál de las cuatro se lo hará construir primero.
flowchart TB P[modelo puro entre pares] --> F[hace falta el censo de replicas] F --> X[la frontera de estabilidad no avanza nunca] X --> D[el estado crece sin limite] D --> S[aparece un servicio siempre encendido] S --> C[lleva el censo y la marca de agua] C --> K[el corte se puede declarar con seguridad] K --> L[la escritura sigue siendo local e instantanea] style X fill:#f38ba8,color:#11111b style K fill:#a6e3a1,color:#11111b
Conviene precisar qué tipo de servidor es este, porque la palabra arrastra connotaciones que no corresponden. No es el árbitro que los primeros capítulos retiraron: no valida escrituras, no resuelve conflictos y su caída no impide trabajar. Es un participante más con dos privilegios operativos, estar siempre encendido y no perder su estado, y esos dos privilegios bastan para desatascar la única pieza del sistema que necesitaba información global. Las capas de sincronización comerciales que han aparecido en los últimos años se organizan casi todas alrededor de esta idea, con nombres distintos para la misma marca de agua.
Hay además un beneficio secundario que rara vez se menciona y que en la práctica pesa mucho. Un testigo permanente no solo permite calcular la frontera: permite conservar, él solo, la historia que los demás descartan. Un dispositivo que reaparece por debajo del corte puede reconciliar contra el servidor aunque ninguna otra réplica tenga ya el pasado necesario, y eso convierte el escenario de la lección anterior de irreversible en meramente lento. La política de reaparición sigue existiendo, porque el servidor también acabará podando algún día, pero el plazo pasa a ser una decisión de coste de almacenamiento en un solo sitio en lugar de una restricción impuesta por el dispositivo más olvidadizo de la flota.
Conviene además distinguir dos formas muy distintas de tener servidor, porque se confunden con facilidad y solo una es compatible con lo que este track defiende. En la primera, el servidor es el origen de la verdad y los clientes son cachés que se adelantan; si se cae, se puede seguir leyendo y a veces escribiendo en local, pero el estado de un cliente no es autónomo y su reconciliación consiste en aceptar lo que el servidor diga. En la segunda, el servidor es una réplica más que resulta estar siempre encendida; su estado no tiene privilegio semántico alguno y si se pierde por completo, cualquier cliente puede reconstruirlo. La diferencia no se nota en el día a día y decide todo lo demás.
Conviene ser explícito con el coste de esta decisión para no venderla como gratuita. Un testigo permanente es infraestructura que hay que operar, pagar y proteger, y sobre todo es un punto donde vuelve a existir alguien que ve todos los documentos, lo cual tiene consecuencias directas sobre las promesas de privacidad de los primeros capítulos. Se puede mitigar cifrando el contenido de extremo a extremo y dejando que el servidor solo vea identificadores y vectores, que es exactamente lo que hacen los diseños más cuidadosos, y esa mitigación funciona bien para el censo y mucho peor para el rescate, porque un servidor que no puede leer no puede reconstruir nada por ti.
Al elegir biblioteca es habitual apoyarse en comparativas de rendimiento, y conviene saber qué miden y qué no. El banco de pruebas dominante en este terreno consiste en reproducir la traza de edición de un documento real, medir el tiempo de aplicar todas las operaciones, la memoria resultante y el tamaño serializado. Es una medida excelente de la calidad de la codificación y no dice absolutamente nada de lo que trata este nivel, porque es una traza de un solo autor sin dispositivos rezagados, sin cortes y sin nadie que reaparezca. Una biblioteca puede ganar todas esas cifras por un margen amplio y no ofrecerte ninguna vía para dejar de crecer. Si la compactación te importa, las preguntas que discriminan son otras y hay que buscarlas en la documentación y no en los gráficos: ¿existe una exportación que descarte historia previa a un punto?, ¿se puede comprobar que dos réplicas compactaron al mismo estado sin comparar los estados enteros?, ¿qué ocurre exactamente al aplicar una operación cuyas dependencias faltan, un error, un silencio o una excepción recuperable?
Qué preguntar antes de comprometerte
De todo el nivel se destila una lista corta de preguntas que conviene responder antes de adoptar una biblioteca, porque las cuatro son difíciles de cambiar después. Si la historia es contenido observable de tu producto, la primera familia queda descartada y con ella la mayor parte de la compacidad. Si no lo es, activar la recolección desde el principio ahorra años de crecimiento. Si vas a necesitar podar de verdad, necesitas una exportación superficial y una identidad por contenido con la que verificar. Y si no puedes garantizar un testigo siempre encendido, tu política de reaparición va a ejecutarse con frecuencia y tiene que estar diseñada, no improvisada.
Conviene hacer esas cuatro preguntas en el orden en que están, porque cada una condiciona la siguiente y responderlas al revés produce evaluaciones interminables. La primera es de dominio y no admite discusión técnica: o la historia es contenido de tu producto o no lo es. La segunda es una consecuencia mecánica de la primera. La tercera solo tiene sentido si ya sabes que vas a podar. Y la cuarta es la única que depende de tu operación y no de tu dominio, de modo que es también la única que puede cambiar más adelante sin obligar a reescribir nada.
Hay una quinta pregunta menos evidente que merece incluirse porque decide cuánto duele equivocarse en las otras cuatro: qué tan portable es tu estado entre bibliotecas. Casi ninguna ofrece una migración a otra familia, de modo que la elección inicial tiende a ser definitiva en la práctica aunque nada la declare así. La forma de recuperar algo de margen es no dejar que el tipo de dato de la biblioteca sea también tu formato de archivo: mantener una representación propia y explícita del contenido, exportable e independiente, aunque sea redundante. Cuesta trabajo, parece innecesario mientras todo va bien, y es lo único que convierte un cambio de biblioteca en un proyecto acotado en lugar de en una reescritura.
Conviene terminar situando este nivel en el conjunto del track, porque marca una transición. Hasta aquí las preguntas tenían respuesta técnica: cómo converger, cómo ordenar sin reloj común, cómo representar una secuencia, cómo evitar que el texto se entrelace. Todas ellas admitían un algoritmo mejor que otro y un criterio objetivo para decidirlo. La compactación es la primera que no lo admite, y no porque falte investigación sino porque su respuesta correcta depende de a quién estás dispuesto a dejar atrás. Los niveles siguientes construirán herramientas mejores para ejecutar esa decisión, y ninguna de ellas la tomará por ti.
Conviene cerrar el nivel entendiendo qué se ha aprendido de verdad sobre la arquitectura, porque es fácil leer la sección anterior como una capitulación y no lo es. Local-first hizo una promesa muy precisa y la ha cumplido: retiró la coordinación de la ruta de escritura. Escribir es local, es instantáneo, funciona sin red y no depende de que nadie más esté disponible. Ese resultado es sólido, está demostrado y es la razón entera por la que existe el campo. Lo que nunca prometió, y lo que treinta y tantos niveles de este track han ido revelando poco a poco, es que la coordinación pudiera desaparecer del sistema entero. Solo cambió de sitio. Se fue de la escritura y reapareció en el mantenimiento, que es exactamente el lugar donde uno querría tenerla si pudiera elegir: fuera de la ruta crítica, sin nadie esperando delante de una pantalla, ejecutable en segundo plano, reintentable y aplazable durante semanas sin que nada se rompa. Un servidor de compactación no es el árbitro que se retiró volviendo por la puerta de atrás; es un participante privilegiado en el único proceso que quedaba necesitando información global, y su caída no impide trabajar sino solo limpiar. Esa distinción no es un tecnicismo, es toda la diferencia entre una arquitectura y otra, y se comprueba con una prueba muy simple que conviene aplicar a cualquier sistema que se anuncie como local-first: apaga el servidor y mira si alguien deja de poder escribir. Si dejan, es una aplicación en la nube con caché optimista y el nombre está mal puesto. Si no dejan, y lo único que se detiene es la recolección de historia, entonces has llegado al punto de equilibrio real de esta disciplina, que no es la ausencia total de infraestructura sino la eliminación de la infraestructura del camino del usuario. Los sistemas que perduran no son los que consiguieron no tener servidor: son los que consiguieron que su servidor pudiera estar apagado durante un mes sin que nadie se enterase salvo por el tamaño de los ficheros.
- Busca en su documentación si existe una exportación que descarte historia anterior a un punto. Si no la encuentras, escribe cómo piensas dejar de crecer sin ella.
- Comprueba si el estado tiene una identidad derivada de su contenido con la que dos réplicas puedan verificar que compactaron igual.
- Aplica una operación cuyas dependencias no estén presentes y anota el comportamiento exacto. Ese comportamiento es la mitad de tu política de reaparición.
- Enumera qué funciones de tu producto dependen de la historia. Si la lista está vacía, activa la recolección hoy y mide el ahorro a los treinta días.
- Apaga tu componente de servidor durante una semana en un entorno de pruebas y anota qué deja de funcionar. Si alguien no puede escribir, tu arquitectura no es la que crees.
- Escribe media página con tu política completa de compactación: quién declara el corte, con qué criterio, cada cuánto y qué le pasa al que vuelve. Es el entregable real de este nivel.