DEV Community

Cover image for Aurora DSQL: Cómo controlar el tiempo
Miguel Angel Muñoz Sanchez for AWS Español

Posted on • Originally published at cloudhastaenlasopa.com

Aurora DSQL: Cómo controlar el tiempo

En el artículo anterior vimos cómo hemos llegado hasta Amazon Aurora DSQL y por qué necesitamos una base de datos que permita escrituras consistentes en múltiples regiones. Al final del artículo planteamos la pregunta clave: ¿Cómo ha conseguido AWS una BBDD con escritura multiregional sin romper ACID?

La respuesta está en algo que parece imposible: controlar el tiempo.

En este artículo vamos a explorar cómo AWS ha conseguido que Aurora DSQL consiga algo que parecía imposible o al menos muy complejo y es que una BBDD Postgres admita la escritura en múltiples regiones con consistencia.

En este artículo vamos a ver:

  • El problema fundamental del tiempo en bases de datos distribuidas
  • La solución de AWS: Sincronización de tiempo a escala global
  • Aurora DSQL: Arquitectura real de sincronización distribuida
  • Comparativa con Aurora PostgreSQL
  • Limitaciones y consideraciones

El Problema Fundamental: El Tiempo en Bases de Datos Distribuidas

Antes de entender cómo Aurora DSQL resuelve el problema, necesitamos comprender por qué el tiempo es tan complejo en sistemas distribuidos.

El Teorema CAP o Teorema Brewer

En 2000, Eric Brewer formuló el Teorema CAP que establece que en un sistema distribuido solo puedes garantizar dos de estas tres propiedades:

  • Consistency (Consistencia): Todos los nodos ven los mismos datos al mismo tiempo
  • Availability (Disponibilidad): El sistema sigue funcionando aunque fallen algunos nodos
  • Partition tolerance (Tolerancia a particiones): El sistema continúa operando aunque se pierda comunicación entre nodos

Cualquier base de datos como Aurora PostgreSQL o Aurora MySQL elige Consistencia y Disponibilidad, sacrificando la tolerancia a particiones. Por eso para escritura en Aurora PostgreSQL tenemos un nodo primario y el resto de nodos son réplicas secundarias, tanto en la misma región como en múltiples regiones.
Pero Aurora DSQL requiere que las tres propiedades estén garantizadas, lo que requiere un enfoque completamente diferente.

El Problema de la Ordenación Temporal

Imagina esta secuencia de eventos:

Región us-east-1  (10:00:00.020123): UPDATE users SET balance = 900 WHERE id = 123
Región us-west-2  (10:00:00.019548): UPDATE users SET balance = 200 WHERE id = 123
Región eu-west-1  (10:00:00.022020): UPDATE users SET balance = 500 WHERE id = 123
Región eu-south-2 (10:00:00.019386): UPDATE users SET balance = 300 WHERE id = 123

Enter fullscreen mode Exit fullscreen mode

¿Cuál de estas transacciones debería ejecutarse primero?

Son ejecuciones en 4 regiones diferentes en el mismo segundo, sobre el mismo dato y con valores diferentes.

Si utilizamos un sistema como Aurora PostgreSQL con un nodo en la región us-east-1 primario, el dato correcto sería el dato de esta región, ya que el resto de ejecuciones tendríamos que sumar la latencia, lo que produciría inconsistencias.

Además de este problema estamos suponiendo que los sistemas están completamente sincronizados al milisegundo, pero esto no es siempre así. Una sincronización de tiempo no es sencilla.

Por último estamos viendo un ejemplo de una acción, mientras que una transacción conlleva varias acciones anidadas, lo que puede suponer un problema mayor.

Este es el problema que Aurora DSQL debe resolverCrear un orden temporal global y consistente para todas las transacciones, independientemente de dónde se originen y tener un tiempo real y sincronizado entre regiones.

La Solución de AWS: Sincronización de Tiempo a Escala Global

Tradicionalmente, cualquier centro de datos ha utilizado NTP (Network Time Protocol) para sincronizar con relojes atómicos que tienen una precisión a nivel del nanosegundo. Sin embargo, NTP presenta limitaciones críticas para sistemas que requieren precisión extrema, ya que tiene dependencias con una red conectada y por tanto sufre de latencia de red y por tanto puede llegar a una precisión de milisegundos.

