Arquitectura de Couchbase

Marco de pruebas funcionales y de integración (FIT)

Este blog describe el diseño y desarrollo del framework de pruebas, es decir, el framework FIT para transacciones Couchbase en un entorno distribuido. Empezaremos introduciéndote en la arquitectura de alto nivel, después te guiaremos a través del desarrollo del framework.

Vamos a repasar varios problemas relacionados con las pruebas de los SDK de transacciones y las soluciones a los mismos, y se utilizarán ejemplos relevantes mientras se recorre el desarrollo del marco. Aunque en este blog no se mencionan todos los detalles técnicos del marco de trabajo, se intenta dar una visión global del mismo.

Couchbase ofrece transacciones en múltiples SDKs: Java , Dotnet, y CXX por ahora y con un plan para soportar otros SDKs en un futuro cercano. Probar SDKs que ofrecen la misma funcionalidad plantearía múltiples problemas durante la automatización de pruebas. La redundancia en la automatización de pruebas es el primero que nos vendría a la mente a todos. Aparte de la redundancia, también tenemos que asegurarnos de que todos los SDKs tienen implementaciones similares de las transacciones de Couchbase. Por ejemplo: el manejo de errores se hace exactamente igual en todos los SDKs. Estos son sólo un par de problemas. Con un enfoque principal en Transacciones, este blog proporcionará varios problemas que enfrentaríamos al probar múltiples SDKs y cómo nosotros en Couchbase los hemos resuelto.

Introducción a Couchbase Transactions

Las Transacciones ACID Distribuidas aseguran que cuando múltiples documentos necesitan ser modificados entonces sólo la modificación exitosa de todos justifica la modificación de cualquiera, o todas las modificaciones ocurren exitosamente; o ninguna de ellas ocurre. El cumplimiento de Couchbase con las propiedades ACID se puede encontrar aquí.

Transacciones en entornos distribuidos: 

Clúster de un solo nodo: Las transacciones de Couchbase funcionan tanto en clusters multi nodo como en clusters de un solo nodo. Sin embargo, la configuración del clúster debe ser compatible con Couchbase.

Soporte de transacciones para consultas N1QL: Asegúrese de que al menos uno de los nodos del clúster dispone de servicio de consulta

Pruebas del SDK de Couchbase Transactions:

Durante la fase de diseño del marco, un análisis en profundidad del plan de pruebas y su automatización nos planteó múltiples retos. A continuación se exponen algunos de los principales retos y sus soluciones. A continuación, analizaremos el problema y su solución. También veremos el progreso del desarrollo del marco junto con estas discusiones sobre los problemas.

Problema1: Problema de redundancia:

En Couchbase, actualmente soportamos transacciones en 3 SDK's diferentes: Java , Dotnet, y CXX. En un futuro próximo soportaremos algunos SDK más, incluyendo Golang. Esto claramente proporciona al QE un problema de redundancia, es decir, podríamos tener que automatizar el mismo caso de prueba varias veces para cada SDK. 

Resolución: Cada caso de prueba puede clasificarse en 3 partes principales:

  1. Preparación de pruebas: datos de pruebas, infraestructura de pruebas, etc, 
  2. Ejecución de pruebas, por ejemplo: ejecución de operaciones de transacción, como insertar, reemplazar, etc. y 
  3. Validación de resultados.

Un examen más detallado de estas tres partes revela que las pruebas del SDK sólo intervienen en la fase de ejecución, mientras que la preparación de las pruebas y la validación de los resultados son independientes del SDK, es decir, no importa realmente qué SDK se utilice. Esto nos ha llevado a diseñar un marco que consta de dos partes: el controlador y el ejecutor. El controlador se encarga de la preparación completa de la prueba y de la validación de los resultados. El controlador dirige la ejecución de la prueba, pero sólo de forma abstracta (aprenderemos más sobre esto más adelante), es decir, emite comandos al ejecutor y éste los toma y realiza la ejecución real de la prueba.

Marco FIT está diseñado en un modelo cliente-servidor en el que el conductor actúa como cliente y el ejecutor como servidor.

FIT Framework Architecture

