Sin categoría

Couchbase @ Ziniki : La odisea de encontrar "Find

Ziniki Infrastructure Systems construyó su nivel de integración sobre Couchbase, porque la combinación de almacenamiento de documentos con mapreduce incremental les proporcionó una forma potente de consultar datos. En este blog, Gareth Powell, fundador y arquitecto de Ziniki, describe su experiencia en el uso de vistas mapreduce en Couchbase. 


Tengo una gran experiencia en SQL y algo en MongoDB. Debido a que gran parte de SQL se basa en uniones y la sentencia "SELECT", ingenuamente supuse que habría una funcionalidad similar de "búsqueda" en Couchbase como en MongoDB, que había utilizado brevemente en otro proyecto. Al principio me sorprendió ver que no era así.

El problema

La razón por la que decidí usar Couchbase para este proyecto fue su capacidad de crear índices complejos en segundo plano usando su tecnología mapreduce incremental. Hablaré un poco más de esto en la siguiente sección, pero antes de entrar en eso, déjame describir primero lo que estaba tratando de construir.

Estoy construyendo una capa de middleware sobre Couchbase. Este nivel tiene conocimiento sobre los usuarios, sus credenciales, identidades, datos personales, requisitos de seguridad y similares. También es consciente de la aplicación y tiene lógica y reglas para controlar el acceso de la aplicación a los datos de cualquier usuario individual.

Para que todo esto funcione, es necesaria una "capa de definición de datos" que describa el dominio de datos de la aplicación. Yo utilizo XML para ello, pero cualquier cosa, desde diagramas UML o herramientas de diseño visual hasta JSON, sería aceptable. Lo importante es que todas las definiciones tengan un significado inequívoco y que no se planteen cuestiones delicadas como "el problema de la parada". También es importante que la representación sea esencialmente neutra en cuanto al lenguaje y pueda utilizarse para generar definiciones para cualquier lenguaje de programación o clase de activo que pueda estar implicado. Por ahora, puedes pensar que esto equivale a un conjunto de definiciones de clases en tu lenguaje de programación orientado a objetos favorito.

Debido a la naturaleza abstracta de mi modelo de datos, opté por utilizar claves únicas globales (UUID) como claves para los documentos almacenados en Couchbase en lugar de codificar a mano las claves basadas en los datos. Esto me permitió generar una clave exactamente una vez. La clave identificará el objeto de forma única y será su identidad principal sin importar cuánto pueda cambiar el objeto.

El otro aspecto de mi modelo de datos es que asume que los datos pueden ser "grumosos", es decir, que será muy común definir objetos compuestos como grupos de objetos altamente interconectados, que están todos conectados al grafo de objetos principal a través de un representante que también es responsable de manejar los aspectos de seguridad del grumo.

Mecanismos de almacenamiento de Couchbase

Couchbase define la noción de "vistas", que son increíblemente poderosas y se pueden utilizar de muchas maneras diferentes. En gran parte, sin embargo, esta entrada de blog es una advertencia contra su uso para cosas para las que no deberían usarse.

Todo en Couchbase está diseñado de tal manera que funciona eficazmente "a escala". Esto difiere de la mayoría de los otros sistemas que usarás, los cuales están definidos alrededor de un conjunto particular de semántica (teoría relacional, por ejemplo) y luego metidos con calzador hasta que pueden operar efectivamente a escala (como con Star Schema en bases de datos analíticas). La consecuencia de esto es que las garantías que te ofrece Couchbase son el conjunto mínimo que puede ofrecer a escala masiva. Como en todo sistema escalable, todo está desacoplado. Si planeas - como yo - ofrecer un sistema masivamente escalable, no tiene sentido que te quejes de estas restricciones. Tarde o temprano te encontrarás con estos límites y, en realidad, sólo es cuestión de saber cómo afrontar las consecuencias. Mi consejo es que dejes todo lo que puedas a Couchbase y selecciones el mecanismo de acceso que mejor se adapte a tu aplicación. Esencialmente, hay dos mecanismos de acceso en Couchbase -

