Revisión de CockroachDB: una base de datos SQL escalable construida para la supervivencia

Hasta hace muy poco, cuando buscaba una base de datos, tenía que elegir: ¿escalabilidad o consistencia? Las bases de datos SQL como MySQL garantizan una sólida coherencia, pero no se escalan bien horizontalmente. (La fragmentación manual para la escalabilidad no es una idea divertida de nadie). Las bases de datos NoSQL como MongoDB se escalan maravillosamente, pero ofrecen solo una consistencia eventual. ("Espere lo suficiente y podrá leer la respuesta correcta", que no es una forma de hacer transacciones financieras).

Google Cloud Spanner, un servicio de base de datos relacional completamente administrado que se ejecuta en Google Compute Engine (GCE) lanzado en febrero de 2017, tiene la escalabilidad de las bases de datos NoSQL al tiempo que conserva la compatibilidad con SQL, los esquemas relacionales, las transacciones ACID y una sólida coherencia externa. Spanner es una base de datos relacional fragmentada, distribuida globalmente y replicada que utiliza un algoritmo de Paxos para llegar a un consenso entre sus nodos.

Una alternativa a Spanner, y el tema de esta revisión, es CockroachDB, una base de datos SQL distribuida de código abierto y escalable horizontalmente desarrollada por ex-Googlers que estaban familiarizados con Spanner. CockroachDB toma prestado de Spanner de Google para el diseño de su sistema de almacenamiento de datos, y utiliza un algoritmo Raft para llegar a un consenso entre sus nodos.

Al igual que Cloud Spanner, CockroachDB es una base de datos SQL distribuida construida sobre un almacén de valores clave transaccional y coherente, en el caso de CockroachDB en RocksDB. Los principales objetivos de diseño de CockroachDB son el soporte para transacciones ACID, la escalabilidad horizontal y (sobre todo) la supervivencia, de ahí el nombre.

CockroachDB está diseñado para sobrevivir a fallas de disco, máquina, rack e incluso centros de datos con una interrupción mínima de la latencia y sin intervención manual. Por supuesto, para lograr eso, debe ejecutar un clúster de muchas instancias de nodos simétricos de CockroachDB, utilizando varios discos, máquinas, racks y centros de datos.

A diferencia de Cloud Spanner, que utiliza la API TrueTime disponible para la sincronización horaria en los centros de datos de Google, CockroachDB no puede contar con la presencia de relojes atómicos y relojes satelitales GPS para sincronizar la hora con precisión entre nodos y centros de datos. Eso tiene varias implicaciones. Para empezar, Google TrueTime da un límite superior para las compensaciones de reloj entre los nodos en un grupo de siete milisegundos. Eso es lo suficientemente pequeño como para que un nodo Spanner solo espere siete milisegundos después de una escritura antes de informar que se ha comprometido una transacción, para garantizar la coherencia externa.

Sin TrueTime o una instalación similar, CockroachDB debe recurrir a NTP, que proporciona límites superiores en la sincronización del reloj entre 100 milisegundos y 250 milisegundos. Dada esa ventana de tiempo más grande, CockroachDB no espera después de escribir. En cambio, a veces espera antes de leer, básicamente reiniciando una transacción si lee un valor con una marca de tiempo mayor que el comienzo de la transacción, nuevamente para garantizar la coherencia.

Cuando todos los nodos de un clúster de CockroachDB tienen los límites superiores pequeños para las compensaciones de reloj que puede obtener de GPS o relojes atómicos, que es algo que acaba de estar disponible en las principales nubes públicas, puede tener sentido ejecutarlos con la --linearizable bandera. Eso los hace esperar el desplazamiento máximo del reloj antes de devolver una confirmación exitosa, al igual que Spanner.

Cómo funciona CockroachDB

Cada nodo de CockroachDB consta de cinco capas:

  • SQL, que traduce las consultas SQL del cliente en operaciones de valor clave
  • Transacción, que permite cambios atómicos en múltiples entradas de valor-clave
  • Distribución, que presenta rangos de clave-valor replicados como una sola entidad
  • Replicación, que replica de manera consistente y sincrónica los rangos de clave-valor en muchos nodos y permite lecturas consistentes mediante concesiones
  • Almacenamiento, que escribe y lee datos de valor-clave en el disco

La capa SQL analiza las consultas en un archivo Yacc y las convierte en un árbol de sintaxis abstracto. A partir del árbol de sintaxis abstracta, CockroachDB genera un árbol de nodos del plan, que contienen código clave-valor. Luego, se ejecutan los nodos del plan, comunicándose con la capa de transacciones.

