Database sharding (II)

Particionando la base de datos

En una entrada anterior explicaba el particionamiento de base de datos, que consiste en separar "un mogollón" de datos almacenados en una única base de datos en otras más pequeñas, que puedan instalarse en servidores independientes (más pequeños y más baratos) para mejorar la fiabilidad y la escalabilidad de un sistema.

En esta entrada investigo un poco en los factores de decisión para realizar el particionamiento de datos.

Las particiones deberían ser lo más independientes entre sí como sea posible.

No me grites que no te veo

Una "transacción" viene a ser una operación atómica para guardar información. Por ejemplo, si un usuario compra un producto nos gustaría que esa información relativa a la compra quedase almacenada permanentemente, ¿no? Al fin y al cabo no nos gustaría que nuestros usuarios dejen de recibir las compras en casa porque no hemos grabado bien la información, ¿verdad? Las transacciones nos permiten almacenar esta información de forma atómica, de modo que la información no se pierde durante el proceso de grabado en la base de datos. Por ejemplo, al realizar la compra podemos grabar la compra en sí (en la "tabla de compras") y al mismo tiempo podemos reducir la cantidad de elementos comprados en el stock (en la "tabla de stock"). Una transacción permitiría que ambas modificaciones a la base de datos se realizaran atómicamente, de modo que un fallo en el proceso no dejase información inconsistente en la base de datos..

La mayoría de las bases de datos son muy buenas grabando esta información de forma atómica, porque tienen un control completo del ordenador donde se han instalado (de su memoria, de su disco, etcétera), y son capaces de detectar fallos y de gestionarlos de modo adecuado de manera que la información sea siempre consistente.

Pero si separamos la base de datos en diferentes particiones (shards) más pequeñas entonces grabar la información de forma consistente es mucho más difícil: tendríamos que hacer una única transacción que afecte a diferentes particiones, y eso es más difícil hacerlo bien, y es mucho más lento que hacerlo en una única partición. Además necesitaríamos utilizar un gestor de transacciones específico (que soporte el estándar X/Open XA de transacciones distribuidas, por ejemplo) que sea capaz de coordinar la transacción entre diferentes particiones: una complicación y más coste.

Esta importante implicación técnica del particionamiento de bases de datos tiene una repercusión muy importante en el diseño del particionamiento. Quizá el primer consejo que pueda darse si se va a hacer un particionamiento es que se siga la política del "no me grites que no te veo", esto es, que las particiones sean lo más independientes posible entre sí:

Estrategia no me grites que no te veo

Particionar la base de datos de forma que la información de cada partición (shard) sea lo más independiente posible del resto de particiones. Si va a particionar una base de datos siga la estrategia de "share nothing", o "compartir nada".

Consecuencias del no me grites que no te veo. Casos prácticos: Amazon.

La estrategia del "no me grites que no te veo" (es decir, de hacer que las particiones compartan lo menos posible) tiene a su vez importantes implicaciones en nuestro modelo de datos. Veámoslo con un (hipotético) ejemplo: Amazon.

La transacción principal de Amazon es "El usuario X compra el producto Y", es decir, nuestro modelo de datos constaría de "usuarios", "productos" y "compras" ¿cómo podemos particionar su base de datos?

El primer paso consiste, quizá, en tener tiendas independientes por zona geográfica. Efectivamente, Amazon tiene tiendas en España, EEUU y Reino Unido, por ejemplo, por lo que podríamos separar éstas en particiones independientes. Es decir, podríamos transformar nuestra transacción original en "El usuario X compra el producto Y en la tienda de España", o "El usuario X compra el producto Y en la tienda de EEUU", y cada una de estas tiendas geográficas sería susceptible de convertirse en una partición, o "shard".

El problema, entonces, es que estaríamos compartiendo los usuarios entre las diferentes tiendas, y eso es un problema si el usuario cambia de tienda (porque el usuario, por ejemplo, puede cambiar de domicilio y emigrar de España a EEUU). Aplicar la estrategia del "no me grites que no te veo" implica que necesitamos tener el concepto de "cuenta de usuario", de modo que un usuario X pueda tener una cuenta en la tienda de España y otra cuenta en EEUU, por ejemplo, consiguiendo así mejorar la independencia entre las tiendas.

Es decir, nuestra transacción original se transformaría, por ejemplo, en: "El usuario X utiliza la cuenta de España para comprar el producto Y en la tienda de España". De este modo podríamos tener una partición para la tienda de España, con sus cuentas de usuarios en España y sus productos españoles, independiente totalmente de la partición de EEUU o de la partición de UK.

La estrategia del "no me grites que no te veo" afecta, entonces, a cómo diseñamos la base de datos. También, obviamente, afecta a cómo construimos las aplicaciones, por lo que es importante valorar (siempre al principio) si el esfuerzo de utilizarla compensa la mejora en la escalabilidad de las aplicaciones.

Un caso de estudio doloroso

Para terminar, y para no enrollarme más, me gustaría incluir un enlace a este ejemplo real, en inglés (o en pseudo-español, con el traductor de Google) con experiencias prácticas de la aplicación de particionamiento en la base de datos, en el que se aprecian los dolorosos problemas que conlleva no usar la técnica del "no me grites que no te veo" en el diseño de particiones.

En el caso de estudio no se aplicó la estrategia del "no me grites que no te veo" y las transacciones en la base de datos se bloquearon entre sí, dejando el sistema bloqueado. Sin duda estos señores eligieron un incorrecto nivel de aislamiento transaccional, que causó los bloqueos innecesarios, pero la causa raíz del problema es que su modelo de datos les obligaba a realizar transacciones entre múltiples particiones. Y eso es un doloroso error.

Como esta entrada se queda muy larga quizá otro día comente sobre herramientas disponibles para realizar sharding de manera menos dolorosa.