Marcio Cunha

Sistemas Distribuidos Tolerantes a Fallos con Resolución de Conflictos CRDT

Aprenda a diseñar arquitecturas de software capaces de operar en múltiples continentes sin pérdida de datos utilizando tipos de datos replicados sin conflictos.

Marcio Cunha•4 min
También disponible en:PortuguêsEnglish
Resumen
  • La replicación de datos entre centros de datos geográficos exige decisiones estrictas en el teorema de CAP.
  • El uso de estructuras matemáticas específicas elimina la necesidad de bloqueos centralizados.
  • La consistencia eventual garantiza que todos los nodos alcancen automáticamente el mismo estado.
  • La resolución de conflictos ocurre de forma determinista directamente en la capa de datos.
  • La latencia de escritura disminuye considerablemente cuando las operaciones ocurren de forma local.

El Desafío de la Consistencia a Escala Global

Construir sistemas que funcionen alrededor del mundo sin bloquearse requiere repensar cómo guardamos la información. Cuando un usuario en Tokio y otro en São Paulo modifican el mismo archivo al mismo tiempo, las computadoras deben decidir quién tiene la razón. En la práctica, esto significa que no podemos depender de un único servidor central para validar cada clic, ya que la distancia física crea retrasos inevitables en la velocidad de la luz.

La ingeniería tradicional intenta resolver esto creando copias de los datos en varios lugares. Sin embargo, si el cable submarino que conecta Brasil con Estados Unidos se rompe, ambas mitades de la aplicación siguen funcionando por separado. Cuando la conexión regresa, las computadoras deben unir las piezas sin perder lo que cada usuario escribió en ese lapso. Este escenario de redes inestables y particiones es la prueba de fuego para cualquier arquitectura moderna de software.

El Teorema de CAP y la Elección por la Disponibilidad

Existe una regla matemática en la informática llamada Teorema de CAP, que establece que un sistema distribuido solo puede garantizar dos de tres propiedades al mismo tiempo: consistencia, disponibilidad y tolerancia a particiones. Dado que el internet del mundo real falla constantemente, la tolerancia a particiones es obligatoria. Esto nos obliga a elegir entre detener el sistema durante los fallos o aceptar que diferentes partes de la red tengan visiones temporalmente diferentes de los datos.

En la arquitectura moderna, casi siempre elegimos la disponibilidad. Esto significa que la aplicación sigue abriendo y aceptando nuevos registros incluso si el servidor principal está incomunicado. La consecuencia inevitable es que, por unos instantes, un cliente puede ver un saldo bancario desactualizado. El secreto de la ingeniería no es impedir esta divergencia, sino garantizar que los datos converjan al valor correcto tan pronto como la red se estabiliza.

Cómo Funcionan los Tipos de Datos Replicados sin Conflictos

Para resolver este rompecabezas sin bloquear el sistema, utilizamos estructuras matemáticas conocidas como CRDTs, o Tipos de Datos Replicados Libres de Conflictos. Piense en esto como un carrito de compras de supermercado donde varias personas pueden agregar productos al mismo tiempo, y al final todas las compras se suman perfectamente, sin importar quién puso qué primero. En la práctica, el CRDT dicta reglas matemáticas para fusionar la información de forma automática y predecible.

Existen dos tipos principales de estas estructuras: las basadas en operaciones y las basadas en estado. Las basadas en estado envían todo el historial o el resumen actual del documento a los demás servidores, que combinan la información utilizando una regla lógica llamada unión. Las basadas en operaciones transmiten solo la acción realizada, como agregar la letra A en la posición 5. Ambas garantizan que si todos los servidores reciben los mismos avisos, terminan exactamente con el mismo resultado final.

Ejemplo Práctico de Implementación con Contadores

Para visualizar la lógica en código, podemos observar un contador distribuido simple escrito en JavaScript. Permite que diferentes servidores aumenten el valor de forma independiente antes de sincronizar los estados finales. Este modelo evita cuellos de botella y permite la operación sin conexión en dispositivos móviles y nodos remotos.

class PNCounter {
constructor(nodeId, totalNodes) {
this.nodeId = nodeId;
this.P = new Array(totalNodes).fill(0);
this.N = new Array(totalNodes).fill(0);
}

increment() {
this.P[this.nodeId]++;
}

decrement() {
this.N[this.nodeId]++;
}

value() {
const sumP = this.P.reduce((a, b) => a + b, 0);
const sumN = this.N.reduce((a, b) => a + b, 0);
return sumP - sumN;
}

merge(remoteP, remoteN) {
for (let i = 0; i < this.P.length; i++) {
this.P[i] = Math.max(this.P[i], remoteP[i]);
this.N[i] = Math.max(this.N[i], remoteN[i]);
}
}
}

En este ejemplo, cada servidor tiene su propio espacio en las listas de incremento y decremento. Cuando ocurre la sincronización, la función de unión toma siempre el valor más alto registrado por cada nodo. Esto garantiza que no se pierda ninguna actualización, incluso si los mensajes llegan desordenados debido a la latencia de la red.

Gestión de Estado y Desafíos Operativos

A pesar de resolver la sincronización matemática, los CRDTs traen un desafío invisible: el consumo de memoria y espacio en disco. Como necesitan guardar el historial o metadatos sobre quién cambió qué para poder hacer la unión después, el tamaño de los archivos tiende a crecer con el tiempo. En la práctica, los equipos de ingeniería deben implementar rutinas de limpieza, conocidas como compactación, para descartar rastros antiguos que ya han sido confirmados por toda la red.

Otro punto crítico es el orden de las operaciones en la interfaz de usuario. Incluso si las matemáticas garantizan que los datos sean idénticos en todos los servidores, la experiencia humana puede resultar confusa si un texto borrado reaparece porque el cambio provino de un servidor más lento. Por eso, además de usar estructuras inteligentes en la base de datos, los desarrolladores deben diseñar pantallas que comuniquen claramente el estado de guardado y sincronización en tiempo real.

Consideraciones Finales

Diseñar sistemas geográficamente distribuidos exige abandonar la comodidad de las bases de datos tradicionales que bloquean todo para garantizar el orden. La adopción de estructuras matemáticas para la resolución autónoma de conflictos permite construir aplicaciones resilientes, rápidas y verdaderamente globales. La inversión en arquitecturas descentralizadas vale la pena cuando un negocio necesita operar sin interrupciones, sin importar los fallos de red o la distancia entre los usuarios.