El almacén de claves/valores es esencialmente sincrónico: en un único conjunto de operaciones, puede asegurarse de que la asignación de un único valor a una clave específica se ha realizado correctamente, de forma única o atómica. Esto le permite asegurarse de que cualquier operación que realice sólo tenga éxito si se aplican las restricciones de unicidad de la clave.

La consulta de documentos mediante vistas es asíncrono y finalmente consistente: es decir, las vistas se actualizan en algún momento (posiblemente lejano) de cuando usted pidió que se hiciera un cambio, pero en última instancia, si deja de hacer algo en el sistema, se "pondrán al día" y cuando (finalmente) lo hagan, el sistema será 100% coherente.

Estos dos mecanismos ofrecen otras semánticas diferentes. Por ejemplo, mientras que el almacén clave-valor exige que las claves tengan valores distintos, las vistas no. La función de mapeo de una vista puede crear tantas claves como quiera con los mismos valores; las vistas también admiten claves multiparte con una riqueza que el simple almacén clave-valor no permite.

Por último, los dos interactúan, en el sentido de que la entrada a una vista es exactamente el conjunto de documentos que se encuentran en el almacén de claves/valores.

Definición de vistas

Las vistas en Couchbase se definen usando un par de funciones map y reduce. La función reduce es opcional y simplemente está presente para permitir que múltiples filas de una vista sean "colapsadas" en una sola fila. Como para los propósitos de este artículo, estoy principalmente interesado en usar vistas para crear un "índice" de objetos en Couchbase, no discutiré más los métodos "reduce", pero si quieres hacer cosas excitantes (como análisis de datos), vale la pena echarles un vistazo en la documentación.

Las funciones map y reduce en Couchbase están definidas en JavaScript y lógicamente son "llamadas" cuando se necesitan. Para depurar es posible definir un entorno sencillo en Chrome o Rhino que envuelva estas funciones y permita ver cómo operarían "semánticamente" fuera de Couchbase. Sin embargo, este no es directamente el mecanismo que se utiliza para implementar la creación de vistas dentro de Couchbase: más bien, las actualizaciones se retrasan, se agrupan y se procesan por lotes para lograr el máximo rendimiento a escala. Además, las actualizaciones se desglosan por nodo servidor y pueden producirse en cualquier orden.

Obviamente, se trata de un mecanismo increíblemente potente para definir un índice, sobre todo en comparación con los mecanismos SQL estándar. Por ejemplo, si tienes un atributo en un documento que es un objeto array es posible calcular y poner la longitud (o de hecho la suma) del objeto en uno de los atributos.

Desde mi punto de vista, uno de los problemas de este enfoque es que las funciones operan en una "caja de arena", es decir, cada documento que se presenta a la función de mapa ha sido separado de su contexto. Si los datos son desiguales, esto puede ser un problema, pero en una visión del mundo escalable y estrictamente compartida, este enfoque tiene mucho sentido. Dado que los documentos están en hash en diferentes servidores, recuperarlos durante el procesamiento por lotes de JavaScript sería relativamente caro e ineficiente.

Acceso a las vistas

Una vez definida una vista mediante estas funciones JavaScript, es bastante fácil acceder a las vistas por rango de teclas, ya sea directamente mediante HTTP o a través de uno de los muchos enlaces de cliente. En mi proyecto, utilizo la biblioteca de cliente Java, que abre las vistas que estoy utilizando y, a continuación, realiza consultas basadas en las definiciones abstractas proporcionadas en mi archivo XML de definiciones. El problema de la coherencia "eventual" Una vez definidos los índices de mis datos, he intentado acceder a ellos para recuperar los datos almacenados. Dado que había optado por utilizar UUID semánticamente irrelevantes como claves en el almacén de claves/valores, había renunciado a la oportunidad inmediata de encontrar algo allí utilizando una clave "natural", y opté por utilizar el mecanismo de índices para generar estas claves "naturales". Pero rápidamente descubrí que tenía casos de uso que no encajaban bien con la semántica de la vista.

Credenciales

