Arquitetura do Couchbase

Estrutura de testes funcionais e de integração (FIT)

Este blog descreve o projeto e o desenvolvimento da estrutura de teste, ou seja, a estrutura FIT para transações do Couchbase em um ambiente distribuído. Começaremos apresentando uma visão arquitetônica de alto nível e, em seguida, o acompanharemos no desenvolvimento da estrutura.

Vamos analisar vários problemas envolvidos no teste dos SDKs de transações e as soluções para eles, e exemplos relevantes serão usados durante o desenvolvimento da estrutura. Mesmo que nem todos os detalhes técnicos da estrutura sejam mencionados neste blog, ele definitivamente tenta dar uma visão holística da estrutura.

O Couchbase oferece transações em vários SDKs: Java, Dotnet e CXX por enquanto e com um plano para oferecer suporte a outros SDKs em um futuro próximo. O teste de SDKs que oferecem a mesma funcionalidade apresentaria vários problemas durante a automação de testes. A redundância da automação de testes é o primeiro que vem à mente de todos. Além da redundância, também temos que garantir que todos os SDKs tenham implementações semelhantes das transações do Couchbase. Por exemplo: o tratamento de erros é feito exatamente da mesma forma por todos os SDKs. Esses são apenas alguns problemas. Com foco principal em Transações, este blog apresentará vários problemas que enfrentamos ao testar vários SDKs e como nós do Couchbase os resolvemos.

Introdução às transações do Couchbase

As transações ACID distribuídas garantem que, quando vários documentos precisam ser modificados, somente a modificação bem-sucedida de todos justifica a modificação de qualquer um, ou todas as modificações ocorrem com sucesso ou nenhuma delas ocorre. A conformidade do Couchbase com as propriedades ACID pode ser encontrada em aqui.

Transações em ambientes distribuídos: 

Cluster de nó único: As transações do Couchbase funcionam em clusters de vários nós e de um único nó. No entanto, a configuração do cluster deve ser suportada por Couchbase.

Suporte a transações para consultas N1QL: Assegurar que pelo menos um dos nós do cluster tenha serviço de consulta

Teste do SDK do Couchbase Transactions:

Durante a fase de projeto da estrutura, uma análise profunda do plano de teste e de sua automação nos colocou diante de vários desafios. Abaixo estão alguns dos principais desafios e suas resoluções. A seguir, discutiremos o problema e sua solução. Também veremos o progresso do desenvolvimento da estrutura junto com essas discussões de problemas.

Problema 1: Problema de redundância:

No Couchbase, atualmente oferecemos suporte a transações em três SDKs diferentes: Java, Dotnet e CXX. Em um futuro próximo, ofereceremos suporte a mais alguns SDKs, incluindo o Golang. Isso claramente fornece ao QE um problema de redundância, ou seja, talvez tenhamos que automatizar o mesmo caso de teste várias vezes, uma vez para cada SDK. 

Resolução: Cada caso de teste pode ser classificado em três partes principais:

  1. Preparação do teste, por exemplo: dados de teste, infraestrutura de teste etc, 
  2. Execução de testes, por exemplo: execução de operações de transação, ou seja, inserir, substituir etc. e 
  3. Validação de resultados.

Uma análise mais detalhada dessas três partes revela que o teste do SDK está envolvido apenas na fase de execução do teste, enquanto a preparação do teste e a validação do resultado são, na verdade, independentes do SDK, ou seja, não importa qual SDK é usado. Isso nos levou a projetar uma estrutura que consiste em duas partes, ou seja, driver e executor. O driver cuida de toda a preparação do teste e da validação dos resultados. O driver conduz a execução do teste, mas apenas de forma abstrata (aprenderemos mais sobre isso abaixo), ou seja, emite comandos para o executor e o executor os recebe e realiza a execução real do teste.

Estrutura do FIT é projetado em um modelo cliente-servidor em que o driver atua como cliente e o executor como servidor.

