Google Cloud Spanner in detail

Vuelve la serie de artículos centrada en las bases de datos distribuidas y en esta ocasión le llega el turno a Cloud Spanner, el servicio gestionado, escalable y multiregión de bases de datos relacionales de Google, con el que el gigante Mountain View pretende captar nuevos clientes para su Cloud particular y como no, afianzar su diabólico y verdadero plan de dominar el mundo.

Como ya es costumbre en la casa, en el presente artículo se pretende describir la arquitectura y características de la citada base de datos, no sin antes invitaros a leer la primera y segunda parte acerca de los conceptos básicos de las bases de datos distribuidas.

Introduction

Cloud Spanner se describe oficialmente como un servicio crítico de bases de datos relacionales y completamente administrado, que ofrece coherencia en las transacciones a escala global, esquemas, SQL (ANSI 2011 con extensiones) y replicación automática síncrona para brindar una alta disponibilidad.

Posiblemente no sea la traducción al español mas acertada de Google, pero básicamente habla de un servicio gestionado de base de datos relacional, multiregión, escalable, altamente disponible, y lo mas importante, que garantiza la consistencia de las operaciones de forma global entre todos los nodos que conforman el cluster.

¿Significa esto que resuelve los problemas de escalabilidad asociados a las bases de datos tradicionales, al mismo tiempo que preserva todas las características que las han convertido en imprescindibles en las ultimas décadas? Si bien ese podría ser el objetivo final, aun se encuentra un poco lejos de dicha meta.

Comencemos con un poco de historia. El primer cliente o servicio que hizo uso de Cloud Spanner fue F1, una reescritura del backend de anuncios/publicidad de Google. Allá por 2011, F1 hacia uso de una MySQL con sharding manual que debía manejar decenas de terabytes sin comprimir, lo cual dificultaba enormemente su gestión y escalabilidad, llegando incluso a tener almacenar datos en un BigTable externo. Pero, ¿por que decantarse por Cloud Spanner en lugar de hacer uso de servicios contrastados como BigTable o Megastore?

En primer lugar, porque Spanner solventaba los principales problemas de los que adolecía F1, como la necesidad de realizar el sharding de forma manual o la ausencia de replicación síncrona de los datos y un sistema de failover automático, algo complicado en una arquitectura maestro-exclavo de MySQL, con riesgo de sufrir perdidas de datos o servicio.

Por otro lado, BigTable no se ajustaba tan bien como cabria esperar, ya que puede ser difícil de utilizar en aquellas aplicaciones que requieren de esquemas complejos y en evolución, o de una consistencia fuerte para las transacciones, ya que emplea una estrategia de replicación asíncrona entre datacenters, que se traduce en una consistencia eventual. Megastore en cambio sí que era capaz de garantizar estos requisitos, gracias a su modelo de datos semi-relacional, si bien su rendimiento de escritura era mas bien pobre.

En definitiva, Cloud Spanner es una evolución de estos dos servicios con el que cubrir una necesidad demandada por el mercado que no tenía respuesta hasta el momento.

Architecture

A cada despliegue de Cloud Spanner se le denomina universo y normalmente se tiende a crear uno por entorno.

Cada universo está compuesto por un conjunto de zonas, que no son mas que un grupo de máquinas ubicadas en un mismo datacenter de Google, que se utilizan para dar servicios a aquellos clientes geográficamente mas próximos. También representan el conjunto de ubicaciones en las que se pueden replicar los datos, pudiendo convivir multiples zonas en un mismo datacenter.

Así, cada zona esta compuesta por un y solo un zonemaster, encargado de asignar los datos a un conjunto de spanserver, los cuales a su vez se ocupan de almacenar los datos. Finalmente están los location proxies, que determinan para cada petición de los clientes, el spanserver en el que se ubican los datos solicitados.

¿Y que hay del universe master y placement driver? El primero es el encargado de proporcionar el estado de las distintas zonas que componen el universo a través de una consola (UI), con el objetivo de simplificar las tareas de debugging. El placement driver en cambio, se encarga de mover los datos entre las distintas zonas, ya sea para cumplir con los requisitos de replicación o para balancear la carga. Para poder llevar acabo estar labor, se comunica de forma periódica con los spanservers.

