Observação importante: As transações ACID de vários documentos agora estão disponíveis no Couchbase. Veja: Transações ACID para aplicativos NoSQL para obter mais informações!

As propriedades ACID são um tópico sobre o qual recebo muitas perguntas. Em geral, as pessoas perguntam no contexto de transações: "Existem transações NoSQL?", "Posso usar transações ACID no Couchbase?" e assim por diante. Mas os aplicativos distribuídos atuais nem sempre esperam ou precisam de todas as propriedades ACID do banco de dados. Vou me aprofundar em como as propriedades NoSQL e ACID se combinam no Couchbase.

Antes de fazermos isso, vamos definir do que estamos falando. ACID significa: Atomicidade, Consistência, Isolamento, Durabilidade. Foi criado na década de 1980, mas existe desde a década de 1970 em bancos de dados relacionais tradicionais e não distribuídos. Ele descreve uma classe de banco de dados que é capaz de fornecer operações com garantias ACID.

Couchbase e transações

O escalonamento, o desempenho e a flexibilidade têm sido o foco principal da plataforma de dados Couchbase, e as transações distribuídas e com vários documentos muitas vezes não condizem com essas características.

A Parte 1 desta postagem de blog em duas partes abordará os blocos de construção do ACID que são disponíveis no Couchbase. Você pode usar essas "primitivas" sem sacrificar o dimensionamento, o desempenho e a flexibilidade gerais do Couchbase. Dependendo do seu caso de uso, essas primitivas podem ser adequadas por si só ou em conjunto.

Atualmente, o Couchbase oferece suporte a muitas propriedades ACID em documentos únicos, mas não oferece suporte a transações de vários documentos. Na parte 2 desta série, mostrarei uma abordagem que usa esses blocos de construção para criar algo como uma transação de vários documentos com o Couchbase. Mas, antes disso, é importante ter uma compreensão completa dos componentes básicos.

Quais são as propriedades do ACID?

Os aplicativos evoluíram de monolíticos para aplicativos baseados em microsserviços distribuídos. Os microsserviços ainda esperam certos aspectos da transacionalidade, como uma confirmação ou reversão atômica, mas não necessariamente um comportamento ACID completo. O comportamento ACID completo ainda pode ser importante, mas nos softwares modernos da Web e de dispositivos móveis, ele costuma ser menos prioritário do que o desempenho e a escalabilidade. Entretanto, não se trata de uma situação "ou um ou outro". O Couchbase fornece algumas ferramentas e recursos para ajudá-lo a equilibrar as propriedades ACID e o desempenho.

A é para Atomicidade

"Atomicidade" significa que um grupo de operações tem sucesso ou falha. O Couchbase fornece atomicidade para documentos únicos. Uma operação para obter ou definir um documento é bem-sucedida ou falha. Em comparação com a garantia de um RDBMS de múltiplos operações bem-sucedidas ou fracassadas em conjunto, isso pode não parecer muito.

Mas considere que modelagem de dados é muito diferente entre os bancos de dados de documentos e as tecnologias de bancos de dados relacionais.

Em um banco de dados relacional, você normalmente normalizar os dados. Por exemplo, para armazenar um carrinho de compras com 3 itens, você precisaria de:

  • 1 linha em uma tabela do ShoppingCart
  • 3 linhas em uma tabela ShoppingCartItems

Shopping Cart example for ACID properties

Se você quiser criar um carrinho de compras com 3 itens, serão necessários quatro INSERIR declarações. Portanto, talvez seja necessário que o banco de dados relacional trate esses 4 comandos atomicamente.

Agora, considere um banco de dados de documentos. Você criaria um único documento JSON de carrinho de compras, que conteria 3 itens.

Obter ou definir esse documento é uma única operação atômica no Couchbase. Um modelo totalmente normalizado nem sempre é necessário. Se eu estendesse esse modelo para incluir o endereço e meu endereço mudasse um mês depois de eu enviar um pedido, o pedido para o qual o endereço foi enviado realmente mudaria? A normalização visa, em parte, reduzir a duplicação de dados. Essa é a atitude correta e eficiente a ser tomada em algumas situações, mas não em todas.

Se você puder modelar seus dados dessa forma, reduzirá imediatamente a necessidade de uma transação de várias operações (se estiver preocupado com o aumento do tamanho do documento, não se preocupe, você pode usar um subdocumento ou N1QL para operar em uma parte do documento).