FIT Framework Architecture

Motorista: Consiste em toda a preparação de testes e validação de resultados. Todos os testes são os testes clássicos do Junit e podem ser executados como um único teste individual ou como um conjunto de testes específico ou um conjunto de testes completo. Todos os testes são escritos uma e somente uma vez. Esses testes podem ser reutilizados em todos os SDKs.

Intérprete: Esse é um aplicativo simples escrito uma vez para cada SDK. Dentro de um driver, cada teste é moldado na forma de um objeto Java e enviado para o gRPC Camada. O protocolo gRPC faz o trabalho de converter esse objeto Java em um objeto de teste específico da linguagem e o envia para o executor. O executor obtém esse objeto de teste, lê as instruções e executa as operações de transação necessárias. Quando a transação é concluída, o executor recupera o resultado e o envia de volta ao driver por meio do protocolo gRPC

Quando o driver recebe o objeto de resultado, ele prossegue com a validação do resultadoProcesso de desenvolvimento de testes: Agora que temos uma ideia de nível superior de como o driver e o performer operam dentro da estrutura do FIT, vamos ver o aspecto técnico e como eles interagem entre si usando alguns testes de exemplo simples.

Exemplo 1: Teste de transações com uma única operação: Operação básica de "substituição" 

Código do motorista: 

Como você pode ver, todos os testes são sempre escritos apenas uma vez e como testes Junit.

A preparação do teste e a validação do resultado são independentes do SDK e, portanto, são feitas no próprio teste Junit. 

No entanto, a parte de execução do teste é feita de forma abstrata. Na parte superior, parecerá que está sendo executada no próprio driver. Mas ele se envolve em computação distribuída seguindo a chamada de procedimento remoto. Todo o teste é convertido em um objeto Java, chamado de objeto "TransactionBuilder" em nossa estrutura FIT, usando a classe Transaction Builder e, em seguida, enviado ao executor por meio da camada gRPC usando o método "sendToPerfomer". 

Neste exemplo, em que estamos tentando testar a operação de substituição de transações, criamos um objeto Java que terá todos os detalhes:

  1. Identificação do documento no qual a transação deve ser executada 
  2. Valor atualizado, ou seja, o novo valor que queremos que a transação imponha ao documento
  3. Operação de transação, nesse caso, é "substituir"

Depois de criar esse objeto java, o sendToPerformer simplesmente invoca o gRPC para enviá-lo ao servidor.

Consulte o código do Java Performer: basicPerformer

Portanto, na primeira etapa, o executor lê o objeto de teste e verifica a operação que precisa executar. Em nosso exemplo, como se trata de uma operação de substituição, op.hasReplace() retornará true e op.hasInsert(), op.hasRemove() etc. retornarão false.

Dentro do bloco de código de substituição, o executor recupera o ID do documento, o local do documento e o valor atualizado do documento. Depois que todas as informações relevantes são recuperadas, o executor executa a transação, ou seja, a operação ctx.replace().

Depois que a transação é executada com êxito, o resultado é enviado de volta ao driver, que, da mesma forma, recupera as informações relevantes do objeto de resultado e realiza a validação do resultado.

Exemplos de funcionalidades testadas: Esse recurso da estrutura nos ajudou a testar o SDK de transações não apenas para o conteúdo do documento, mas também para os metadados da transação, ou seja, os metadados esperados estão presentes sempre que necessário e os metadados são removidos sempre que necessário.

Agora que já temos algumas informações técnicas sobre a estrutura FIT, vamos entrar em mais detalhes:

Eg2: Teste de transações com mais de uma operação:

Código do motorista:

Nesse teste, a transação executa a inserção do documento com docId1 e a substituição do documento com docId2. Portanto, temos que adicionar "insert" e "replace" ao objeto de teste e todas as informações necessárias para cada uma dessas operações são enviadas ao executor.

Consulte o código do Java Performer: performerSupportsTwoOps