En resumen, desde Google han preferido dejar de lado la arquitectura descentralizada peer to peer (P2P) de nodos simétricos empleada en DynamoDB o Kafka, para dar paso a una estructura zonal con coordinadores, que posibilita de igual manera un escalado horizontal teóricamente ilimitado así como una consistencia fuerte de lectura/escritura. Y sí, como no podía ser de otra forma, es posible agregar o eliminar zonas, en caliente, a medida que se ponen en servicio nuevos centros de datos y se apagan los antiguos, respectivamente.

Ahora bien, como en todo buen servicio gestionado, Google se encarga de abstraer a los desarrolladores de todos estos conceptos y los simplifica al máximo bajo el paraguas de las instancias.

Una instancia de Cloud Spanner define la ubicación (regional o multiregional), replicación y el numero de nodos, siendo esta una configuración inmutable, a excepción del número de nodos, claro está (Como siempre, el mínimo recomendando para un entorno productivo es de 3 nodos). Tal y como se puede observar, su aprovisionamiento es cuanto menos sencillo.

La siguiente imagen ilustra un diagrama de arquitectura para una instancia regional de 4 nodos en la que cada zona dispone de una réplica completa de la base de datos. Esto es debido a que, para cualquier configuración regional, Cloud Spanner mantiene 3 réplicas de lectura y escritura ubicadas en 3 zonas distintas de la región seleccionada, con las que atender las peticiones de lectura/escritura y garantizar la disponibilidad en caso de falla en alguna de ellas (No os preocupéis, se profundizará mas en esto en el capítulo centrado en la replicación).

Finalmente, es importante destacar que por cada nodo aprovisionado, se construye un serving task o spanner server en cada una de las zonas y no uno por zona.

A modo de curiosidad, cada nodo ofrece hasta 2 TiB de almacenamiento y un rendimiento estimado de 10.000 consultas por segundo (QPS) o 2.000 QPS de escrituras por segundo al escribir filas de 1 KB en una configuración regional.

El rendimiento para una configuración multiregional desciende hasta las 7.000 consultas por segundo (QPS) o 1.800 QPS de escrituras por segundo. Por tanto, el cruce de estos datos con las volumetrías del caso uso a desarrollar, debería producir un primer esbozo de la configuración a aplicar.

Data modeling

Aunque Cloud Spanner se auto define como una base de datos relacional, lo cierto es que internamente trabaja con un modelo de datos basado en tablas semi-relacionales esquematizadas, con soporte para lenguajes de consultas como SQL.

Es decir, no es un modelo puramente relacional, más bien se asemeja a un almacén de clave valor. Cada tabla debe contener un conjunto ordenado de una o más columnas de clave primaria, a través de las que se identifica el resto de las columnas de clave no primaria.

Gracias a ello, Google puede realizar sharding a nivel de fila y garantizar así una escalabilidad horizontal teóricamente infinita para su producto.

Esto no impide que funcione de una forma similar (que no igual) a las bases de datos tradicionales conocidas por todos: una base de datos puede contener un conjunto de tablas esquematizadas (max 2560) con sus filas, columnas (max 1024), claves primarias (max 16) e índices, para las que se garantiza una consistencia fuerte en las operaciones realizadas sobre ellas, independientemente del numero de tablas afectadas.

Ahora bien, esta forma de modelar internamente los datos también acarrea una serie de consecuencias que es mejor conocer.

Lo primero es que si se quiere garantizar la integridad referencial entre tablas, como si de una RDBMS tradicional se tratara, será necesario definir una relación padre-hijo entre ellas. De esta forma, Cloud Spanner ubica físicamente juntas las filas, lo que habilita la integridad referencial y a su vez, mejora significativamente el rendimiento cuando se realizan operaciones que afectan a múltiples tablas.

Tal y como reza la documentación oficial, esta asociación no es obligatoria, pero de lo contrario, las garantías de cardinalidad o las actualizaciones y eliminaciones en cascada no se aplicarán, pasando a ser responsabilidad de los desarrolladores. Por desgracia, no se trata de una decisión que ha de tomarse a la ligera, ya que, una vez creada la tabla, no será posible realizar esta asociación sin destruirla y recrearla de nuevo.

A continuación, se muestra un sencillo ejemplo de como definir una relación padre-hijo entre dos tablas y como posteriormente se almacenan físicamente los datos.

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

Algo similar ocurre con la clave primaria, ya que, una vez definida no es posible cambiarla sin destruir y recrear la tabla, lo cual te obliga a realiza un análisis en profundidad del uso de esta a corto/medio plazo. Probablemente esto sea consecuencia de que se utilice la PK como shard-key para determinar el nodo en el se ubican los datos y un cambio en la misma obliga a recalcular y reubicar toda la información almacenada.

