
Lo prometido es deuda y tras dos artículos centrados en detallar los conceptos básicos sobre las base de datos distribuidas, es el momento de conocer DynamoDB, uno de los productos estrella del catalogo de AWS.
En el presente articulo se pretende describir la arquitectura y características de la citada base de datos, así como detallar aquellos casos de uso en los que se recomienda su aplicación.
Introduction
Amazon DynamoDB es un sistema NoSQL clave-valor, escalable, multiregión y completamente administrado, especialmente construido para aquellos sistemas de baja latencia que requieran que al menos el 99.9% de las operaciones de lectura y escritura se realicen en unos pocos milisegundos. No esta nada mal, ¿no?
Originalmente fue concebido para dar solución a los problemas de escalabilidad a los que se enfrentaba la plataforma e-comerce de Amazon allá por las navidades de 2014.
Por aquel entonces, la compañía de Jeff Bezos disponía de cientos de sistemas, como el servicio de recomendaciones personalizadas o la detección de fraudes, entre otros, que requerían de un almacenamiento altamente escalable, en el que la consistencia no era primordial y en el que únicamente se realizaban operaciones de lectura/escritura por clave primaria. Evidentemente esto era perfectamente posible a través de los sistemas RDBMS tradicionales, pero su capacidad de escalado y replicación eran mas bien limitadas y el coste del hardware elevado.
Partiendo de dichas premisas, Amazon ideo una implementación de Dynamo, una especificación que define un conjunto de técnicas para la construcción de sistema de almacenamiento estructurado de valor clave altamente disponible, que a la postre se acabaría convirtiendo en la base de los servicios Amazon DynamoDB o S3 que todos conocemos.
Architecture
DynamoDB se basa una arquitectura descentralizada peer to peer (P2P) de nodos simétricos, en el que cada uno de ellos tiene exactamente el mismo conjunto de responsabilidades que el resto. Es decir, no hay distintas tipologías de nodos que ejerzan funciones dedicadas, si bien algunos de ellos pueden desempeñar adicionalmente ligeras tareas de coordinación.
Cada nodo tiene toda la información de enrutamiento necesaria para redirigir cada petición directamente al nodo apropiado, sin necesidad de acceder a un registro centralizado en el que almacenar, tanto la ubicación de los datos, como el estado de salud del resto de los nodos que conforman el cluster.
Para ello, internamente implementa el protocolo gossip, también conocido como epidemic protocol, en el que cuando un nodo habla con otro, no solo proporciona información sobre su estado, sino también sobre los nodos con los que se había comunicado previamente.
Este diseño de arquitectura simplifica el proceso de aprovisionamiento y mantenimiento del sistema, al mismo tiempo que trata de garantizar un teórico escalado lineal del rendimiento. Es decir, si con un nodo la base de datos es capaz de atender 200 peticiones, al añadir un segundo nodo podría llegar hasta las 400, si bien esto depende de mas factores, todo ello sin procesos de actualización que afecte todos los nodos ni tiempos de caída.
Data modeling
DynamoDB trabaja con el concepto de tabla, que no es mas que un conjunto de items, que a su vez se componen de una colección de atributos clave-valor.
A diferencia de las bases de datos relacionales, las tablas no tienen un schema estricto, por lo que los items pueden tener un número distinto de atributos y los atributos con el mismo nombre pueden ser de distinto tipo. Eso sí, los items no pueden rebasar los 400Kb de tamaño y no es posible realizar operaciones que afecten a más de una tabla.
Finalmente, señalar que todo item debe tener una clave primaria que se utilizará para definir en que partición se alojan los datos, tal y como se detalla en el siguiente aportado. Esta clave puede estar formada por un único campo denominado Partition Key o acompañada de una Sort Key, con el que tratar de ordenar los items dentro de una partición y mejorar el rendimiento de las búsquedas ordenadas.
Partitioning
En DynamoDB los datos se particionan a lo largo de los distintos nodos que forman el clusters en base a un algoritmo de hashing consistente. Concretamente, se aplica un hash MD5 sobre la clave primaria para obtener un identificador de 128-bit que se utiliza para determinar en qué nodo deben residir.
Mediante este algoritmo, el rango de salida de la función hash forma un espectro circular fijo o “anillo”, en el que a cada nodo del cluster se le asigna un rango aleatorio dentro de este espacio que representa su “posición”.
Así, cada nodo se hace responsable de la región en el anillo entre él y su predecesor, por lo que la salida o llegada de un miembro solo afecta a sus vecinos inmediatos.
Esto es posible al uso de un algoritmo de hashing consistente citado previamente. A diferencia de la mayoría de las estrategias de hashing convencionales, un cambio en el número de nodos del cluster, no implica que se reasignen casi todas las claves, tan solo, de media, K/n, donde K es el numero de claves y n el numero de nodos.
Ahora bien, esta estrategia de distribución de datos tiene un aspecto negativo, ya que el tiempo de respuesta se ve muy mermado al realizar búsquedas por campos que no sean la clave primaria, ya que los datos pueden encontrarse desperdigados entre varios nodos.
Es por lo que DynamoDB implementa una variante en el que cada nodo físico gestiona múltiples nodos virtuales, que se ubican en distintas posiciones del anillo, para tratar de minimizar la problemática descrita. Aun así, para disfrutar de un rendimiento optimo se recomienda realizar operaciones por clave primaria.
En lo que al número de particiones por tabla se refiere, es la propia DynamoDB quien lo calcula de forma interna siguiendo la siguiente formula.