C é de Consistência

"Consistência" significa, em geral, que uma operação "não violará as restrições de integridade do sistema declaradas". Quaisquer restrições no banco de dados sobre os dados permanecem consistentes antes e depois de uma operação ACID. Por exemplo, se um conjunto atômico de operações falhar, os dados permanecerão consistentes com o que eram antes da operação (porque podem ser revertidos). Há outros tipos de restrições de consistência que um banco de dados pode impor: em um banco de dados relacional, por exemplo, não é possível inserir 6 colunas de dados em uma tabela de 5 colunas.

Em vez de um esquema, o Couchbase usa JSON. Cada documento contém implicitamente seu próprio esquema. Se você executar uma operação de inserção com JSON, deverá fornecer um JSON válido (os SDKs do Couchbase normalmente cuidarão disso para você). O Couchbase só impõe isso no nível do documento. O Couchbase não pode impedir que você use um esquema JSON diferente de documento para documento. Por exemplo, considere estes dois documentos:

Um bucket do Couchbase permite ambos os documentos. Observe que os nomes dos campos não são consistentes entre si. Isso significa que há mais responsabilidade no nível do aplicativo para garantir que os documentos tenham nomes consistentes (mas, normalmente, isso será derivado do seu modelo de dados e, portanto, é tratado automaticamente).

Outra restrição que o Couchbase impõe é a chave. Cada documento deve ter uma chave exclusiva. Se você tentar inserir outro documento com uma chave de "user::mgroves", por exemplo, ocorrerá um erro. Portanto, se você precisar impor uma restrição exclusiva, usar a chave do documento é uma maneira de conseguir isso. (Também é vantajoso usar uma chave natural quando possível, para que um cluster do Couchbase possa realizar uma pesquisa direta).

Consistência da consulta

Por fim, gostaria de mencionar consistência do índice. Wcom a linguagem de consulta N1QL, O Couchbase é capaz de indexar campos ou combinações de campos em documentos JSON (assim como os bancos de dados relacionais podem indexar colunas ou combinações de colunas). No entanto, o Couchbase atualiza os índices de forma assíncrona para oferecer melhor desempenho do que outros tipos de bancos de dados. Ao executar uma consulta N1QL, o comportamento padrão é "Not Bounded". Isso significa que o mecanismo de consulta retornará resultados com base no estado atual do sistema. Portanto, hipoteticamente, se você criar um novo documento e executar uma consulta imediatamente, esse documento poderá não aparecer nos resultados.

Felizmente, há duas outras opções para ajustar a consistência das consultas: RequestPlus e AtPlus.

RequestPlus está no extremo oposto do espectro de consistência. Ele aguardará que todos os documentos atualmente conhecidos pelo cluster façam parte dos recálculos do índice antes de processar a consulta. A compensação aqui é a latência, é claro. Uma consulta RequestPlus possivelmente levará mais tempo para ser executada.

AtPlus está no meio. Em vez de aguardar a conclusão de um índice inteiro, o AtPlus aguardará apenas a indexação dos documentos conhecidos por uma instância específica do seu aplicativo. Isso proporciona menor latência com uma janela estreita de consistência, mas exige mais trabalho para o SDK. A solicitação de consulta também precisa ser tratada pela mesma instância.

Observação: a "consistência" no ACID é diferente do que a "Consistência" no Teorema CAP. Quando estiver pensando no Couchbase e no CAP, lembre-se de que este é um fortemente consistente banco de dados distribuído. Isso significa que cada documento tem um único documento correto por cluster, e não pode haver um documento "conflitante" ou "irmão" com a mesma chave em outro lugar do cluster. Um banco de dados altamente consistente não implicam em qualquer coisa sobre transações ou propriedades ACID.

I é para Isolamento

"Isolamento" é a capacidade de uma operação ocorrer somente após a conclusão de outra operação com os mesmos dados. Dessa forma, cada operação é independente de outras operações. Isso é muito importante quando um banco de dados está lidando com acesso simultâneo a dados. Ele deve aparecer que o banco de dados esteja lidando com apenas uma operação por vez. Para conseguir isso, os dados que estão sendo atualizados devem ser bloqueado não podem ser modificados (e/ou visualizados) até que a operação seja concluída.

O Couchbase fornece isolamento de leitura comprometida por padrão (novamente) no nível de documento único.