Finalmente están las actualizaciones del esquema, las cuales en Cloud Spanner no implican un tiempo de inactividad, ya que se ejecutan como una operación en segundo plano. Esto permite que mientras se ejecutan scripts DDL se pueda continuar leyendo y escribiendo en la base de datos sin interrupción alguna.

Ahora bien, estas modificaciones puede que requieran validar que los datos existentes cumplen con las nuevas restricciones, un proceso que puede tardar entre varios minutos o varias horas, en función de la cantidad de datos y nodos afectados, así como la carga de estos. Esto implica que, aunque los cambios para las nuevas peticiones se aplican de forma instantánea (imaginar que una de las columnas existentes pasa a ser NOT NULL), esta validación en segundo plano puede llegar a fallar y como consecuencia, durante ese período de tiempo se habrán bloqueado escrituras que el anterior esquema si hubiera aceptado.

Esto son solo algunos de los puntos a tener en cuenta, pero es altamente recomendable sino imprescindible leer en profundidad la documentación de Cloud Spanner y tener bien claros los requisitos de la aplicación a construir, antes de embarcarse en esta trepidante aventura.

Partitioning

En Cloud Spanner los datos se particionan a lo largo de los distintos spanservers que forman el universo mediante una estrategia de sharding basada en rangos de la clave primaria, ordenada lexicográficamente.

Esto permite a Google realizar búsquedas filtradas y ordenadas por la primary key de forma eficiente, pero no garantiza una distribución uniforme de los datos y por lo tanto tampoco de la carga de trabajo.

En consecuencia, es altamente recomendable leer el documento de buenas practicas de Google para definir la clave primaria, entre otras cosas, porque podría acabar generando puntos calientes o cuellos de botella en la plataforma.

Por poner ejemplo, si se utiliza una clave compuesta en la que la primera parte es incremental, todas las inserciones se realizarán al final de su espacio de claves y serán atendidas por el mismo servidor, creando así un punto caliente.

Fuente Original

Finalmente, remarcar que Cloud Spanner no siempre aplica sharding sobre todos los datos, es decir, si el tamaño de las tablas no sobre pasa los 4GB, puede que la base de datos esté gestionada por un único servidor.

A medida que el volumen de lo mismos aumenta, comienza a dividir de forma automática y totalmente transparente para el desarrollador, los datos en múltiples fragmentos llamados splits (shards), que pueden distribuirse de forma independiente entre los distintos nodos.

Dicho esto, y con el objetivo de escalar tanto las lecturas como las escrituras de forma eficiente, cada split puede ser reubicado por el sistema en cualquiera de los spanservers disponibles, garantizando eso si que siempre es un único nodo que controla las escrituras sobre el mismo.

Replication

Llega el momento de hablar de la replicación, uno de los factores clave que permite al servicio de Google garantizar una consistencia fuerte con semántica ACID para las transacciones de lectura y escritura.

Tal y como se detallaba en secciones anteriores, Cloud Spanner opta por una estrategia de replicación síncrona de los datos basada en Paxos. Para ello, realiza múltiples copias de los split mencionados previamente y los ubica en distintas zonas en función de la configuración geográfica seleccionada.

Así, el algoritmo de consenso de Paxos determina que una de esas réplicas debe ser elegida para que actúe como líder y se encargue de gestionar todas las escrituras, mientras que el resto de réplicas tan solo pueden podrán atender, por si solas, aquellas peticiones de lectura que puedan conformarse con una consistencia eventual. En caso de requerir una consistencia fuerte en la lectura, deberán comunicarse con la réplica líder o realizar la petición directamente sobre ella.

En lo que al proceso de replicación propiamente dicho se refiere, cuando la réplica líder recibe una petición de escritura, la reenvía al resto de réplicas para votar esa escritura y solo se confirma cuando la mayoría de las réplicas la aceptan (“cuórum de escritura”). Internamente, cuando una réplica recibe una solicitud por parte del líder, completa su escritura y responde al líder con un voto sobre si la escritura debe confirmarse. Por supuesto, si alguna de las réplicas se retrasa en la escritura, pero esta ha sido confirmada por el resto, puede solicitar los datos que faltan a otra réplica para tener una copia completa y actualizada de los mismos.

