wandres.dev
RECONCILIACIÓN EFICIENTE · saber qué falta sin mandarlo todo

Elegir el mecanismo: tamaño, réplicas, frecuencia y transporte

Ninguno de los tres mecanismos gana siempre, y la elección correcta se deduce de cuatro medidas del sistema real, con la advertencia de que todo diseño maduro acaba componiendo varios en capas.

⏱ 23 min

Las tres lecciones anteriores han presentado tres mecanismos que resuelven el mismo problema con propiedades incompatibles: la resta de vectores es exacta y de una sola ronda pero hipoteca metadatos y retención del historial; el descenso por un árbol de resúmenes no recuerda nada del interlocutor y funciona contra desconocidos pero paga una ronda por nivel; los resúmenes probabilísticos caben en un paquete y a veces callan un dato. Presentarlos así, uno detrás de otro, produce inevitablemente la pregunta equivocada, que es cuál es el mejor. No existe tal cosa, y quien busca esa respuesta acaba eligiendo por familiaridad o por moda y descubriendo el error cuando el sistema ya tiene usuarios y el mecanismo entra en el régimen donde degenera. La pregunta correcta se contesta con cuatro números que cualquiera puede medir en su propio sistema en una tarde, y su respuesta casi nunca es un mecanismo sino una combinación con un camino rápido para el caso frecuente y una vía de escape para el caso raro. Esta lección cierra el nivel construyendo ese criterio, mostrando qué eligió cada sistema real del track y por qué, y nombrando los errores de elección concretos que se repiten una y otra vez en despliegues que funcionaban perfectamente en las pruebas.

🎯 Al terminar esta lección sabrás
  • Medir los cuatro parámetros que determinan la elección y saber cuál domina en cada caso.
  • Traducir latencia, ancho de banda y consumo de radio a un criterio comparable entre mecanismos.
  • Reconocer el régimen en que cada mecanismo degenera y diseñar la vía de escape correspondiente.
  • Entender la composición en capas que usan los sistemas reales del track y por qué eligieron distinto.
  • Detectar los errores de elección más frecuentes antes de que lleguen a producción.

Los cuatro parámetros que deciden

El primero es el tamaño del estado sincronizable, contado en número de elementos y en bytes totales. Es el parámetro que decide si la conversación tiene sentido: por debajo de unos cientos de kilobytes, mandarlo todo gana a cualquier mecanismo de este nivel en simplicidad, en rondas y muchas veces también en bytes, porque el resumen y su protocolo no son gratis. La pregunta relevante no es cuánto ocupa hoy sino cuánto ocupará dentro de tres años de uso continuado, porque este parámetro solo crece.

El segundo es el número de participantes que escriben, y hay que contarlo con precisión y con mala intención. No es el número de usuarios ni el de dispositivos: es el número de identificadores de actor distintos que aparecerán en los metadatos a lo largo de la vida del documento, contando pestañas, reinstalaciones, sesiones anónimas y todo lo que genere un identificador nuevo. Es el parámetro que decide si la contabilidad por vectores es viable, y el que más se subestima porque en desarrollo siempre vale dos.

El tercero es la frecuencia de sincronización, que determina la distribución del tamaño de la diferencia. Sincronizar cada segundo significa una diferencia casi siempre vacía y ocasionalmente de dos o tres elementos; sincronizar una vez al día significa una diferencia de cientos y una cola larga de dispositivos que vuelven tras semanas. Lo que hay que medir no es la media sino el histograma completo, porque el mecanismo se elige por el caso frecuente y la vía de escape se diseña por la cola.

El cuarto es el transporte, y se descompone en cuatro cifras que conviene anotar por separado: la latencia de ida y vuelta, el coste por byte, si el canal pierde y reordena, y si el interlocutor es de confianza. La primera decide cuántas rondas se pueden gastar, la segunda cuántos bytes, la tercera si hacen falta listas de excepciones y la cuarta descarta de golpe todos los mecanismos que no permiten verificar lo recibido.

Lo que domina segun el enlace

  centro de datos ....... 0.5 ms de ida y vuelta   las rondas son casi gratis
  fibra domestica ....... 20 ms                     equilibrio entre rondas y bytes
  movil en cobertura .... 60 a 150 ms               cada ronda cuesta mas que 10 KB
  movil con radio dormida  1 a 2 s de despertar     minimizar numero de intercambios

La tabla de decisión

Mecanismo          bytes        rondas    metadatos    exacto   verificable
  todo entero      n            1         ninguno      si       no
  vectores         d + R        1         O de R       si       no
  arbol de hashes  d log n      log n     ninguno      si       si
  filtro clasico   n entre 25   1 mas     ninguno      no       no
  filtro invertible d           1 o 2     ninguno      casi     no