AWS necesitaba una sincronización entre regiones mucho mayor, si quería tener un sistema como Aurora DSQL.

Amazon Time Sync Service

AWS ha invertido mucho en que sus infraestructuras estén sincronizadas, para ello creó Amazon Time Sync Service.

Este servicio utiliza una flota de satélites con relojes atómicos que se conectan con todas las regiones de AWS proporcionando el mismo tiempo a todas ellas.
Los relojes atómicos son muy caros, pero son necesarios en cualquier sistema de posicionamiento satélite como GPS.
Además tienen una ventaja adicional, cuando envían los datos de tiempo sabemos con exactitud a qué distancia está el satélite y por tanto la latencia de la señal con un nivel de precisión muy alto.
A diferencia de un servicio tipo de NTP contra relojes atómicos en tierra esta latencia es estable y por tanto nos da una precisión mucho mayor.

Time Sync

La precisión alcanzada es del orden de nanosegundos entre regiones, algo impensable hace pocos años.

ClockBound: Midiendo la Precisión del Tiempo

Complementando Time Sync Service, AWS ha desarrollado ClockBound, un demonio y librería open source que mide la precisión del reloj de las instancias EC2 y permite determinar el orden temporal real de los eventos.

Aunque el Amazon Time Sync Service proporciona tiempo muy preciso, siempre existe una pequeña desviación en la sincronización. ClockBound cuantifica esta desviación, y añade ciertas funcionalidades fundamentales para Amazon DSQL:

Funcionalidades principales:

  • Intervalos de tiempo con incertidumbre: En lugar de un timestamp exacto como 10:00:00.123456789, ClockBound proporciona un rango [10:00:00.123456785, 10:00:00.123456793] qué garantiza que el tiempo real está dentro de ese intervalo.
  • Comparación temporal definitiva: Puede determinar si un evento A ocurre definitivamente antes que un evento B, o si son potencialmente concurrentes.
  • Detección de concurrencia: Identifica cuándo dos eventos pueden haber ocurrido al mismo tiempo, lo que es crucial para resolver conflictos de transacciones en DSQL
  • Optimización de esperas: Calcula el tiempo mínimo que debe esperar una aplicación para garantizar que un timestamp sea globalmente único.

Con ClockBound podemos comparar nuestra secuencia de tiempos de forma más exacta:

Evento us-east-1: [10:00:00.020120, 10:00:00.020126] 
Evento us-west-2: [10:00:00.019545, 10:00:00.019551]
Resultado: us-west-2 ocurrió definitivamente antes que us-east-1

Evento eu-west-1: [10:00:00.022018, 10:00:00.022022]
Evento eu-south-2: [10:00:00.019384, 10:00:00.019388] 
Resultado: eu-south-2 ocurrió definitivamente antes que eu-west-1

Evento us-east-1: [10:00:00.020120, 10:00:00.020126]
Evento us-west-2: [10:00:00.019545, 10:00:00.019551]
Evento con conflicto: [10:00:00.019550, 10:00:00.020125]
Resultado: El evento conflictivo se solapa con ambos
Enter fullscreen mode Exit fullscreen mode

Como podéis suponer sin ClockBound Aurora DSQL probablemente no existiría.

Por cierto aunque ClockBound ha sido desarrollado por AWS es un proyecto de código abierto que cualquiera puede usar.

Aurora DSQL: Arquitectura Real de Sincronización Distribuida

Bueno ya va siendo hora de empezar a hablar de cómo funciona Aurora DSQL y qué componentes lo conforman.

Ahora que entendemos cómo AWS ha resuelto el problema del tiempo, podemos explorar cómo Aurora DSQL utiliza esta tecnología para crear una base de datos verdaderamente distribuida que mantiene las propiedades ACID a escala global.

¿Cómo Funciona DSQL?

Aurora DSQL no es simplemente un Aurora PostgreSQL mejorado. Es una arquitectura completamente nueva que reimagina cómo debe funcionar una base de datos para permitir la consistencia en multi-región.