Conductor: Consiste en la preparación de todas las pruebas y la validación de los resultados. Todas las pruebas son las pruebas clásicas de Junit y se pueden ejecutar como una sola prueba individual o como un conjunto de pruebas específico o conjunto de pruebas completo . Todas las pruebas se escriben una sola vez. Estas pruebas se pueden reutilizar para todos los SDK.

Intérprete: Se trata de una sencilla aplicación escrita una vez para cada SDK. Dentro de un controlador, cada prueba se moldea en forma de objeto Java y se envía al gRPC Capa. El protocolo gRPC convierte este objeto Java en un objeto de prueba específico del lenguaje y lo envía al ejecutor. El ejecutor obtiene este objeto de prueba, lee las instrucciones y ejecuta las operaciones de transacción necesarias. Una vez completada la transacción, el ejecutor recupera el resultado y lo envía de vuelta al controlador a través del protocolo gRPC.

Una vez que el controlador recibe el objeto de resultado, procede a la validación del resultado.Proceso de desarrollo de pruebas: Ahora que ya tenemos una idea general de cómo funcionan el controlador y el ejecutor dentro del marco FIT, veamos el aspecto técnico y cómo interactúan entre sí mediante unas sencillas pruebas de ejemplo.

Ej1: Probar transacciones con una sola operación: Operación "reemplazar" básica 

Código del conductor: 

Como se puede ver todas las pruebas se escriben siempre una sola vez y como pruebas Junit.

La preparación de la prueba y la validación de los resultados son independientes del SDK, por lo que se realizan en la propia prueba Junit. 

Sin embargo, la parte de ejecución de la prueba se hace de forma abstracta. En la parte superior, parecerá que se ejecuta en el propio controlador. Pero se involucra en la computación distribuida siguiendo la llamada a procedimiento remoto. Toda la prueba se convierte en un objeto Java, denominado objeto "TransactionBuilder" en nuestro marco FIT, utilizando la clase Transaction Builder y, a continuación, se envía al ejecutor a través de la capa gRPC utilizando el método "sendToPerfomer". 

En este ejemplo en el que intentamos probar la operación de reemplazo de transacciones, creamos un objeto Java que contendrá todos los detalles:

  1. Id del documento en el que se supone que se ejecuta la transacción 
  2. Valor actualizado, es decir, el nuevo valor que queremos que la transacción imponga al documento.
  3. Operación de transacción, en este caso es "reemplazar"

Una vez creado dicho objeto java , el sendToPerformer simplemente invoca la función gRPC para enviarla al servidor.

Consulte el código Java Performer : basicPerformer

Así que en el primer paso, el ejecutor lee el objeto de prueba y comprueba la operación que necesita ejecutar. En nuestro ejemplo, como se trata de una operación de reemplazo, op.hasReplace() devolverá true y op.hasInsert(), op.hasRemove() etc devolverán false.

Dentro del bloque de código replace, el ejecutor recupera el id del documento, la ubicación del documento y el valor actualizado del documento. Una vez recuperada toda la información relevante, el ejecutor ejecuta la transacción, es decir, la operación ctx.replace().

Una vez que la transacción se ha ejecutado correctamente, el resultado se envía de vuelta al controlador y éste, de forma similar, recupera la información relevante del objeto de resultado y realiza la validación del resultado.

Ejemplos de funcionalidades probadas: Esta característica del marco nos ayudó a probar el SDK de transacciones no sólo para el contenido del documento, sino también para los metadatos de la transacción, es decir, los metadatos esperados están presentes siempre que sea necesario y los metadatos se eliminan siempre que sea necesario.

Ahora que ya tenemos algunas nociones técnicas sobre el marco FIT, entremos un poco más en detalle:

Eg2: Comprobación de transacciones con más de una operación:

Código del conductor:

En esta prueba, la transacción realiza la inserción del documento con docId1 y la sustitución del documento con docId2. Así que tenemos que añadir "insertar" y "reemplazar" en el objeto de prueba y toda la información necesaria para cada una de estas operaciones se envía al ejecutor.

Consulte el código Java Performer : performerSupportsTwoOps

Dado que tenemos insertar y reemplazar , el op.Insert devolverá true y el ejecutor recupera la información requerida y realiza la inserción y luego op.replace() devolverá true y luego el ejecutor ejecuta las operaciones de reemplazo y devuelve el resultado al controlador.

