La mayoría de nosotros podríamos hacer un primer intento con un modelo de base de datos relacional mientras dormimos.
Sin embargo, una vez que haya elegido trabajar con una base de datos de documentos, tendrá que pensar de forma un poco diferente.
No es más difícil, es sólo que estás optimizando para cosas diferentes.
Entonces, ¿qué hay que hacer para hacerlo bien?
Pues bien, hay tres principios que pueden ayudarle a orientar su pensamiento:
- Responda a las preguntas que sabe que le harán.
- Incrustar datos por comodidad, remitir por integridad.
- Nombra tus claves de forma previsible y semántica.
Hoy vamos a analizar en detalle el primero de ellos.
Preguntas, no respuestas
Cuando dividimos nuestros datos en las tablas, columnas y filas del modelo relacional, estamos optimizando la capacidad de consulta. Estamos construyendo una fuente de respuestas casi sin límites y aplazando nuestras decisiones sobre qué preguntas hacer.
Pongamos un ejemplo sencillo: un sistema de gestión de existencias que nos permita hacer un seguimiento del botín con la marca Couchbase.
En este sistema tenemos camisetas, memorias USB, bolígrafos y ese tipo de cosas. De vez en cuando recibimos pedidos para enviarlos a grupos de encuentro, conferencias y particulares.
Lo más probable es que eso nos diera las siguientes tablas relacionales:
- productos
- clientes
- pedidos
- detalles_pedido
A primera vista, Detalles del pedido puede parecer poco obvio. Sin embargo, nos permite almacenar referencias a todos los artículos de cada pedido sin romper la primera forma normal. De lo contrario, tendríamos que serializar las partidas de cada pedido en una cadena y almacenarla en una columna TEXT.
A menos que cometamos un error, cada producto, cliente y pedido aparecerá una sola vez en nuestra base de datos. Eso nos garantiza que las actualizaciones de los registros son universales y facilita enormemente la consulta de los datos de la forma que elijamos.
Compensaciones, siempre hay compensaciones.
De este modo, podemos almacenar los datos de nuestro sistema de gestión de botines en su forma más pura y consultarlos después de la forma que más nos convenga.
Entonces, ¿cuál es el problema?
Bueno, hay un par de compensaciones:
- Las consultas SQL son caras: por cada join hay una búsqueda en disco, hay sobrecarga de CPU, hay un usuario esperando a que se renderice su página.
- Las bases de datos relacionales son difíciles de escalar en un clúster.
Centrémonos de momento en la primera compensación: si se quiere ser poco amable, se podría decir que el modelado de datos relacionales es un ejercicio de robo de ciclos de CPU a tu futuro yo.
Conocemos la mayoría de nuestros patrones de consulta desde el principio y, sin embargo, a menudo dedicamos tiempo a despojar cuidadosamente a nuestros datos de ese contexto. Dividimos los datos una vez y luego pasamos el resto de la vida útil de nuestras aplicaciones pidiendo al servidor de base de datos que los recomponga.
Por supuesto, eso tiene su lugar, pero es casi lo contrario del camino más eficiente con una base de datos de documentos.
No hagas esperar a la gente
Lo primero que hacemos al modelar una base de datos documental es preguntarnos: "¿qué preguntas quiero hacer a mis datos?".
Luego, cuando cambia el estado de nuestro sistema, calculamos las respuestas a esas preguntas y las almacenamos previamente en la base de datos.
En lugar de repasar las respuestas cada vez que realizamos una consulta, extraemos la respuesta completamente formada de la base de datos en una sola consulta.
¿Qué significa eso en la práctica?
Volvamos a nuestro ejemplo de gestión del botín. Una de nuestras preguntas sería:
¿Qué tengo que hacer para satisfacer un pedido concreto?
Con una base de datos relacional, escribiríamos una consulta SQL que buscaría el pedido, utilizaría una unión para encontrar los artículos del pedido, luego otra unión para encontrar el detalle de cada artículo y otra unión para encontrar los detalles del cliente.
Con Couchbase, sería algo más parecido a esto:
- El usuario selecciona sus artículos y realiza su pedido.
- Nuestro sistema escribe el pedido en la base de datos como un documento único.
- Cuando necesitamos sacar los detalles del pedido, es una lectura para coger el lote.
El documento de pedido resultante podría tener este aspecto:
"orderID": 200,
"cliente":
{
"Nombre": "Matthew Revell",
"address": "11-21 Paul Street",
"ciudad": "Londres"
},
"productos":
[
{
"itemCode": "RedTShirt",
"itemName": "Camiseta roja Couchbase",
"quantityOrdered": 3
},
{
"itemCode": "USB",
"itemName": "Memoria USB negra de 8 GB con el logotipo rojo de Couchbase",
"quantityOrder": 51
}
],
"status": "pagado"
}
Este ejemplo está muy desnormalizado. Nuestro sistema de gestión de botín ya tendría detalles de cada cliente y producto en documentos separados, pero estamos repitiendo lo que sabemos sobre ellos incrustando sus detalles aquí.
En una próxima entrada analizaremos las ventajas y desventajas de incrustar datos o hacer referencia a ellos.
La producción es más compleja
En un sistema de producción, lo más probable es que generemos varias de esas respuestas ya preparadas. Sin pensarlo mucho, probablemente se nos ocurran unas cuantas para nuestro sistema de gestión de botines:
- Historial de pedidos de clientes: los clientes quieren consultar todo lo que han pedido, así que registraríamos este pedido contra esa persona.
- Estado del pedido en tiempo real: del mismo modo, nuestros clientes querrán ver el estado de su pedido, por lo que podemos generar esa información ahora y actualizarla cuando haya algún cambio.
- Instrucciones de envío: tendremos que decir a la gente de nuestro almacén lo que tienen que enviar y a dónde.
Lo importante es que no tenemos que generar todo esto mientras un ser humano espera una respuesta.
Si bien es posible que deseemos actualizar el estado del pedido en vivo del cliente de inmediato, para que pueda verificar que su pedido fue registrado, está bien procesar la instrucción de envío de forma asincrónica.
De este modo, cuando un humano consulta algo en nuestro sistema, los datos ya están ahí esperando.
En resumen: calcula previamente tus respuestas
El primer paso para modelizar eficazmente una base de datos documental es pensar en precalcular las respuestas.
Sabemos de antemano qué preguntas queremos plantear y, por lo tanto, podemos adaptarnos a ellas a la hora de redactar los datos.
Aunque la idea de una representación pura, normalizada y matemáticamente sólida de nuestros datos tiene un gran atractivo intelectual, en la práctica puede dificultar el servicio a múltiples usuarios simultáneos y la ampliación de nuestras operaciones a medida que cambia la demanda.
Calculando nuestras distintas respuestas en el momento de la escritura:
- eliminar los retrasos de nuestra experiencia de usuario
- puede distribuir más fácilmente los datos en un clúster
- se obtiene la ventaja añadida de un menor desajuste entre el estado del objeto y lo que hay en la base de datos.
La próxima vez analizaré cuándo incrustar datos y cuándo remitirlos.
Espero que explores al menos un caso simbólico no trivial en esta serie, como darte cuenta a posteriori de que necesitas ver qué cantidad de cada artículo pedir (map-reduce incremental quizás).
¿Cómo va a funcionar bien la actualización de la información de un cliente si la tiene almacenada en 8 millones de sitios diferentes?
Si se hace referencia a los mismos datos en ocho millones de lugares diferentes, y nunca deben desincronizarse, entonces es casi seguro que es mejor mantener un registro canónico de esos datos y hacer referencia a ellos desde cada uno de los ocho millones de lugares en los que se necesitan.
Si tuvieras un gran número de copias de esos datos que tuvieran que permanecer en documentos separados, y también necesitaras mantener la coherencia, entonces la ruta más fácil sería una consulta N1QL, sospecho.
Entonces, ¿hay una Parte II?
La segunda parte está aquí: https://www.couchbase.com/data-modelling-when-embed-or-refer/