El primer problema que me encontré fue con las credenciales. Couchbase actualiza sus vistas después de una cierta cantidad de actividad o una cierta cantidad de tiempo, lo que ocurra primero. Aunque eso probablemente sería suficiente para casos de uso de la vida real, nuestras pruebas se basaban en scripts repetibles y automatizados. El primer guión que escribí simulaba que un usuario se registraba en el sistema e inmediatamente se daba la vuelta y se conectaba. Utilizaba el mecanismo de vista para extraer las credenciales "únicas" del usuario (mecanismo de inicio de sesión e identificador de inicio de sesión) y asignarlas al UUID de las credenciales. Sin embargo, cuando fui a buscar esto desde la vista después de crear la credencial, todavía no había llegado al índice. He intentado utilizar la opción "stale" en la vista, pero para las operaciones de inicio de sesión, esto puede ser costoso (normalmente alrededor de 2,5s para hacer la consulta).

Artefactos generados

Otro problema relacionado era el de los artefactos que generaba durante el procesamiento de las peticiones de los usuarios. Estos artefactos "recordaban" interacciones anteriores del usuario y permitían al sistema responder adecuadamente. En cada caso, el artefacto tenía una clave "natural" única que reflejaba el usuario, la operación que estaba realizando y el objeto sobre el que la estaba realizando. Utilicé vistas para rastrearlos y volver a cargarlos cuando el mismo usuario realizaba la misma operación en el mismo objeto en una fecha posterior.

Me encontré con el mismo problema con la coherencia final: a la velocidad a la que se ejecutaban mis scripts de prueba automatizados, emitía la segunda solicitud antes de que el objeto creado en la primera hubiera llegado al índice.

Documentos anidados

El tercer caso que me encontré fue el de los documentos de un mismo "grupo". Una vez encontrado el principal -el que se estaba "asegurando" por usuario-, quería navegar a algunos de los otros objetos de la misma "vecindad". Definí una vista que describía el tipo de objetos que quería encontrar e incluía el ID del objeto original. Buscando en este índice el ID de objeto que tenía y las características que buscaba, creí que podría recuperar todos los documentos del grupo.

De nuevo, la velocidad a la que mis pruebas generaban los documentos y luego intentaban acceder a ellos me estaba matando. Creaba los objetos, me daba la vuelta e intentaba acceder a la vista, sólo para descubrir que seguía vacía. Unos segundos más tarde, cuando intentaba diagnosticar el problema a través de la interfaz de usuario, los objetos estaban allí, pero el índice aún no se había actualizado.

La primera solución: Duplicar los índices

A medida que experimentaba con Couchbase, reconocí la diferencia entre el almacén de claves/valores y el mecanismo de vistas. Mi primer intento de resolver este problema fue simplemente duplicar todo el trabajo que Couchbase estaba haciendo en la vista pero en "tiempo real" en el almacén de claves/valores. En realidad, esto no fue una carga tan grande como podría parecer: como todas mis definiciones de datos estaban en forma abstracta, estaba generando las definiciones de la vista y era relativamente fácil extender esto para generar el mismo código en Java para almacenar los elementos en el almacén de claves/valores.

Esto solucionó mis problemas, pero nunca me gustó por varias razones. La más obvia, la ineficaz duplicación me hizo cuestionar la elección de Couchbase. Pero lo más importante es que el número de casos diferentes que surgían en el código sugerían que estaba confundiendo cuestiones. Las dos bifurcaciones más importantes eran la diferencia entre índices "únicos" y "no únicos"; y la diferencia entre índices que necesitaban considerar contextos de seguridad y los que no.

La segunda solución: Reconocer la naturaleza individual de las cosas

La servicial gente de Couchbase me indicó el patrón "lookup" en la documentación de Couchbase, que describía en detalle un problema muy similar al que yo tenía con las credenciales.

El patrón de búsqueda describe cómo utilizar una indirección dentro del almacén de claves/valores para tener esencialmente un único objeto con múltiples claves. Hay una clave única (el UUID en mi caso) y todas las demás claves secundarias apuntan a ella. Pude reelaborar mis definiciones de índice para distinguir entre el caso en el que quería una vista que pudiera soportar múltiples filas con la misma clave, y el caso en el que quería sólo una fila con cualquier clave dada. Para ello, especifiqué cómo se podía construir una clave única a partir de los campos del objeto de datos, que luego se utilizaba como una clave de búsqueda que apuntaba al UUID del objeto.