Para obter um isolamento mais rigoroso, há dois tipos de bloqueios que você pode fazer no Couchbase: bloqueio pessimista e bloqueio otimista.

Bloqueio otimista

O bloqueio "otimista" se baseia em um valor no Couchbase chamado CAS (compare-and-swap). Todo documento tem um valor CAS, que é um valor opaco. Toda vez que esse documento é alterado, ele recebe um novo valor CAS. Ao tentar atualizar um documento, você passa um valor CAS como parte da operação. Se os valores CAS corresponderem, o Couchbase permite a operação. Caso contrário, a operação não será permitida e o Couchbase retornará um erro.

Como exemplo, vamos supor que haja dois processos: A e B. A e B fazem uma solicitação "get" ao Couchbase para buscar um documento: A e B. A e B fazem uma solicitação "get" ao Couchbase para buscar um documento. O Couchbase retorna o documento, juntamente com um valor CAS. Em seguida, A e B enviam uma solicitação "set" ao Couchbase, transmitindo o valor CAS que receberam anteriormente. O processamento será acelerado, ocorrendo em um deles primeiro. Digamos que seja A. Quando chegar a vez de B, o valor CAS do documento já terá sido alterado e, portanto, a operação falhará.

Aqui está um exemplo em .NET. Vamos supor que estejamos trabalhando em um jogo para celular e que queiramos acompanhar o nível da espada de um jogador. Quanto mais alto o nível, mais dano ele causa. Neste exemplo, A está tentando fazer upgrade para o nível 2 e B está tentando fazer upgrade para o nível 3.

No final desse processo, B falha e a espada do jogador permanece no nível 2. Se você quiser que B seja bem-sucedido, uma solução é buscar novamente o documento, obter o valor CAS mais recente e tentar novamente.

É claro que essa solução pode falhar novamente. E mais uma vez. Mas é por isso que ela é chamada de "otimista". Ela pressupõe que o documento não estará sob forte contenção e, eventualmente, será bem-sucedida. Não é necessário nenhum bloqueio real por parte dos servidores: apenas uma verificação de valores.

Bloqueio pessimista

Você pode usar o bloqueio pessimista para realmente definir um bloqueio. Isso pode ser útil se você quiser bloquear um gráfico de documentos para alterar vários documentos.

Há uma operação atômica disponível no Couchbase chamada "GetAndLock". Essa operação retorna o documento e um valor CAS. Nesse momento, o documento é considerado "bloqueado". Nenhum outro bloqueio pode ser feito nele por outros processos, e somente o valor CAS pode desbloquear o documento.

Aqui está um exemplo do C# de uma trava pessimista em ação:

Além disso, ao usar GetAndLock, você deve definir um período de tempo limite. Após o período de tempo limite, o Couchbase libera o bloqueio automaticamente. Aqui está um exemplo do tempo limite em ação.

Ao executar esse exemplo, a seguinte saída ocorrerá:

Pessimistic lock time out

Usando esses bloqueios, você pode obter o isolamento de um documento individual, para garantir que as alterações ocorram na ordem esperada.

D de Durabilidade

Tradicionalmente, "durabilidade" significa que, quando uma operação é concluída com êxito, a disco armazena as alterações feitas pela operação. Em um banco de dados distribuído, a durabilidade pode significar que o disco e/ou a memória em outros nós armazenar as alterações. A replicação para outros nós é o mecanismo preferido para durabilidade no Couchbase, pois a rede é muito mais rápida do que o disco. Em última análise, para um desenvolvedor, isso significa que, mesmo que ocorra uma falha no sistema, a alteração ainda ocorrerá.

O Couchbase tem uma arquitetura "memory-first". Isso significa que os resultados das operações de gravação são reconhecidos quando recebidos na memória e, em seguida, colocados em uma fila para serem gravados de forma assíncrona no disco ou replicados em outro nó logo depois. Portanto, se uma operação for gravada na memória e o sistema for desligado imediatamente, essa operação não será durável. Essa é a troca padrão que o Couchbase faz: velocidade sobre durabilidade.

No entanto, o Couchbase permite que você substitua essa configuração padrão e especifique um nível mais forte de durabilidade com Requisitos de durabilidade. Isso fará com que o pêndulo se afaste do desempenho e se aproxime das propriedades ACID. A vantagem desse design é que o desenvolvedor do aplicativo sabe e decide quando é importante pagar o custo extra.

