CRDT (Tipos de datos replicados sin conflicto)
# CRDT (Tipos de datos replicados sin conflicto)

Los tipos de datos replicados sin conflicto (CRDT) son un tipo de dato abstracto que está diseñado para poder replicarse en múltiples máquinas o procesos. Estos tipos de datos se utilizan en sistemas de replicación que siguen una estrategia optimista.

Utilizar estos tipos de datos simplifica los sistemas de almacenamiento de datos distribuidos, pues asegura autonomía y consistencia ya que las réplicas pueden actualizarse sin necesidad de sincronizarse con otras réplicas, de forma independiente y concurrente.

Al ser un tipo de datos abstracto, CRDT necesita una interfaz bien definida con la cual interactuarán las aplicaciones que quieran comunicarse con los tipos de datos replicados sin conflicto.

El concepto de tipos de datos replicados sin conflicto fue expuesto con alto grado de detalle en 2011 por Marc Shapiro, Nuno Preguiça, Carlos Baquero y Marek Zawirski. Aunque anteriormente algunos de estos autores ya habían publicado investigaciones en las que describían o hacían referencia a este tipo de datos.

## Propagación de actualizaciones en CRDT

Cuando se produce una modificación en algún CRDT este cambio se propaga de forma asíncrona al resto de las réplicas. Hay dos mecanismos para enfocar esta propagación: Peer to peer o siguiendo un modelo cliente servidor. En el primer caso el par modificado es el responsable de actualizar cada uno de sus pares individualmente, mientras que en el segundo caso un CRDT es designado como réplica primaria, será el responsable de recibir todas las notificaciones de cambios en las réplicas y propagará estas modificaciones al resto de réplicas.

## Tipos de CRDTs

Los tipos de datos replicados libres de conflicto se pueden clasificar en dos estrategias diferentes en función del punto de vista que se siga para conseguir la consistencia. Estas dos estrategias son los CRDTs basados en estado y los CRDTs basados en operación.

Ambos tipos de CRDT pueden garantizar la consistencia y son intercambiables, la diferencia radica en la sencillez de la implementación y el coste de la comunicación. Los CRDTs basados en estado son menos complejos en cuanto a la implementación, pero el coste del envío de los estados puede ser mayor. Por otro lado, la implementación de los CRDTs basados en operación puede ser más compleja pero el coste de la comunicación es menor.

### CRDTs basados en estado (CvRDTs)

Los CRDTs basados en estado también son llamados CvRDTs (tipos de datos replicados convergentes).

En los CRDTs basados en estado las actualizaciones producen modificaciones únicamente sobre el estado de una de las réplicas. Eventualmente, las réplicas envían su estado a parte o a todas las demás réplicas. Cuando una réplica recibe el estado de otra réplica, es responsable de combinarlo con su propio estado. Al cabo de un tiempo todas las réplicas alcanzarían un estado consistente.

Sin embargo, para que las réplicas puedan alcanzar este estado consistente, el método de combinación debe cumplir ciertas propiedades. La primera de ellas es que la operación de combinación debe ser conmutativa, es decir, el estado resultante de combinar el estado de dos réplicas debe ser igual independientemente de cuál sea la réplica receptora y cuál sea la emisora. La segunda de ellas es que debe ser asociativa, es decir, el estado resultante de combinar el estado de tres réplicas debe ser igual independientemente del orden en el que se envíen los estados. Por último, debe ser idempotente, es decir, el estado resultante de combinar el estado de una réplica consigo mismo debe ser el propio estado. Una condición suficiente para que se cumplan estas tres propiedades es que la operación combinación se corresponda con la operación supremo del semirretículo superior formado por el conjunto de estados y una función de ordenación parcial entre ellos.

Una mejora de los CRDTs basados en estado son los basados en delta, en los que se envía solamente parte del estado cuando el cambio solo afecta a una parte de la réplica. El envío del estado completo es necesario solamente en la primera sincronización, de forma que se reduce la cantidad de información que es necesario enviar.

### CRDTs basados en operación (CmRDTs)

Los CRDTs basados en operación también son llamados CmRDTs (tipos de datos replicados conmutativos).

A diferencia de los CRDTs basados en estado, los CRDTs basados en operación no cuentan con una operación combinación. En su lugar, cada vez que se realiza una operación en una réplica se envía una función de actualización al resto con un mecanismo de envío con ordenación causal. Esta función de actualización es generada inmediatamente antes de ejecutar la actualización en la réplica originaria de la operación. Es muy importante que esto sea así para garantizar la dependencia causal de las actualizaciones. Cuando se tienen operaciones concurrentes en distintas réplicas, el orden de aplicación de las actualizaciones debería no importar. Por ello, la condición suficiente para que un tipo de dato sea un CRDT basado en operación es que cuente con una función “actualizar” que sea conmutativa para cualquier par de operaciones que puedan ser ejecutadas concurrentemente en distintas réplicas.