Esto resolvió los dos primeros retos utilizados anteriormente. Para las credenciales, pude utilizar el "mecanismo de credenciales" (basic, OpenId, OAuth, etc.) y el identificador único de inicio de sesión del usuario con ese mecanismo como clave única; para los artefactos, pude utilizar la combinación de identificador de usuario, operación y UUID del objeto. En cada caso, añadí automáticamente el hecho de que se trataba de una clave secundaria y el tipo de objeto indexado.

El tercer reto era de naturaleza diferente y requería una solución distinta, pero de nuevo utilizar una vista había sido la elección equivocada. En este caso, el conjunto de identificadores de objeto únicos que había que tener en cuenta estaba contenido en la definición de objeto real que ya tenía en memoria. En lugar de buscar los objetos en una vista, el enfoque correcto era leer cada uno de estos objetos desde el almacén de claves/valores utilizando sus UUID y ver cuáles tenían las características apropiadas.

Aunque no está claro que este enfoque pueda ampliarse a miles o millones de objetos contenidos, por el momento tampoco está claro que sea necesario. Pero otros enfoques (híbridos) son posibles en este caso. Por ejemplo, sería posible analizar cada objeto contenido a medida que se escribe en el almacén de claves/valores y añadirle un conjunto adecuado de subelementos caracterizados si coincide con los criterios. Equilibrar el tamaño, el número y las relaciones entre los objetos es un reto completamente distinto al que me enfrento y que puede que algún día constituya el contenido de otra entrada del blog.

Principales conclusiones

La principal conclusión es que es importante entender qué es lo que realmente quieres conseguir y asegurarte de que eliges el mecanismo de acceso a Couchbase adecuado para satisfacer las necesidades de tu aplicación.

En mi caso, yo estaba tratando de abusar de las vistas porque es una característica fresca y poderosa que es difícil de resistir. Pero el hecho es que, las vistas simplemente no son un buen ajuste para todos los requisitos de acceso a datos en su aplicación.

La otra cosa que hay que tener en cuenta es ser comprensivo con las necesidades del software de infraestructura y reconocer que para la escalabilidad es importante tener datos compartidos. Si te opones a ello o intentas evitarlo, sólo conseguirás sufrimiento.

Entonces, ¿cómo elegir qué patrones de acceso a Couchbase usar para tus datos? A partir de esta experiencia, he escrito algunas pautas que los desarrolladores pueden utilizar para decidir qué patrón de acceso a datos elegir. Aunque no se trata de una lista exhaustiva, aquí tienes 4 patrones típicos que deberías tener en cuenta:

1. Si tienes la clave de un objeto, úsala para obtener el objeto directamente del almacén de claves/valores de Couchbase.

2. Si no tienes eso, pero estás buscando un objeto que tendría una clave secundaria única, entonces intenta encontrar la clave leyéndola del índice secundario y luego obtén el objeto del almacén de claves/valores de Couchbase usando su clave.

3. Si ya tienes un objeto y contiene referencias a otros objetos, utiliza esas referencias directamente; no vayas a buscarlas basándote en una relación que se ha escrito en una vista.

4. Finalmente, si estás buscando recuperar todos los objetos que coinciden con ciertos criterios en toda la base de datos, y la semántica de tu operación es tal que no hay dependencia inherente de orden, entonces accede a una vista que hayas definido. Recuerda que las vistas en Couchbase son eventualmente consistentes.

Comparte este artículo
Recibe actualizaciones del blog de Couchbase en tu bandeja de entrada
Este campo es obligatorio.

Autor

Publicado por Gareth Powell, fundador y arquitecto, Ziniki

Gareth Powell es fundador y arquitecto de Ziniki. Gareth Powell describe su experiencia en el uso de vistas mapreduce en Couchbase.

1 Comentarios

Deja un comentario

¿Listo para empezar con Couchbase Capella?

Empezar a construir

Consulte nuestro portal para desarrolladores para explorar NoSQL, buscar recursos y empezar con tutoriales.

Utilizar Capella gratis

Ponte manos a la obra con Couchbase en unos pocos clics. Capella DBaaS es la forma más fácil y rápida de empezar.

Póngase en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítanos ayudarle.