- RCU: Read Capacity Units – 4KB/sec.
- WCU: Write Capacity Units – 1KB/sec.
Por ejemplo, suponiendo una tabla de 120GB, con 9000 RCU y 6000 WCU, el nímero de particiones por throughput sería el siguiente:
(9000/3000) + (6000/1000) = 3 + 6 = 9
En cambio, el número de particiones por tamaño de tabla sería el siguiente:
120/10 = 12
Así, el número de particiones final sería el siguiente:
MAX(9, 12) = 12
Aquí es cuando surge uno de los principales problemas que presenta este producto, que no la implementación de Dynamo en si, ya que tanto el RCU como el WCU se establece a nivel de tabla y se divide de forma proporcional entre todas las particiones. Por tanto, si el dataset contuviera algún registro al que se accediera de forma muy recurrente (hot key), habría que sobredimensionar dichos valores, para poder garantizar su disponibilidad.
Esto evidentemente supone un incremento exponencial de los gastos, ya que AWS factura en base al aprovisionamiento de espacio, RCU y WCU entre otros. Es por ello muy recomendable la lectura del siguiente articulo titulado The million dollar engineering problem, en el cual se habla precisamente de esto.
Ademas, este aumento de capacidad puede acarrear también la creación innecesaria de nuevas particiones, que a su vez pueden penalizar el rendimiento de aquellas consultas no ejecutadas a través de la clave primaria (Cuantos mas nodos se tengan que consultar para obtener el resultado final, mayor será el tiempo de respuesta). No os equivoquéis, el sharding es genial porque facilita un escaldo horizontal infinito, pero sí no se ejecuta de forma correcta puede penalizar el rendimiento.
En resumen, cuando se trabaja con DynamoDB es extremadamente importante definir una clave de partición que distribuya uniformemente las lecturas y escrituras entre todas las particiones.
Replication
Llega el momento de hablar de la replicación, factor que impacta de lleno en el nivel de disponibilidad y consistencia que es capaz de ofrecer el producto.
Básicamente, aquellas bases de datos enfocadas a garantizar una consistencia total de los datos suelen optar por un modelo de replicación síncrona, a costa de sacrificar la disponibilidad de los datos en ciertos escenarios de fallo. Es decir, los datos no se encuentras disponibles hasta que la base de datos determina que son absolutamente correctos, motivo por el cual no es posible garantizar de forma simultánea la consistencia y disponibilidad total.
Por contra, aquellas bases de datos que priorizan la disponibilidad, es decir, que trabajar con consistencia eventual, suelen optar por un modelo de replicación asíncrona optimista. Ahora bien, este modelo se encuentra con la problemática de lidiar con la resolución de conflictos en la deben definir cuándo y quien los resuelve.
- Cuando: Los conflictos se pueden resolver en tiempo de lectura o escritura. El hecho de delegarlo a las escrituras permite mantener una lógica simple de lectura, si bien la disponibilidad de se ve mermada, ya que el sistema no permitirá realizar nuevas actualizaciones hasta que conflicto sea resuelto. En cambio, si se delega en las lecturas la consistencia total deja de ser una opción.
- Quien: Los conflictos pueden ser resueltos por la propia base de datos o por los consumidores. Si se delega en el sistema de almacenamiento este podrá aplicar políticas simples como que siempre prevalezca la ultima escritura o estrategias basadas en el quorum. En cambio, delegarlo en las aplicaciones consumidoras permite ofrecer una experiencia mas personalizada para cada caso de uso, a costa de aumentar su complejidad.
Dicho esto, aquí es donde se presenta una de las principales diferencias entre Dynamo y DynamoDB.
Dynamo vs DynamoDB
La implementation original de AWS de Dynamo se decanta por una replicación asincrona gestionada por parte de la propia base de datos. El motivo es evidente, rechazar las actualizaciones de los datos en determinados servicios de Amazon, como, por ejemplo, el carro de la compra, podría traducirse en una mala experiencia para el cliente.
Por contra, DynamoDB opta pon un modelo de replicación mixto, gestionado también por la propia base de datos, con el que tratar de ofrecer al cliente operaciones de lectura tanto con consistencia eventual como con consistencia total. Para ello, AWS se encarga de crear por defecto y de forma no configurable, 3 replicas para cada tabla y ubicar cada una de ellas en un AZ (Availability Zone) dentro de una misma región, independientemente del caso de uso. Así, las operaciones de escrituras son, en teoría, siempre consistentes, ya que cada una de ellas se escribe de forma síncrona en dos replicas y de forma asíncrona en la tercera restante.
Dicho esto, desde 2017 AWS también oferta tablas globales con las que dotar de capacidades multiregion a DynamoDB. Cada una de las regiones seleccionadas dispone de su propia tabla completamente aislada, que se enlaza con el resto a través de un modelo de replicacion asíncrona, con lo que el nivel de consistencia entre regiones se vuelve eventual. Además, al tratarse de una arquitectura de nodos simétricos P2P en la que no hay nodos maestros coordinadores o lideres, no tiene mas remedio que emplear un algoritmo “last-writer-wins” para la resolución de conflictos, el cual puede provocar perdidas inesperadas de datos entre regiones.
Consistency
Tal y como se detallaba en la anterior sección, DynamoDB trabaja siempre con una consistencia total para las escrituras, mientras que el usuario puede escoger en cada petición de lectura, si opta por la disponibilidad (consistencia eventual) o por la fiabilidad (consistencia total basada en quorum). Sobra decir que en este ultimo modelo tanto los tiempos de respuesta como el coste por petición se incrementa.
Ahora bien, ¿Se trata entonces de una base de datos que cumple con la 3 capacidades del teorema de CAP? Por desgracia, no.
Lo primero de todo es que, tal y como señala la documentación oficial de AWS, “Una lectura de consistencia alta podría no estar disponible si se produce un retraso o una interrupción en la red. En este caso, DynamoDB podría devolver un error de servidor (HTTP 500).”. Por tanto, DynamoDB pasa de ser un sistema AP (Available and Partition Tolerant) a un sistema CP (Consistent and Partition Tolerant), al no poder garantizar la disponibilidad de los datos bajo cualquier circunstancia.
Por otro lado, los modelos de lectura/escritura basados en QUORUM tampoco garantizan una consistencia total en todos los escenarios. Analicemos la fórmula que emplean:
(nodes_written + nodes_read) > replication_factor
Es decir, para un factor de replicación de 3, los datos deben ser escritos de forma síncrona en 2 nodos y ser leídos también de 2 nodos en cada operación. De esta forma se logra una consistencia total de los datos, ya que al menos una de las réplicas participa tanto en el proceso de escritura como en la de lectura, lo que a su vez garantiza que se leerá la última escritura.
Ahora bien, en caso de producirse un error en el proceso de escritura y únicamente uno de los nodos fuera actualizado, dos consultas podrían devolver dos valores distintos para un mismo dato.
Estos problemas se podrían solventar mediante otros diseños de arquitectura basados en el uso de coordinadores que se encargarán de gestionar los conflictos, si bien no se debe olvidar cual es la verdadera naturaleza de Dynamo: Una base de datos distribuida clave-valor, escalable y en la que se prioriza la disponibilidad por encima de la consistencia.
AWS Lambda
Antes de abordar el capitulo de las conclusiones, es un buen momento para desgranar las dos principales características por las que es tan habitual ver las funciones Lambda de AWS junto a una DynamoDB.
Lo primero de todo es que, a diferencia de la mayoría de bases de datos, DynamoDB no se despliega sobre ninguna VPC (Virtual Private Network) del cliente, lo que reduce considerablemente el cold start de las funciones Lambda al no tener que crear una ENI (elastic network interface) sobre la VPC cada vez que se ejecutan.
Por otro lado, el hecho de poder interactuar vía HTTP resuelve todos los problemas relacionados con la gestión de conexiones de la base de datos que fueron detallados en el articulo titulado Serverless: Managing database connections.
Conclusiones
En conclusión, DynamoDB es una excelente base de datos distribuida con sus fortalezas, como la escalabilidad horizontal, alta disponibilidad y sus bajos tiempos de respuesta, pero también con sus debilidades, como los problemas derivados al tratar de ofrecer una consistencia total, unos costes que tienden a dispararse o rendimiento mas pobre en operaciones no realizadas sobre la clave primaria.
Por tanto, y al igual que con cualquier otra base de datos, es importante conocer bien sus características y decidir si realmente encaja o no con el caso de uso en cuestión.
Referencias
Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito:
- https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf
- https://brewing.codes/posts/dynamo-data-modeling/
- https://blog.yugabyte.com/apache-cassandra-lightweight-transactions-secondary-indexes-tunable-consistency/
- https://blog.yugabyte.com/11-things-you-wish-you-knew-before-starting-with-dynamodb/
- https://cloudacademy.com/blog/amazon-dynamodb-ten-things/
- https://docs.aws.amazon.com/es_es/amazondynamodb/latest/developerguide/Introduction.html
- https://medium.com/expedia-group-tech/dynamodb-why-migrate-to-dynamodb-from-cassandra-f4955be87b19
- http://muratbuffalo.blogspot.com/2010/11/dynamo-amazons-highly-available-key.html
- https://read.acloud.guru/why-amazon-dynamodb-isnt-for-everyone-and-how-to-decide-when-it-s-for-you-aefc52ea9476
- https://shinesolutions.com/2016/06/27/a-deep-dive-into-dynamodb-partitions/