Leída con los cuatro parámetros delante, la tabla se convierte en un criterio. Con estado pequeño gana la primera fila y no hay más que discutir. Con población de actores acotada, historial conservado y sincronización frecuente gana la segunda, porque es exacta, cabe en una ronda y su coste en metadatos es despreciable cuando el número de réplicas se cuenta con los dedos. Con estado grande, población abierta, historial podado o interlocutor no fiable gana la tercera, que es la única que no exige recordar nada ni confiar en nadie. Y cuando la latencia manda, la diferencia es pequeña y el conjunto es plano y sin estructura natural de árbol, ganan las dos últimas, con la obligación de cerrar con una comprobación exacta.

flowchart TB
A[mide el estado] -->|cabe en un mensaje| T[mandalo entero]
A -->|no cabe| B[cuenta los actores]
B -->|pocos y con historial intacto| V[resta de vectores]
B -->|muchos o historial podado| C[hay confianza en el par]
C -->|no| M[arbol de hashes]
C -->|si| L[la latencia es alta]
L -->|si| F[filtro invertible con estimador]
L -->|no| M
style T fill:#a6e3a1,color:#11111b
style V fill:#89b4fa,color:#11111b
style M fill:#f9e2af,color:#11111b
style F fill:#cba6f7,color:#11111b
ℹ️
Las rondas se pagan en segundos y en batería, no en bytes

Merece la pena hacer la aritmética una vez para no volver a discutirlo. Un descenso de doce niveles sobre un enlace móvil con ciento veinte milisegundos de ida y vuelta cuesta un segundo y medio de espera pura, con independencia de que los doce mensajes sumen tres kilobytes. Si además la radio del dispositivo estaba dormida, el primer intercambio añade un segundo largo y cada intercambio posterior mantiene la radio encendida, que es donde se va la batería de verdad en un cliente móvil. Por eso en ese contexto un mensaje de doscientos kilobytes en una sola ronda puede ser una elección mejor que tres kilobytes en doce, y por eso los sistemas que se envían el árbol entero de profundidad fija en un solo mensaje no están siendo perezosos sino comprando la moneda correcta.

Componer en capas en lugar de elegir uno solo

Ningún sistema serio de los que el track ha estudiado usa un solo mecanismo, y ver qué eligió cada uno es más instructivo que cualquier recomendación general. Las bibliotecas de edición colaborativa basadas en índices por actor se sincronizan intercambiando el vector de estado, porque su población de actores es pequeña, su historial vive entero en memoria y su sincronización es continua: la resta es imbatible ahí. Las bibliotecas que identifican cada cambio por su hash y forman un grafo de cambios no pueden restar nada, porque no hay contadores densos que restar, y por eso su protocolo envía un filtro probabilístico sobre los hashes que tiene junto con sus cabezas, acepta los falsos positivos y los resuelve con una ronda adicional.

Los almacenes de clave y valor distribuidos llevan tres capas simultáneas y cada una cubre un régimen distinto de divergencia. Para la escritura que falla en el momento, una entrega diferida que reintenta en segundos. Para la divergencia detectada al leer, una reparación puntual sobre la clave concreta. Y para la divergencia acumulada que ninguna de las dos vio, una reparación periódica por árboles de resúmenes sobre rangos enteros del espacio de claves. Y el control de versiones distribuido negocia primero un punto común explorando el grafo de revisiones, y solo después construye el paquete con lo que falta.

// Un enrutador de mecanismo es mas util que una eleccion fija
function elegirMecanismo({ bytesEstado, actores, historialIntacto, parFiable, latencia }) {
  if (bytesEstado < 256 * 1024) return "estado entero";
  if (actores < 64 && historialIntacto) return "resta de vectores";
  if (!parFiable) return "arbol de hashes";
  return latencia > 80 ? "filtro invertible con estimador" : "arbol de hashes";
}

El patrón que se repite en los tres casos es el mismo y es el que conviene llevarse: un camino rápido que cubre la inmensa mayoría de las sincronizaciones con coste casi nulo, una vía estructural para cuando el camino rápido no se puede servir, y una transferencia completa como último recurso para el arranque en frío y para la divergencia masiva. Lo que distingue a un diseño maduro no es haber elegido bien el camino rápido sino haber implementado la vía de escape y haber definido con precisión el evento que la dispara.

🚀

Camino rápido

Cubre el caso frecuente con un mensaje diminuto y una ronda, y es el que se optimiza porque es el que se ejecuta siempre.

🪜

Vía estructural

Entra cuando el camino rápido no puede servirse: historial podado, par desconocido o metadatos desbordados.

🧱

Transferencia completa

Sigue siendo la respuesta correcta en el arranque en frío y cuando la divergencia se acerca al tamaño del estado.

Cierre exacto

Toda sincronización termina comparando un resumen que confirme la igualdad, sea cual sea el camino que se haya tomado.