Componentes principales:

DSQL Arch

Es importante entender que cada capa escala de forma horizontal, independientemente del resto de capas y dinámicamente, todo ello dependiendo de la carga que demandemos a nuestro Aurora DSQL.

Esto hace que sea un servicio totalmente serverless.

Adjudicators:

Aunque la primera capa es Query Processor, vamos a hablar primero de la capa de Adjudicator.

La capa de Adjudicator es probablemente el componente más innovador de Aurora DSQL.
Son procesos distribuidos que implementan algoritmos de consenso para garantizar que todas las regiones estén de acuerdo sobre el orden de las transacciones.

Cuando una transacción necesita ejecutarse, la capa de adjudicator revisa si existe conflicto con alguna otra transacción reciente. Para esto utilizan un algoritmo de consenso optimizado y distribuido, que permite detectar posibles conflictos.

Además esta capa escala horizontalmente pudiendo tener múltiples adjudicators para diferentes particiones de nuestra BBDD, cada adjudicator se ocupa de un espacio diferente de nuestra BBDD.

En Aurora DSQL todas las capas están desacopladas, esto implica que podemos definir sistemas de particiones diferentes para cada capa, de forma que los adjudicator no se particionan en función de cómo vamos a almacenar nuestros datos, sino de cómo vamos a analizar los conflictos entre transacciones, mejorando el rendimiento de esta tarea.

Los adjudicators no están replicados en cada región, sino que están distribuidos por espacios de la BBDD, en cualquier momento un adjudicator puede cambiar de región.

Journal Layer:

El Journal es donde se registran todas las transacciones aprobadas por los Adjudicators.
Es similar al WAL (Write-Ahead Log) de Aurora PostgreSQL, pero distribuido en varias regiones.

Esto permite que cada transacción aprobada por un adjudicator se escriba directamente en los Journal.
Esta capa es la responsable de la durabilidad de los datos, ya que los registros del Journal son inmutables, están distribuidos y replicados en las regiones que elijamos, de forma que podemos tener una trazabilidad de todas las transacciones en este log de forma confiable y con respaldo.

Storage:

La capa de almacenamiento de Aurora DSQL extiende el concepto de Aurora Storage pero añadiendo capacidades de distribución global y consistencia temporal.

Una de las ventajas principales del Storage de Aurora DSQL, es que no es necesario que se ocupe de los conflictos (adjudicator) ni de la durabilidad (journal), esto da más flexibilidad a esta capa permitiendo una optimización mayor.

En vez de basar esta capa en una replicación de datos síncrona, lo que hace esta capa es basar la distribución del storage en particiones de datos, pero añadiendo capacidades de sharding no solo basados en particiones por claves, sino también por tiempo de acceso y regiones, de esta forma los datos están distribuidos de una forma más óptima aunque también estén replicados en las diferentes regiones.

Esto no es posible en una base de datos tradicional.

Query Processor:

Ya que hemos entendido cómo funcionan el resto de capas podemos hablar de Query Processor.

Query Processor es la capa donde se recibe cualquier Query y es donde Aurora DSQL recibe las peticiones SQL y las convierte en operaciones distribuidas. A diferencia de Aurora PostgreSQL, aquí no hay un nodo "primario" único.

Como hemos visto antes nuestro storage no está particionado de una forma estándar, los datos no están replicados en el mismo storage, sino que están distribuidos en diferentes particiones de storage para mejorar el rendimiento.

Esto provoca que cualquier operación de lectura sea menos eficiente porque tenemos que saber dónde está el dato. También pasa con las operaciones de escritura ya que estas van a ser analizadas por la capa de adjudicator antes de escribirse en el Journal y posteriormente a la capa de Storage.

Además hemos visto que todas estas capas están distribuidas en diferentes particiones y que no tienen por qué ser iguales, es decir la capa de adjudicator no está particionada igual que la capa de storage.

Por este motivo Query Processor es una capa fundamental, ya que es la capa que orquesta todas nuestras querys, de forma que es capaz de saber en qué partición está un dato en storage para consultarlo o a qué adjudicator tiene que asignar la tarea de escritura de cualquier dato.