La capa de transacciones implementa la semántica de transacciones ACID con confirmaciones de dos fases en todo el clúster, incluidas las transacciones de rango cruzado y de tabla cruzada, que tratan declaraciones individuales como transacciones (también llamado modo de confirmación automática). La confirmación de dos fases se logra mediante la publicación de registros de transacciones y los intentos de escritura, la ejecución de operaciones de lectura y el tratamiento de los intentos de escritura encontrados como conflictos de transacciones.

La capa de distribución recibe solicitudes de la capa de transacciones en el mismo nodo. Luego identifica qué nodos deben recibir la solicitud y envía la solicitud a la capa de replicación del nodo correspondiente.

La capa de replicación copia datos entre nodos y garantiza la coherencia entre estas copias mediante la implementación de un algoritmo de consenso de Raft. Puede controlar el factor de replicación a nivel de clúster, base de datos y tabla mediante zonas de replicación. El algoritmo de consenso se usa solo para escrituras y requiere que un quórum de réplicas esté de acuerdo con cualquier cambio en un rango antes de que se confirmen esos cambios.

La capa de almacenamiento almacena datos como pares clave-valor en el disco usando RocksDB. CockroachDB se basa en gran medida en el control de concurrencia de múltiples versiones (MVCC) para procesar solicitudes concurrentes y garantizar la coherencia. Gran parte de este trabajo se realiza mediante el uso de marcas de tiempo de reloj lógico híbrido (HLC).

Al igual que Spanner, CockroachDB admite consultas de viaje en el tiempo (habilitadas por MVCC). Estos pueden remontarse hasta la recolección de basura de la base de datos más reciente, realizada de manera predeterminada a diario.

Instalación y uso de CockroachDB

CockroachDB se ejecuta en sistemas operativos Mac, Linux y Windows, al menos para desarrollo y prueba. Las bases de datos de producción de Cockroach generalmente se ejecutan en máquinas virtuales Linux o contenedores orquestados, a menudo alojados en nubes públicas en múltiples centros de datos. El binario de Windows de CockroachDB todavía se encuentra en una fase beta y no se recomienda para producción, y Apple ya no fabrica hardware de servidor.

Laboratorios de cucarachas

Como puede ver en la captura de pantalla anterior, hay cuatro opciones para instalar CockroachDB en una Mac. Elegí Homebrew como probablemente el más fácil de los cuatro.

Por cierto, Cockroach Labs publica una advertencia en el sitio que dice que ejecutar una aplicación con estado como CockroachDB en Docker es complicado, no se recomienda para implementaciones de producción y utilizar una herramienta de orquestación como Kubernetes o Docker Swarm para ejecutar un clúster. Analizaré las opciones de orquestación de contenedores en la siguiente sección.

La instalación en una Mac es tan fácil brew install cockroachcomo se muestra arriba. En mi caso, la actualización automática de Homebrew tomó mucho más tiempo (tiempo suficiente para preparar té) que la instalación real de CockroachDB, que solo tomó unos segundos.

Una vez que tenga CockroachDB instalado, es bastante fácil activar un clúster local, aunque existe el paso adicional de generar certificados de seguridad con el cockroach certcomando si desea que el clúster sea seguro. Una vez que tenga un clúster en ejecución (usando el cockroach startcomando una vez para cada nodo, con los nodos subsiguientes configurados para unirse al clúster del primer nodo), puede conectarse a la página de administración web en cualquier nodo con un navegador y usar el cockroach sqlcliente integrado para enviar SQL consultas a cualquier nodo. La mayoría de los navegadores se quejarán de los sitios con certificados generados por CockroachDB, pero debería poder hacer clic en esa advertencia y continuar en el sitio.

La configuración de producción recomendada de CockroachDB es diferente a los valores predeterminados, que se configuraron para instancias de desarrollo y prueba. Puede desarrollar en un clúster de un nodo si lo desea. Para la producción, debe tener un mínimo de tres nodos, ejecutar cada nodo en una máquina, VM o contenedor por separado, y darle a cada instancia una memoria caché y SQL adicional. La configuración predeterminada es de 128 MB para caché y memoria SQL; la configuración de producción recomendada es dar a cada uno el 25 por ciento de RAM:

cockroach start --cache=25% --max-sql-memory=25%

Cuantos más nodos ejecute, mejor será la resistencia. Cuanto más grandes y rápidos sean los nodos, mejor será el rendimiento. Si desea tener nodos con un rendimiento aproximadamente comparable a los nodos de Google Cloud Spanner, que entregan 2,000 escrituras por segundo y 10,000 lecturas por segundo, entonces querría algo como las instancias n1-highcpu-8 de GCE, que tienen ocho CPU y 8 GB de RAM , con discos SSD locales (en lugar de discos giratorios).