Existe también una variante de los CRDTs basados en operación que son los CRDTs basados en operaciones puras, que reducen el tamaño de la información enviada pues solamente se envían la operación y sus potenciales argumentos.

## Implementaciones más conocidas de CRDTs

Hay 3 categorías principales: contadores (cuya función es incrementar y decrementar un único valor), conjuntos (cuya función es añadir y eliminar elementos a un conjunto) y registros (almacenan un único valor que puede actualizarse).

### Contadores

Los contadores son un tipo de CvRDT cuya función es permitir leer y modificar un valor de manera concurrente en diferentes máquinas. Se basan en que el método de combinación es asociativo, conmutativo e idempotente.

Para implementar un contador es necesario mantener un estado interno y que todos los estados posibles puedan ser ordenados por alguna relación binaria.

**G-Counter (Grow-Only-Counter)**

Su característica principal es que el contador sólo se incrementa, no se decrementa.

La representación del estado interno es una matriz cuyo tamaño es el número total de nodos y cada nodo tiene una identificación entre 0 y número de nodos - 1.

El siguiente pseudocódigo muestra de una forma sencilla la lógica de este contador y las funciones necesarias para implementarlo:

La función Consulta devuelve la suma de todos los elementos de la matriz de estado.

La función Incrementar incrementa el valor del elemento de la matriz de estados en la posición correspondiente al identificador del nodo.

La función Combinar compara elemento a elemento dos matrices de estado, escogiendo en cada iteración el mayor de los dos.

**PN-Counter (Positive Negative Counter)**

A diferencia del anterior este contador también puede decrementarse.

La representación del estado interno se consigue a través de dos G-Counters, uno para representar los incrementos (comúnmente llamado p) y otro los decrementos (comúnmente llamado n). De forma que p y n serán dos matrices de tamaño n, con n el número de nodos.

El siguiente pseudocódigo muestra de una forma sencilla la lógica de este contador y las funciones necesarias para implementarlo:

La función Consulta devuelve la resta de las sumas de las dos matrices de estado.

La función Incrementar incrementa el valor del elemento de la matriz de estados p (matriz que representa los incrementos) en la posición correspondiente al identificador del nodo.

La función Decrementar incrementa el valor del elemento de la matriz de estados n (matriz que representa los decrementos) en la posición correspondiente al identificador del nodo.

La función Combinar compara elemento a elemento dos matrices de estado p, escogiendo en cada iteración el mayor de los dos. Realiza la misma operación con dos matrices de estado n.

### Sets

Los conjuntos (sets) tienen dos operaciones características : añadir (add) y eliminar (remove).

**G-Set (Grew Only Set)**

La característica principal de este conjunto es que sólo se le añaden elementos, no se eliminan.

El estado interno está representado por un conjunto (set) inicialmente vacío.

El siguiente pseudocódigo muestra de una forma sencilla la lógica de este conjunto y las funciones necesarias para implementarlo:

La función Consulta devuelve el conjunto.

La función Añadir añade un elemento al conjunto.

La función Combinar realiza la operación de unión sobre dos conjuntos, el resultado de esta operación sobrescribe el conjunto del nodo en el que se realiza.

**2P-Set (2 Phase Set)**

En un conjunto 2P-Set, a diferencia del caso anterior, sí se pueden eliminar elementos aunque no permite reinserciones.

En este caso el estado interno está representado por dos G-SET, uno para representar el conjunto de valores añadidos al 2P-SET (comúnmente llamado a (add)) y otro para representar el conjunto de valores eliminados del 2P-SET (comúnmente llamado r (remove) o tombstone).

El siguiente pseudocódigo muestra de una forma sencilla la lógica de este conjunto y las funciones necesarias para implementarlo:

La función Consulta devuelve el resultado de aplicar la operación diferencia al conjunto a y al conjunto r.

La función Añadir añade un elemento al conjunto a (conjunto que representa los elementos añadidos al conjunto global del nodo 2P-SET).

La función Añadir añade un elemento al conjunto r (conjunto que representa los elementos eliminados del conjunto global del nodo 2P-SET).

La función Combinar realiza la operación de unión sobre dos conjuntos a, el resultado de esta operación sobrescribe el conjunto a del nodo en el que se realiza. Realiza la misma operación para dos conjuntos r.