Como temos insert e replace, op.Insert retornará true e o executor recuperará as informações necessárias e executará a inserção e, em seguida, op.replace() retornará true e o executor executará as operações de substituição e retornará o resultado ao driver.

Exemplos de funcionalidades testadas: Inicialmente, não oferecemos suporte a todas as operações válidas de transações múltiplas no mesmo documento na mesma transação. Também foram testadas as operações múltiplas regulares de transações em documentos diferentes. Problemas como falhas nas transações para substituir/remover documentos e expiração foram bem testados com esse suporte

Vimos em ambos os exemplos que se espera que a transação seja bem-sucedida. No entanto, para cenários de casos negativos, esperamos que a transação lance erros/exceções. Esses erros/exceções são específicos do SDK e, portanto, precisam ser tratados no executor. Portanto, o driver precisa informar ao executor qual erro/exceção deve ser excluído e o executor precisa fazer essa validação

Problema2 Verificação de erros:

  1. Para causas diferentes, a transação deve entender a causa e lançar os erros/exceções relevantes. Portanto, tivemos que testar não apenas a funcionalidade das transações, mas também os códigos de erro e as exceções lançadas por elas
  2. O tratamento de exceções de transação é diferente para cada erro/exceção: A exceção Documento não encontrado deve ser tratada de forma diferente de algumas exceções transitórias 
  3. Mesmo para a mesma exceção, o estágio da transação em que ela ocorre também resulta em um tratamento diferente. Por exemplo: os conflitos de gravação/gravação para inserção/substituição são tratados de forma diferente das operações de obtenção.

Resolução: O driver deve enviar códigos para as causas e exceções para o executor. O executor lerá os códigos das causas de falha e os induzirá usando Hooks.

Hooks são implementações internas do Couchbase que ajudam a testar cenários de falha. Em nosso exemplo abaixo, estamos apenas tentando criar uma expiração antes de inserir um documento

Quando a falha for induzida, o executor também esperará o erro/exceção que essa transação deve lançar. Se a exceção não for lançada ou se uma expectativa incorreta for lançada, o executor falhará nos testes e enviará a falha no objeto de resultado para o driver. O driver lê esse objeto de resultado e fornece a falha esperada e a real como saída.

Eg3: testar cenários de casos negativos :

Código do motorista:

Portanto, nesse teste, o driver está dizendo ao executor para executar a inserção e, em seguida, esperar que a transação expire durante essa operação de inserção. Enviamos o código "EXPECT_FAIL_EXPIRY" para transmitir isso ao executor.

Consulte o código do Java Performer: performerSupportsErrorHandling

Exemplos de funcionalidades testadas: Todos os códigos de erro e tratamento de exceções/erros foram testados. Foram realizados testes funcionais relacionados a qualquer SDK que não oferecesse suporte ou não estivesse em sincronia com a funcionalidade acordada de tratamento de erros. O recurso de expiração de transações foi bem testado com esse suporte da estrutura.  

Problema 3: gerenciamento de versões: Temos que testar diferentes versões de bibliotecas de transações e as versões posteriores teriam novos recursos que não estão disponíveis na versão anterior. versão. Portanto, a estrutura de teste teve que entender qual recurso não é compatível e evitar a execução desses testes. 

Resolução: Usamos as extensões de execução de teste de condição do Junit5. Cada conjunto de testes é anotado com uma condição "@IgnoreWhen". Todas as condições mencionadas nessa condição serão recuperadas e usadas no método "ExecuteWhen" que substituímos. Antes de o driver começar a executar qualquer teste, ele entrará em contato com o executor e obterá todas as funcionalidades compatíveis com ele. O método "ExecuteWhen" usará as informações fornecidas em "@IgnoreWhen" e os recursos do executor e decidirá se um conjunto de testes precisa ser executado ou ignorado. 

Eg3:

Consulte o código do driver Java: driverSupportsVersionManagement