Cuanto más distribuya sus nodos a diferentes centros de datos, mejor podrá garantizar la inmunidad a las fallas a nivel del centro de datos. Sin embargo, existe un costo: la latencia de ida y vuelta entre los centros de datos tendrá un efecto directo en el rendimiento de su base de datos, y los clústeres entre continentes se desempeñan notablemente peor que los clústeres en los que todos los nodos están geográficamente cerca.

Cockroach Labs proporciona instrucciones detalladas para la implementación en AWS, Digital Ocean, GCE y Azure. Las configuraciones recomendadas utilizan equilibradores de carga, ya sea los servicios de equilibrio de carga gestionados nativos o equilibradores de carga de código abierto como HAProxy.

La orquestación puede reducir la sobrecarga operativa de un clúster de CockroachDB a casi nada. Cockroach Labs documenta cómo hacer esto para la producción con Kubernetes y Docker Swarm. El repositorio de CockroachDB-CloudFormation en GitHub muestra cómo usar AWS CloudFormation y Kubernetes en una única zona de disponibilidad para desarrollo y prueba. Adaptar esto para producción implicaría modificar la plantilla de CloudFormation para usar múltiples zonas de disponibilidad.

Programación y prueba de CockroachDB

CockroachDB admite el protocolo de cable PostgreSQL, por lo que escribe su código como si estuviera programando contra Postgres, o al menos un subconjunto de Postgres. Esta página enumera los controladores probados para varios enlaces de lenguaje de programación, incluidos los lenguajes del lado del servidor más populares. Esta página enumera ejemplos en 10 lenguajes de programación y cinco ORM. No encontré grandes sorpresas cuando leí el código, aunque detecté algunos errores menores probables en los listados dentro de la documentación. También puede ejecutar su SQL utilizando el cliente interactivo integrado en el cockroachejecutable.

Si bien hay un repositorio dedicado a los generadores de carga de CockroachDB y otro para las pruebas de rendimiento, la evaluación comparativa de los clústeres de CockroachDB no es fácil, especialmente si está tratando de comparar CockroachDB con otras bases de datos de una manera significativa. Un problema es que la red entre los nodos puede ser el paso que limita la velocidad en los clústeres de CockroachDB.

Otro hecho a tener en cuenta es que la mayoría de las bases de datos SQL convencionales no se ejecutan en modo de aislamiento SERIALIZABLE de forma predeterminada; en su lugar, utilizan un modo menos estricto con mejor rendimiento. CockroachDB utiliza el modo de aislamiento serializable de forma predeterminada. Además, sería un poco injusto probar el rendimiento de unión SQL de CockroachDB, que todavía es un trabajo en progreso, con la suite TPC-C.

Y, sin embargo, puede ver fácilmente el poder operativo de CockroachDB. Por ejemplo, muchas bases de datos deben detenerse y reiniciarse para escalarlas. Agregar nodos bajo carga en CockroachDB es muy sencillo, especialmente si está utilizando una herramienta de orquestación. Por ejemplo, la captura de pantalla anterior muestra los comandos para cambiar y mostrar los nodos en un clúster de Kubernetes, y la captura de pantalla siguiente muestra el clúster monitoreado a medida que se agregan los nodos. Una herramienta de generación de carga se ejecutó continuamente durante todo el proceso.

Una demostración aún más impresionante muestra la migración automática entre nubes dentro de un clúster de CockroachDB. Realmente requiere video para hacerle justicia; el video está alojado en la publicación del blog vinculada.

CucarachaDB SQL 

SQL en CockroachDB es más o menos estándar, a diferencia de SQL en Cloud Spanner, que usa una sintaxis no estándar para la manipulación de datos. Sin embargo, a CockroachDB SQL todavía le faltan muchas características.

Por ejemplo, V1.1 carece de soporte JSON, que está previsto para V1.2. También carece de análisis XML, que no está en la hoja de ruta. Carece de cascadas a nivel de fila, planificadas para V1.2, y carece de cursores y activadores, que no están en la hoja de ruta. Los índices geoespaciales son adiciones "potenciales" que pueden figurar en la hoja de ruta en el futuro.

En particular, la implementación inicial de CockroachDB de las uniones SQL en 2016 fue deliberadamente simplista y mostró una escala cuadrática, lo que la hace inútil para consultar grandes conjuntos de datos. La versión en V1.0, realizada por un estudiante cooperativo, implementó combinaciones hash, haciendo que muchas operaciones de combinación escalen linealmente; que puso a CockroachDB al nivel de SQLite. En algún momento de 2018, dada una ronda reciente de financiación, CockroachDB debería tener un rendimiento de unión que se adapte más a las uniones de PostgreSQL, así como el procesamiento de unión de SQL distribuido en el clúster.