De esta forma esta capa minimiza la latencia reduciendo las tareas y loops que se pueden generar en cualquier transacción.

Otra ventaja para mantener la consistencia es que hace una ordenación temporal de las lecturas y elige el dato versionado más óptimo, por eso si realizamos una consulta de lectura de un dato que ha sido modificado después de nuestra lectura, pero por latencia nuestra escritura ha sido más rápida, en vez de devolvernos el dato modificado nos va a dar la versión del dato en el momento de la ejecución de la query.

Ejemplo de Diagrama Completo:

DSQL Arch

¿Pero en qué mejora esta arquitectura a una arquitectura tradicional?

La verdad es que la arquitectura está bien, pero realmente funciona ¿?

Strong Snapshot Isolation

Aurora DSQL utiliza una variación del protocolo Optimistic Concurrency Control en lugar de un modelo tradicional. Esto es crucial para el rendimiento en un entorno distribuido.

Básicamente el protocolo de Optimistic Concurrency Control define que si estamos ejecutando una transacción que pueden concurrir con otra, el commit de esta transacción solamente puede ejecutarse en el caso que cumpla con las reglas de aislamiento definidas.

Por eso DSQL usa Strong Snapshot Isolation, este modo de trabajo permite que varias transacciones puedan ejecutarse en paralelo cumpliendo una serie de reglas:

  1. Todas las lecturas de la transacción se hacen utilizando el timestamp del inicio de la transacción (Tstart).
  2. En el momento de realizar el commit de los datos se establece el timestamp de commit (Tcommit).
  3. La transacción puede ejecutar el commit única y exclusivamente si ninguna otra transacción ha realizado un commit en la misma clave durante el tiempo de inicio de la transacción (Tstart) y el tiempo de commit (Tcommit)
  4. Se ejecutan las escrituras utilizando el timestamp de Commit (Tcommit)

Esto tiene varias ventajas:

  1. Nunca vamos a ver datos que no vengan después de un commit.
  2. Las lecturas son repetibles y por tanto cacheables.
  3. Las lecturas vienen desde un solo punto de tiempo (lógico).
  4. Las escrituras conflictivas son rechazadas, no se pierden escrituras.

Por este motivo es tan importante la capa de Query Processor y Adjudicator, ya que es la encargada de hacer magia y que esto funcione.

Optimización Multi-Región

Hasta ahora lo que hemos visto parece que no es muy multi-región, además que seguiríamos teniendo un problema con las latencias, pero hemos visto un punto importante DSQL separa las lecturas de las escrituras y además tiene un componente fundamental como Query Processor que es donde se encolan las escrituras antes de realizarse el Commit.

De esta forma toda la transacción se puede llegar a ejecutar localmente aunque los datos estén distribuidos ya que únicamente a la hora de realizar el Commit es cuando necesitamos que la capa de Adjudicator valide y es posible que este adjudicator esté en otra región, pero solamente lo necesitamos para ejecutar el commit, no para escribir el dato.

Esto mejora increíblemente el rendimiento, evitando saltos entre regiones innecesarios, por lo que la latencia real de DSQL es mucho más baja de lo que esperaríamos.

DSQL Optimized

Optimización de FailOver

Otra ventaja es que la capa de Adjudicator es una capa que no guarda todos los datos, solamente las transacciones recientes, para estudiar si hay conflicto por lo que es fácilmente replicable en caso de caída de una región.

Por otro lado la capa de Journal sí está distribuida y replicada, por lo que en caso de caída los datos están replicados en multi-región.

Por último la capa de storage, que siempre es algo más lenta a la hora de replicar, tiene la capacidad de rehacer todas las escrituras pendientes utilizando la capa de Journal.
DSQL FailOver

Comparativa con Aurora PostgreSQL o bases de datos tradicionales

Eliminación del Concepto de "Primario"

En Aurora PostgreSQL, hay un nodo primario que maneja todas las escrituras.

En Aurora DSQL, podemos iniciar una escritura desde cualquier región, minimizando las latencias en multi-región.

Escalabilidad Horizontal Real