Ahora bien, seguro que muchos de vosotros os estáis preguntado…¿Cuántas replicas se realizan? ¿Es configurable? ¿Pueden todas ellas convertirse en líderes o existen distintos tipos de roles que pueden adoptar? Vayamos por partes 😉

Cloud Spanner trabaja con tres tipos de réplicas: réplicas de lectura y escritura, réplicas de solo lectura y réplicas de testigos. La siguiente imagen resume a la perfección las funciones de cada una de ellas.

En lo que al número de replicas se refiere, no, no es configurable, sino que viene determinado por la configuración geográfica seleccionada.

Así, cuando se define una configuración regional, Cloud Spanner mantiene siempre 3 réplicas de lectura y escritura ubicadas en 3 zonas distintas de la región seleccionada, con las que atender las peticiones de lectura/escritura y garantizar la disponibilidad y quorum de escritura en caso de falla en alguna de ellas.

Por el contrario, cuando se define una configuración multiregional, mantiene dos regiones de lectura y escritura, que a su vez albergan dos réplicas de lectura y escritura.  Una de estas regiones se designa como la región líder en la que, para sorpresa de nadie, se ubican las réplicas líderes.

Bajo esta configuración, se puede construir una tercera región de lectura compuesta únicamente por replicas de lectura con las que descargar a las líderes de aquellas consultas que no requieran una consistencia fuerte, a su vez que minimiza el tiempo de respuesta para aquellos clientes que se encuentren fuera de las regiones de lectura y escritura.

Finalmente, también se aprovisiona una ultima región llamada región testigo, en la que ubica una réplica testigo que no puede leer ni escribir datos, pero que facilitan la obtención de quórumes para escrituras y líderes, sin el almacenamiento y los recursos de cómputo requeridos por las réplicas de lectura y escritura.

¿Y porque no se utiliza también en la configuración regional? Básicamente porque cuanto menor es el número de réplicas de votación y más cercan están entre ellas, más fácil es alcanzar un acuerdo de escritura y por tanto, menor es el tiempo de respuesta. Así, con tres réplicas de lectura y escritura se optimiza el rendimiento al mismo tiempo que se garantiza que si una zona o réplica fallara, las otras dos aún podrían formar un quorum de escritura. Todo ello con unas latencias de red de ida y vuelta de <1 ms en el percentil 95, según Google.

Pero en un escenario multiregión, si una de las regiones cayera, no seria posible alcanzar un acuerdo en la escritura ya que este debe ser mayor al numero total replicas/2+1. ¿Y porque no añadir una nueva región de lectura/escritura? Porque esto supondría aumento considerable de la cantidad de almacenamiento, recursos y tráfico de red, con el consecuente incremento de los costes.

En resumen, esta estrategia de replicación síncrona basada en Paxos permite que siempre se trabaje sobre la ultima versión de los datos, ya que todas las peticiones de lectura y escritura que requieren de consistencia son atendidas por la réplica líder. Además, en caso de que alguna de las replicas fallara, ya sea por un error en la zona o en el nodo, otra pasaría a tomar su lugar, sin que se pierdan datos por el camino.

Consistency

Tal y como se detallaba en la anterior sección, Cloud Spanner trabaja siempre con una consistencia fuerte 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).

Ahora bien, el factor principal que la diferencia del resto de bases de datos tradicionales monolíticas, es que es capaz de garantizar una consistencia externa para todas sus operaciones. Es decir, de cara al cliente las transacciones se procesan de forma secuencial, como si de una sola maquina se tratara, aunque internamente las ejecute en varios nodos ubicados distintas zonas o regiones. Consistente a la par que horizontalmente escalable.

Todo esto es posible gracias al TrueTime de Google, un reloj distribuido con alta disponibilidad, compuesto por relojes atómicos y GPS, con el que se asignan distintas marcas de tiempo a todas a las transacciones. Tal y como reza la documentación, esto permite que, si una transacción finaliza antes de que otra transacción comience a confirmarse, el sistema garantiza que los clientes nunca podrán ver un estado que incluya el efecto de la segunda transacción, pero no de la primera.

Veamos un ejemplo concreto. Si crear una nueva cuenta corriente en Cloud Spanner y recibes una transacción T1 para ingresar 100€, y acto seguido, una segunda transacción T2 para pagar 50€, la consistencia externa garantiza que nadie verá un estado en el que T2 ocurra antes de T1; dicho de otro modo, la cuenta corriente nunca habrá estado en números rojos. 