**LWW-element Set**

Introduce un orden total en el conjunto, utilizando marcas de tiempo.

Al igual que el anterior, necesita 2 conjuntos, uno para representar los elementos añadidos y otro para representar los elementos eliminados del conjunto.

Las operaciones también son muy similares al 2P-SET, pero se ha de tener en cuenta que en LWW-element Set cuando se añade un elemento al conjunto también se añade su marca temporal.

Si un elemento está en el conjunto de añadidos y no se encuentra en el conjunto de eliminados, o si se encuentra tiene una marca temporal menos reciente que en el conjunto de añadidos, entonces ese elemento pertenece al conjunto.

A diferencia del 2P-SET el LWW-element Set sí permite reinserciones.

**PN-Set**

La lógica de este conjunto se basa en añadir un contador para cada elemento. Este contador se incrementa con la operación Añadir y se decrementa con la operación Eliminar. Si el contador del elemento es positivo entonces ese elemento pertenece al conjunto.

**Observe-Remove Set, OR-Set, Add-Win Set**

Los conjuntos de OR-Set son conjuntos LWW-element Set pero en lugar de marcas de tiempo utilizan etiquetas únicas.

Para cada elemento se mantienen dos listas de etiquetas, una que representa la acciones de añadir (lista de etiquetas añadidas) y otra que representa las acciones de eliminar(también llamada tombstone o lista de etiquetas eliminadas). La operación de añadir elemento al OR-Set genera un identificador único que es añadido a la lista de etiquetas añadidas. Los elementos se eliminan del OR-Set cuando todas las etiquetas que figuran en su lista de etiquetas añadidas también se encuentran en la lista de etiquetas eliminadas.

La operación de combinar dos OR-Set se basa en realizar la operación de unión sobre las dos listas de etiquetas añadidas y sobre las dos listas de etiquetas eliminadas de cada elemento.

Un elemento es miembro del conjunto si la diferencia entre la lista de etiquetas añadidas y vacías es no vacío, es decir, si no todas las etiquetas de la lista de etiquetas añadidas figuran en la lista de etiquetas eliminadas.

Se debe tener cuidado con el crecimiento de la lista de etiquetas eliminadas, una posible optimización es utilizar un vector de marcas de tiempo en cada réplica.

El nombre de Add-Win Set se debe a que, en el caso de quitar y añadir simultáneamente el mismo elemento, la acción de añadir tiene prioridad.

**Remove-wins set (RWSet)**

Es un conjunto OR-Set igual que el anterior, la diferencia: en el caso de quitar y añadir simultáneamente el mismo elemento, en este caso la acción de eliminar tiene prioridad.

### Registros

Es un CRDT que únicamente guarda un valor. Tiene dos operaciones: consultar y actualizar, la primera devuelve el valor y la segunda lo guarda; esta segunda operación no es conmutativa. La solución a esto:

**LWW-Registro (Last Write Wins Register)**

Se asocia a cada nuevo valor una marca de tiempo. En la actualización, si la marca de tiempo del nuevo valor es más reciente (más mayor) que la marca de tiempo del actual valor de la réplica, entonces el valor se actualiza.

El siguiente pseudocódigo muestra de una forma sencilla la lógica de este registro y las funciones necesarias para implementarlo:

La función Consultar devuelve el valor almacenado.

La función actualizar comprueba si la marca temporal del nuevo valor es más reciente que la que posee el valor actual, si es así el valor y la marca de tiempo se actualizan.

## Usos en la industria

Los CRDTs son utilizados en numerosas aplicaciones:

* Microsoft utiliza CRDTs en su base de datos Microsoft Azure CosmosDB para resolver conflictos entre distintos valores.
* Redis Enterprise los utiliza para replicar la base de datos de distintos centros de datos en distintas localizaciones geográficas.
* Netflix utiliza los CRDTs internamente en su sistema de almacenamiento Dynomite.
* La base de datos interna de Facebook, Apollo database, soporta almacenamiento CRDT.
* PayPal y League of Legends utilizan CRDTs de forma interna.
* Los sistemas de navegación GPS TomTom utilizan CRDTs para sincronizar los datos de los usuarios entre múltiples dispositivos.
* PeerPad es un editor de texto colaborativo basado en IPFS y CRDTs.
* La aplicación de Notas de Apple para iOS utiliza CRDTs para la sincronización entre diferentes dispositivos.
* La aplicación de diseño gráfico Figma utiliza CRDTs para hacer posible su funcionalidad colaborativa.