Aurora DSQL puede escalar añadiendo más regiones sin que tengamos una degradación de rendimiento.

FailOver más rápido

En Aurora PostgreSQL, si la región primaria falla, es necesario que una de las réplicas en otra región pase a ser primario (proceso de 1-2 minutos).

En Aurora DSQL el proceso es mucho más rápido porque no existe un primario y la carga está distribuida.

Escalado Horizontal

Aurora DSQL es una BBDD con capas totalmente desacopladas, lo que hace que sea posible realizar un escalado horizontal y desescalado horizontal, cosa que en Aurora PostgreSQL o en una BBDD tradicional no es posible

Además es una BBDD totalmente serverless.

Limitaciones y Consideraciones

Aurora DSQL es una maravilla, pero no es una base de datos para todos los públicos, por lo que personalmente no la recomiendo para todo el mundo.

Es una base de datos increíble si tu caso de uso es el adecuado, pero si tienes un caso de uso tradicional, probablemente no sea tu base de datos, incluso puede llegar a ser muchísimo peor que Aurora PostgreSQL.

Es importante remarcar que Aurora DSQL prioriza consistencia sobre latencia extrema:

  • Transacciones simples: 10-50ms adicionales vs Aurora PostgreSQL.
  • Transacciones complejas: Puede ser 2-5x más lento que Aurora PostgreSQL.
  • Lecturas: No hay mucha variación ya que se utilizan réplicas locales.

Comparativa de Latencia en la misma región

Si nuestras cargas están en una única región, Aurora DSQL va a tener una latencia mucho mayor.

Modelo de Precios de Aurora DSQL

Aurora DSQL utiliza un modelo de precios serverless que cobra por:

Compute (Procesamiento):

  • Se factura por Aurora Compute Units (ACUs) consumidas.
  • Escalado automático basado en la carga de trabajo.
  • No es necesario provisionar

Storage (Almacenamiento):

  • Cobro por GB almacenado por mes
  • Replicación automática incluida en el precio
  • Backup automático incluido

I/O (Entrada/Salida):

  • Cobro por millones de requests de I/O
  • Incluye tanto lecturas como escrituras

Comparación de Costes: Aurora DSQL vs Aurora PostgreSQL

Escenario ejemplo - Aplicación mediana:

Aurora PostgreSQL (Provisioned):

- Instancia db.r6g.large: ~$200/mes
- Almacenamiento (100GB): ~$10/mes
- I/O (10M requests): ~$2/mes
- Total aproximado: ~$212/mes (región única)
Enter fullscreen mode Exit fullscreen mode

Aurora DSQL (Serverless):

- Compute (equivalente): ~$300-400/mes
- Almacenamiento (100GB): ~$15-20/mes
- I/O (10M requests): ~$3-5/mes
- Multi-región incluida: Sin costo adicional
- Total aproximado: ~$320-425/mes (multi-región)
Enter fullscreen mode Exit fullscreen mode

Factores que afectan el costo:

  • Patrón de tráfico: Aurora DSQL es más eficiente para cargas variables
  • Requisitos multi-región: Aurora DSQL incluye replicación en múltiples regiones sin costo adicional.
  • Complejidad de transacciones: Transacciones distribuidas complejas pueden incrementar el costo
  • Escalado automático: Puede resultar más económico para aplicaciones con picos de tráfico

¿Cuándo Aurora DSQL es más rentable?

  • Aplicaciones con tráfico muy variable (serverless se adapta automáticamente) Siempre que la Latencia lo permita
  • Necesidad de multi-región.

Casos de Uso No Recomendados

Aurora DSQL no es ideal para:

  • Aplicaciones que requieren latencia ultra-baja (<1ms)
  • Cargas de trabajo principalmente de lectura
  • Sistemas con presupuesto muy limitado
  • Aplicaciones que pueden tolerar eventual consistency

Aurora DSQL es una nueva categoría de base de datos que da una vuelta a las bases de datos distribuidas y multi-región.
Con esta nueva base de datos, AWS ha abierto la puerta a aplicaciones globales que antes eran técnicamente imposibles.

Top comments (0)