Ejemplos de funcionalidades probadas: Inicialmente no soportábamos todas las operaciones válidas de transacciones múltiples sobre el mismo documento en la misma transacción.Cuando se añadieron, pudimos probar esa funcionalidad con este comportamiento del framework. También se probaron las operaciones múltiples regulares de transacciones en diferentes documentos. Problemas como transacciones que fallan al reemplazar/eliminar documentos y caducidad fueron probados bien con este soporte.

Hemos visto en ambos ejemplos que se espera que la transacción tenga éxito. Sin embargo, para los escenarios de casos negativos, esperamos que la transacción arroje errores/excepciones. Estos errores/excepciones son específicos del SDK por lo que necesitan ser manejados en el ejecutor. Así que el controlador necesita decirle al ejecutor qué error/excepción debe exceptuar y el ejecutor necesita hacer esta validación.

Problem2 Verificación de errores:

  1. Para diferentes causas, la transacción debe entender la causa y lanzar el error/excepción correspondiente. Así que no sólo teníamos que probar la funcionalidad de las transacciones, sino también los códigos de error y las excepciones lanzadas por ellas.
  2. La gestión de excepciones de transacción es diferente para cada error/excepción: La excepción de documento no encontrado debe tratarse de forma diferente a algunas excepciones transitorias. 
  3. Incluso para la misma excepción, la fase de la transacción en la que se produce también da lugar a un tratamiento diferente. Por ejemplo: los conflictos de escritura para insertar/reemplazar se tratan de forma diferente que para las operaciones de obtención.

Resolución: El controlador debe enviar los códigos de las causas y excepciones al ejecutor. El ejecutor leerá los códigos para las causas de fallo y las inducirá utilizando Hooks.

Los hooks son implementaciones internas de Couchbase que ayudan a probar escenarios de fallo. En nuestro ejemplo de abajo sólo estamos tratando de crear un vencimiento antes de insertar un documento

Una vez inducido el fallo, el ejecutor también esperará el error/excepción que se supone que esta transacción debe lanzar. Si no se lanza la excepción o se lanza una expectativa incorrecta, el ejecutor falla las pruebas y envía el fallo en el objeto de resultado al controlador. El controlador lee este objeto de resultado y proporciona el fallo esperado y el real como salida.

Eg3: Probar escenarios de casos negativos :

Código del conductor:

Así que en esta prueba, el controlador le está diciendo al ejecutor que ejecute la inserción y que espere que la transacción expire durante esta operación de inserción. Enviamos el código "EXPECT_FAIL_EXPIRY" para transmitir esto al ejecutor.

Consulte el código Java Performer : performerSupportsErrorHandling

Ejemplos de funcionalidades probadas: Se comprobaron todos los códigos de error y gestión de errores/excepciones. Se realizaron pruebas funcionales relacionadas con cualquier SDK no compatible o no sincronizado con la funcionalidad acordada de gestión de errores. La función de caducidad de transacciones se probó correctamente con este soporte del marco.  

Problema 3: Gestión de versiones: Tenemos que probar diferentes versiones de la biblioteca de transacciones y las versiones posteriores tendrían nuevas características que no están disponibles en el anterior versión. Así que el marco de pruebas tuvo que entender qué característica no es compatible y evitar la ejecución de esas pruebas. 

Resolución: Hemos utilizado las extensiones de ejecución de pruebas de condiciones de Junit5. Cada testsuite está anotado con una condición "@IgnoreWhen". Todas las condiciones mencionadas en ella serán recuperadas y utilizadas en el método "ExecuteWhen" que anulamos. Antes de que el controlador comience a ejecutar cualquier prueba, se pondrá en contacto con el ejecutor y obtendrá todas las funcionalidades soportadas por él. El método "ExecuteWhen" utilizará la información proporcionada en "@IgnoreWhen" y las capacidades del ejecutor y decidirá si un conjunto de pruebas debe ser ejecutado o ignorado. 

Eg3:

Consulte el código del controlador Java : driverSupportsVersionManagement

Ejemplos de funcionalidades probadas: El SDK que desarrolló una característica un poco más tarde que otros SDK, podría utilizar esta característica de la FIT para activar estas pruebas una vez que implementaron su característica. Esto nos ayudó en el desarrollo dirigido por pruebas.