Los errores de elección que se ven en producción

El primero es montar un árbol de resúmenes sobre un estado que cabe en un mensaje. Se paga el mantenimiento del árbol en cada escritura, se pagan varias rondas para descubrir la diferencia y se acaba transfiriendo casi lo mismo que habría costado mandarlo todo. Es el error de quien elige por sofisticación y no por medida, y su síntoma es un protocolo elegante con una latencia peor que la del enfoque ingenuo.

El segundo es adoptar vectores de versiones sin acotar quién es actor. Cada recarga de página, cada pestaña nueva y cada reinstalación añaden una entrada permanente, y como las entradas no se pueden quitar sin demostrar estabilidad causal, el saludo crece de forma monótona hasta pesar más que el documento. Cuando alguien se da cuenta ya hay millones de documentos con vectores enormes y arreglarlo es una migración.

El tercero es dimensionar un filtro con un número de elementos supuesto y no comprobar nada al final. El filtro se satura, la tasa real de falsos positivos se dispara muy por encima de la nominal y el sistema empieza a perder elementos de forma silenciosa y proporcional al crecimiento del conjunto. Es el peor de los errores de este nivel porque no genera ninguna señal.

⚠️
El dispositivo que vuelve después de meses es el caso que hay que diseñar primero

Los tres mecanismos se comportan bien con divergencias pequeñas y todos tienen problemas con el dispositivo que reaparece tras una ausencia larga: el vector apunta a un historial que ya se podó, el descenso encuentra divergencia en casi todas las ramas y paga muchas rondas para descubrirlo, y el filtro invertible se despeña porque la diferencia supera cualquier capacidad razonable. Como ese caso es raro, tiende a diseñarse el último o a no diseñarse en absoluto, y como es el que más daño hace cuando falla, conviene invertir el orden: decide primero qué ocurre a los seis meses de ausencia, comprueba que el sistema detecta la situación de forma explícita en lugar de intentar el camino rápido y fracasar en silencio, y solo entonces optimiza el caso frecuente.

Un protocolo de sincronización es una apuesta sobre la forma de la divergencia, y conviene escribirla antes de codificarla

Lo que hay debajo de las cuatro lecciones anteriores, y lo que se lleva uno de este nivel aunque olvide los algoritmos, es que cada mecanismo de reconciliación incorpora una hipótesis sobre cómo divergen las réplicas en tu sistema concreto, y que su rendimiento no es una propiedad del algoritmo sino del acierto de esa hipótesis. La resta de vectores apuesta a que hay pocos escritores y a que la historia se conserva. El árbol apuesta a que la divergencia es pequeña y está concentrada. El filtro apuesta a que la diferencia cabe en el dimensionamiento elegido. Ninguna de esas apuestas está escrita en el código, todas se toman de forma implícita al elegir una biblioteca, y todas se pueden falsar con datos que cualquiera puede recoger en una semana de uso real. De ahí la práctica concreta que vale más que cualquier preferencia técnica: instrumenta la divergencia antes de elegir el mecanismo y sigue instrumentándola después, guardando el histograma del tamaño de la diferencia, el número de actores por documento, el tiempo desde la última sincronización de cada réplica y el número de rondas por intercambio; con esos cuatro histogramas la elección se vuelve una lectura y sin ellos es una opinión. Y hay una segunda mitad de la regla, más difícil de aceptar porque obliga a escribir código que casi nunca se ejecuta: el valor de un protocolo de sincronización no está en lo rápido que va cuando la hipótesis se cumple sino en lo que hace cuando deja de cumplirse, porque la hipótesis siempre acaba dejando de cumplirse —el estado crece, aparecen actores nuevos, alguien vuelve tras un año, la red empeora— y en ese momento solo hay dos posibilidades: un sistema que detecta la situación, la nombra y cambia de camino, o un sistema que sigue intentando el camino rápido y pierde datos sin decirlo. Toda la diferencia entre una sincronización que se puede mantener durante años y una que hay que rescribir está en si esa detección se escribió el primer día.

⚔️ Convierte tu elección en una decisión medida
  1. Mide los cuatro parámetros de tu sistema real: bytes de estado, actores por documento, histograma de la diferencia y latencia del enlace.
  2. Aplica la tabla de decisión y anota el mecanismo que sale, junto con la hipótesis implícita que estás aceptando.
  3. Escribe la condición exacta que debe dispararse para abandonar el camino rápido y pasar a la vía estructural.
  4. Simula un dispositivo que vuelve tras seis meses y comprueba que el sistema lo detecta de forma explícita.
  5. Instrumenta el número de rondas por sincronización y tradúcelo a segundos con la latencia real de tus usuarios.
  6. Proyecta los cuatro parámetros a tres años de crecimiento y decide si la elección de hoy sigue siendo válida entonces.