Exemplos de funcionalidades testadas: O SDK que desenvolvesse um recurso um pouco mais tarde do que os outros SDKs poderia usar esse recurso do FIT para ativar esses testes assim que implementasse seu recurso. Isso nos ajudou no desenvolvimento orientado por testes.

Problema 4: Vários artistas:  Transações paralelas:

As transações podem ser executadas em paralelo. As transações do Couchbase confirmam o modelo de isolamento. Ou seja, quando duas ou mais transações são executadas no mesmo conjunto de documentos, isso não deve levar a gravações/leituras sujas. Para testar isso, se executarmos aleatoriamente "n" transações em paralelo e, caso isso cause corrupção de documentos, será difícil saber o que exatamente causou a corrupção. Cada transação pode ter muitas operações e cada operação teria vários estágios. Em que operação e em que estágio essas transações colidiram é algo que precisamos saber se precisarmos resolver o problema.

Resolução: Projetamos um mecanismo de travamento em que uma transação executa algumas operações ou alguns estágios em uma operação e sinaliza o início da outra transação. Essa primeira transação aguarda que a segunda seja executada e atinja o estágio desejado. Quando a segunda transação atinge um determinado estágio, ela notifica a primeira transação para prosseguir. Isso é efetivamente o que acontece até mesmo em transações paralelas. Assim, criamos um conjunto de pontos de colisão que poderiam levar a conflitos de gravação-escrita ou leituras sujas e usamos as travas para automatizar esses casos de teste.

Consulte o código do driver Java: driverParallelTransactions

Código do motorista:

Exemplos de funcionalidade testada/bugs encontrados: As transações simultâneas foram testadas com esse suporte

Problema 5: Vários executores: Transações paralelas para diferentes SDKs:

Como oferecemos suporte a transações em vários SDKs, a mesma lógica pode ser usada ao testar a execução paralela de transações com SDKs diferentes. Por exemplo: transações Java versus transações CXX. No exemplo acima, nós nos conectamos ao mesmo executor, pois queríamos executar transações paralelas para o mesmo SDK. Nesse caso, o TXN A se conectará ao Executante A (suponha que o Executante A esteja usando transações Java) e o Txn B se conectará ao Executante B (executando transações CXX)

Consulte o código do driver Java: driverMultiplePerformers

Exemplos de funcionalidade testada/bugs encontrados: Transações simultâneas com diferentes clientes SDK foram testadas com esse suporte. Também nos ajudou a garantir que os metadados da transação estivessem intactos.

Conclusão:

Esse projeto arquitetônico da estrutura FIT não só nos ajudou a resolver os problemas que nos foram apresentados, mas também nos ajudou a automatizar os testes de forma eficiente e a desenvolver a transação no modo de desenvolvimento orientado por testes (TDD). 

Automação eficiente de testes: Dividir a estrutura em um único driver e vários executores nos ajudou a desenvolver partes da estrutura de forma independente. O desenvolvedor de cada SDK nos forneceu o executor e o QE pôde se concentrar na automação de testes, ou seja, no driver. Os desenvolvedores também puderam adicionar testes de unidade ao driver para que todos os testes de transações fossem tratados por essa única estrutura.

Desenvolvimento orientado a testes (TDD): Depois que o Java SDK foi lançado e o desenvolvimento de outros SDKs de transação, ou seja, CXX e dot net, começou, nossa equipe de desenvolvimento teve que desenvolver o aplicativo performer enquanto reutilizava o mesmo aplicativo de driver. Isso os ajudou a desenvolver o SDK de forma TDD.

Esperamos que você tenha gostado deste blog. Estamos adicionando mais recursos a essa estrutura e criaremos um novo blog descrevendo os novos problemas e as novas soluções. Enquanto isso, para saber mais sobre a estrutura FIT, entre em contato com praneeth.bokka@couchbase.com. Para saber mais sobre as transações do Couchbase, visite Transações do Couchbase

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

Autor

Postado por Praneeth Bokka

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.