O comportamento padrão é que a gravação do documento na memória é considerada um sucesso. O Couchbase ainda persistirá e replicará de acordo com a configuração do cluster do Couchbase, mas a chamada do método continuará depois que a operação for reconhecida.

Posso especificar o número de nós a serem replicados para que eu considere uma operação durável bem-sucedida usando ReplicarPara:

E também posso especificar uma combinação de persistência para outros nós e replicação para outros nós que considero durável "o suficiente", usando PersistTo também:

Cada uma dessas chamadas de método será bloqueada até que o Requisitos de durabilidade são atendidos e permitirão que seu aplicativo execute o tratamento adicional de erros.

Observe que, se os requisitos de durabilidade falharo Couchbase ainda poderá salvar o documento e, eventualmente, distribuí-lo pelo cluster. Tudo o que sabemos é que, até onde o SDK sabe, ele não foi bem-sucedido. Você pode optar por agir com base nessas informações para introduzir mais propriedades ACID em seu aplicativo.

Notas finais

Esta postagem do blog falou sobre as várias primitivas disponíveis no Couchbase para criar garantias do tipo ACID em seu aplicativo. Embora essas primitivas não satisfaçam a definição completa de ACID, elas são suficientes para o que a grande maioria dos aplicativos modernos baseados em microsserviços precisa. Para a pequena porcentagem de casos de uso que precisam de garantias transacionais adicionais, o Couchbase continuará a inovar ainda mais.

Na próxima postagem, veremos técnicas e exemplos de código que você pode utilizar para criar uma transação de vários documentos no Couchbase.

Se você tiver alguma dúvida, não deixe de conferir o Fóruns do Couchbase. Você pode encontrar o mesmo código usado nesta postagem do blog no Github.

Agradecimentos especiais a todos os coautores desta postagem do blog: Shivani Gupta, Ravid Mayuram, John Liang, Chin Hong, Matt Ingenthron, Michael Nitschinger (e tenho certeza de que estou esquecendo alguns outros).

Autor

Postado por Matthew Groves

Matthew D. Groves é um cara que adora programar. Não importa se é C#, jQuery ou PHP: ele enviará solicitações de pull para qualquer coisa. Ele tem programado profissionalmente desde que escreveu um aplicativo de ponto de venda QuickBASIC para a pizzaria de seus pais nos anos 90. Atualmente, ele trabalha como gerente sênior de marketing de produtos da Couchbase. Seu tempo livre é passado com a família, assistindo aos Reds e participando da comunidade de desenvolvedores. Ele é autor de AOP in .NET, Pro Microservices in .NET, autor da Pluralsight e Microsoft MVP.

3 Comentários

  1. Perry Krug, Arquiteto Principal, Couchbase maio 21, 2018 em 7:58 am

    Matt, ótima postagem! Acho que também seria útil discutir a "consistência" no Couchbase em termos de referências a documentos e JOINs. Ou seja, você pode evitar problemas típicos de desnormalização fazendo referência a um documento dentro de outro. Dessa forma, quando você faz uma gravação em um dos documentos, ela ainda é ACID.

  2. Obrigado, Perry. Eu discuti exatamente isso, exceto em "atomicidade", pois acho que é mais aplicável.

    1. Obrigado, Matt, eu vi essa discussão inicial, mas acho que vale a pena destacar como vários documentos ainda podem ser atualizados de forma "consistente", mostrando a ligação entre dois documentos e usando um N1QL JOIN ao lê-los de volta. Por exemplo, o documento1 contém a maior parte do meu perfil de usuário e um ponteiro/referência ao documento2, que contém minha lista de pedidos. Quando eu atualizo o documento2, ele ainda é consistente com todo o perfil do usuário quando eu o leio de volta (via JOIN), mesmo que eu não tenha precisado atualizar atomicamente o documento1 e o documento2. É essa combinação de normalização e desnormalização que torna o Couchbase tão poderoso e fácil de obter atomicidade e consistência. O documento1 é desnormalizado para conter várias tabelas, mas o documento1+documento2 é normalizado para evitar o inchaço dos dados e melhorar a simultaneidade... ainda assim, a combinação é atômica e consistente ao atualizar qualquer um dos documentos (ou qualquer um em uma cadeia). Isso faz mais sentido?

Deixar uma resposta