Como se comentaba previamente, esto solo es posible si dispones de un reloj atómico que te permita ordenar con máxima precisión todas las transacciones. Ahora bien, siendo estrictamente sinceros, los distintos nodos o servidores que conforman el servicio TrueTime pueden llegar a tener un desfase de 7ms, por lo que, antes de que una réplica pueda informar que se ha completado la escritura, debe esperar 7 ms. Esto permite que ninguna transacción posterior puede confirmarse en una marca de tiempo anterior, incluso si la transacción anterior se ejecuto en un nodo con esos 7ms de retardo. No parece una solución muy elegante, pero sí sencilla y funcional.

Llegados a este punto solo queda una duda por resolver. Se ha descrito como Cloud Spanner opta por un modelo de replicación síncrono basado en Paxos y como gracias al TrueTime es capaz de garantizar la consistencia externa, pero… ¿Cómo gestiona la concurrencia?

Para aquellas operaciones de lectura/escritura se ha optado por un bloqueo pesimista, concretamente por un 2PC a nivel de celda, siendo esta una columna particular de una fila en particular. ¿Y porque no decantarse por otra estrategia que minimice los bloqueos? Básicamente, porque en Google son firmes defensores de que sean los desarrolladores quienes se ocupen de los problemas de rendimiento debido a uso excesivo de transacciones, a medida que surgen cuellos de botella, en lugar de codificar siempre en torno a la falta de transacciones.

Sobra decir que las transacciones de solo lectura no llevan asociado bloqueo alguno, por lo que se recomienda encarecidamente realizar todas las transacciones de lectura que no involucren escrituras, en transacciones de solo lectura. (Si, aunque no se había mencionado previamente, es necesario especificar para cada transacción se se trata de una operación de lectura/escritura o solo lectura).

Al hilo de esto, se recomienda encarecidamente leer el siguiente articulo titulado Impossible read and write isolation phenomena with Cloud Spanner, en el que demuestra punto por punto, como Cloud Spanner es capaz de garantizar el control de concurrencia más estricto con consistencia externa.

Finalmente, la pregunta de rigor obligatoria. ¿Se trata entonces de una base de datos que cumple con la 3 capacidades del teorema de CAP? No, se trata de una BD CP (Consistent and Partition tolerant) altamente disponible, ya que las peticiones de lectura/escritura son atendidas solo por la replica líder y aunque Google ofrece una disponibilidad teórica del 99,999%, no es suficiente.

Conclusiones

En el presente artículo solo se ha llegado a escudriñar la superficie de Cloud Spanner y ojalá en posteriores artículos podamos profundizar mas en algunos de estos conceptos, pero lo que si es seguro es que la premisa parece los suficientemente interesante como para seguir su evolución con atención.

Ahora bien, seguro que a día de hoy muchas de las características o funcionalidades de las RDBMS tradicionales no están soportadas, por lo que se recomienda encarecidamente leer con detenimiento toda la documentación oficial antes de embarcase en este viaje.

Referencias

Se recomienda encarecidamente leer los siguientes artículos que han servido de base para el escrito:

  1. https://cloud.google.com/spanner/docs/
  2. https://dzone.com/articles/challenges-with-googles-cloud-spanner
  3. https://medium.com/@LightspeedHQ/google-cloud-spanner-the-good-the-bad-and-the-ugly-5795f37a7684
  4. https://research.google/pubs/pub39966/
  5. https://www.cockroachlabs.com/blog/living-without-atomic-clocks/
  6. https://www.ics.uci.edu/~cs237/project2020/spanner.pdf

2 thoughts on “Google Cloud Spanner in detail

  1. Excelente artículo Mikel, super detallado e interesante sobre las capacidades y características internas de Spanner. Por lo que veo, el coste por nodo por GB es unas 5 o 6 veces mayor que el equivalente en Cloud SQL, ¿cierto? No hay punto de comparación en capacidades en entornos distribuidos, pero hay que ser muy consciente y justificar bien la utilización de Spanner respecto a otros mecanismos de consistencia eventual para no inflar innecesariamente el coste operativo de la solución. Gracias por el post!

    Like

    1. ¡Muchísimas gracias Ariel!

      Totalmente de acuerdo, es un producto muy interesante a nivel tecnológico, pero requiere de un caso de uso muy especifico que justifique su inclusion.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s