Problema 4: Múltiples intérpretes:  Transacciones paralelas:

Las transacciones pueden ejecutarse en paralelo. Las transacciones de Couchbase confirman el modelo de aislamiento. Es decir, cuando dos o más transacciones se ejecutan en el mismo conjunto de documentos no deberían conducir a escrituras/lecturas sucias. Para probar esto, si ejecutamos aleatoriamente 'n' transacciones en paralelo y en caso de que cause corrupción de documentos, sería difícil saber qué causó exactamente la corrupción. Cada transacción puede tener muchas operaciones y cada operación tendría múltiples etapas. En qué operación y en qué etapa colisionaron estas transacciones es algo que necesitamos saber si necesitamos resolver el problema.

Resolución: Hemos diseñado un mecanismo de enclavamiento en el que una transacción ejecuta unas cuantas operaciones o unas cuantas etapas de una operación e indica a la otra transacción que se ponga en marcha. Esta primera transacción espera ahora a que la segunda se ejecute y alcance una etapa deseada. Una vez que la segunda transacción alcanza una etapa determinada, notifica a la primera transacción que continúe. Esto es efectivamente lo que ocurre incluso para las transacciones paralelas. Así que se nos ocurrió un conjunto de puntos de colisión que podrían dar lugar a conflictos de escritura-escritura o lecturas sucias y utilizamos los latches para automatizar estos casos de prueba.

Consulte el código del controlador Java : driverParallelTransactions

Código del conductor:

Ejemplos de funcionalidad probada/errores encontrados: Las transacciones concurrentes se probaron con este soporte

Problema5: Múltiples ejecutores: Transacciones paralelas para diferentes SDK:

Dado que soportamos transacciones en múltiples SDK's, la misma lógica puede ser usada mientras se prueba la ejecución paralela de transacciones con diferentes SDK's. Por ejemplo: transacciones Java frente a transacciones CXX. En el ejemplo anterior, nos conectamos al mismo Performer ya que queríamos ejecutar transacciones paralelas para el mismo SDK. En este caso, TXN A se conectará al Performer A (supongamos que el Performer A está usando Transacciones Java) y Txn B se conectará al Performer B (ejecutando transacciones CXX)

Consulte el código del controlador Java : driverMultiplePerformers

Ejemplos de funcionalidad probada/errores encontrados: Las transacciones concurrentes con diferentes clientes SDK se probaron con este soporte. También nos ayudó a garantizar que los metadatos de la transacción estuvieran intactos.

Conclusión:

Este diseño arquitectónico del marco FIT no sólo nos ayudó a resolver los problemas que se nos plantearon, sino que también nos ayudó en la automatización de pruebas eficiente y ayudó al desarrollo de transacciones en el modo de desarrollo dirigido por pruebas (TDD). 

Automatización eficaz de las pruebas: Dividir el marco en un único controlador y varios ejecutores nos ayudó a desarrollar partes del marco de forma independiente. El desarrollador de cada SDK nos proporcionó el intérprete y el QE pudo centrarse en la automatización de las pruebas, es decir, en el controlador. Los desarrolladores también podían añadir pruebas unitarias al controlador para que todas las pruebas de las transacciones fueran gestionadas por este único marco.

Desarrollo basado en pruebas (TDD): Hemos desarrollado el intérprete java y escrito todas las pruebas necesarias para firmar las primeras versiones de las transacciones para Java SDK. Una vez que Java SDK fue liberado y el desarrollo de otras transacciones SDK es decir, CXX y dot net comenzó, nuestro equipo de desarrollo tuvo que desarrollar la aplicación intérprete, mientras que la reutilización de la misma aplicación controlador. Esto les ayudó a desarrollar su SDK de forma TDD.

Esperamos que haya disfrutado de este blog. Estamos añadiendo más funciones a este marco y publicaremos un nuevo blog en el que describiremos los nuevos problemas y las nuevas soluciones. Mientras tanto, si desea más información sobre el marco FIT, póngase en contacto con praneeth.bokka@couchbase.com. Para obtener más información sobre las transacciones de Couchbase, visite Transacciones Couchbase

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

Autor

Publicado por Praneeth Bokka

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.