Artigo original: ACID Databases – Atomicity, Consistency, Isolation & Durability Explained

ACID é a sigla para Atomicidade, Consistência, Isolamento e Durabilidade. Essas são quatro propriedades essenciais que a maioria dos sistemas de gerenciamento de bancos de dados (DBMS, do inglês DataBase Management Systems) oferece como garantia ao lidar com transações.

A maioria dos DBMS populares, como MySQL, PostgresSQL e Oracle, possui garantias ACID prontas para uso. Outros têm garantias ACID parciais, como Redis, DynamoDB e Cassandra. A tendência, no entanto, parece ser que mais e mais DBMS ofereçam conformidade com ACID.

É importante observar que, embora muitos DBMS possam afirmar ser compatíveis com ACID, a implementação dessa conformidade pode variar.

Assim, por exemplo, se o isolamento é uma propriedade fundamental de que você precisa para uma aplicação que está criando, é necessário entender como exatamente o DBMS escolhido implementa o isolamento.

Este artigo explicará o que são transações e analisará em detalhes o que significam atomicidade, consistência, isolamento e durabilidade, usando analogias e exemplos do mundo real.

Índice:

  1. O que são transações?
  2. O que significa atomicidade?
  3. O que significa consistência?
  4. O que significa isolamento?
  5. O que significa durabilidade?
  6. Juntando tudo

O que são transações?

Muitas coisas podem dar errado ao usar um banco de dados:

  • o hardware ou software do banco de dados pode falhar
  • a aplicação que chama o banco de dados pode falhar no meio da operação
  • a rede pode ficar congestionada com mais tráfego do que ela pode lidar (tornando-a inoperante)
  • vários clients podem fazer gravações ao mesmo tempo que sobrescrevem as alterações uns dos outros
  • os clients podem ler dados fantasmas, que não deveriam estar no banco de dados

Várias outras coisas podem dar errado - esta não é, de modo algum, uma lista completa.

Como algo pode dar errado de mais maneiras do que podemos prever, tentar evitar todas as falhas possíveis pode se tornar desnecessariamente caro e complicado. Em vez disso, é melhor projetar um sistema que possa continuar a operar apesar de uma falha. As transações nos permitem fazer isso.

As transações têm um único propósito: garantir que um sistema seja tolerante a falhas. Se ocorrer uma falha em um sistema, o sistema pode continuar a operar sem uma catástrofe completa? Dito de outro modo, o sistema consegue tolerar falhas? Uma resposta "sim" a essa pergunta significa que tal sistema é tolerante a falhas.

Então, o que é exatamente uma transação?

718d52b8-c016-4746-a1eb-73b6aac6d5fa_636x960
Não, não estamos falando DESTE tipo de transação

Uma transação é uma abstração. É um conjunto de operações (leituras e gravações) que são tratadas como uma única operação lógica.

Imagine que você deseja comprar um único livro em uma loja on-line, como a amazon.com.br, por exemplo. As etapas abaixo mostram uma visão simplificada do que precisa acontecer:

  1. Primeiro, você seleciona o livro, adicionando o item à sua cesta.
  2. A quantidade em estoque do livro é verificada para garantir que seja válida (ou seja, o valor do estoque para o título que você está comprando precisa ser maior que zero).
  3. Você clica em "comprar", o que atualiza o estoque da Amazon para o livro e o diminui em 1 (já que você está comprando um único livro).
  4. Além disso, o saldo da sua conta bancária é atualizado para contabilizar o custo do livro.

Uma transação garante que todas as operações relacionadas à compra sejam tratadas como uma única operação. Se qualquer parte da transação falhar, toda a transação é revertida, deixando o banco de dados em um estado como se o cliente nunca tivesse tentado a compra, mantendo assim a integridade dos dados.

A transação é confirmada quando todas as operações dentro da transação são concluídas com sucesso e seus resultados são registrados permanentemente. Essa permanência é normalmente alcançada gravando as alterações no armazenamento do banco de dados, que pode ser em disco para bancos de dados tradicionais ou na memória para bancos de dados na memória, como o Redis.

Ao tratar todas essas diferentes operações como uma única operação lógica, o banco de dados pode oferecer algumas garantias sobre como ele pode ser tolerante a falhas. Essas garantias são atomicidade, consistência, isolamento e durabilidade.

O que significa atomicidade?

Atomicidade significa simplesmente que todas as consultas em uma transação devem ser bem-sucedidas para que a transação seja bem-sucedida. Se uma consulta falhar, toda a transação falhará.

Um restaurante atômico

Imagine usar uma máquina de autoatendimento em um restaurante de fast-food. A transação, neste caso, é pedir comida e consiste em duas operações separadas:

  1. Selecionar comida
  2. Fazer o pagamento

Ambas devem ser bem-sucedidas para que a transação seja bem-sucedida. Se alguma falhar, a transação falhará.

b52901d6-a9e0-43a7-a198-d56ca2a82219_544x886
Cliente fazendo um pedido em um restaurante "atômico"

Você seleciona seu hambúrguer, batata frita e uma bebida no menu da tela sensível ao toque. A máquina solicita que você pague e, somente depois de seu pagamento ser processado com sucesso, ela enviará seu pedido para a cozinha. Momentos depois, seu pedido completo está pronto e você o retira no balcão.

Essa é uma operação atômica: a transação (pedir comida) é totalmente concluída (se você selecionar seu item de comida e fizer um pagamento) ou não é concluída.

Se qualquer parte da transação falhar, toda a transação falhará. Se o seu pagamento falhar, a máquina não processará nenhuma parte do pedido, então a transação falhará. Se você fizer um pagamento sem selecionar um item de comida, a transação também falhará, pois não há nada para a cozinha preparar.

Um restaurante não atômico

Agora, considere a alternativa – um restaurante tradicional com serviço de mesa onde você pede vários pratos. À medida que cada prato é preparado, ele é trazido à sua mesa.

045c9ec7-dbb9-45b7-ad4d-357f7b7cc37c_888x1026
Cliente fazendo um pedido em um restaurante "não atômico"

Novamente, a transação é pedir comida e consiste em duas operações separadas:

  1. Selecionar comida
  2. Fazer o pagamento

Nesse restaurante não atômico, a falha em fazer um pagamento não impede que a transação seja concluída, pois você paga depois de terminar sua refeição. Falhas parciais não fazem com que uma transação falhe.

Isso cria um risco para o restaurante. Os clientes que optam por comer e sair correndo podem pedir comida à vontade e simplesmente ir embora sem pagar, causando prejuízo financeiro para o restaurante.

4136ceee-82b9-4cf0-b0de-5a0af3f1aa71_2218x1278
Restaurantes não atômicos correm o risco de clientes darem o golpe da "saidinha"

Transações atômicas

Se várias consultas SQL são agrupadas em uma transação, a atomicidade é uma garantia de que, se alguma das consultas falhar por qualquer motivo (problemas de hardware, da aplicação ou da rede), a transação será interrompida e o banco de dados retornará ao seu estado anterior, como se nada tivesse acontecido.

Sem atomicidade, se ocorrer uma falha enquanto algumas consultas estão sendo executadas, é difícil saber quais consultas foram confirmadas (ou seja, concluídas) e quais não foram. Executar as consultas novamente após uma falha pode agravar o problema, pois você corre o risco de introduzir dados incorretos no banco de dados ao executar novamente consultas que foram bem-sucedidas anteriormente.

As transações atômicas evitam essa incerteza, pois você sabe que, se a transação anterior falhou, ela falhou em sua totalidade e você pode simplesmente tentar novamente sem se preocupar em introduzir dados inconsistentes.

O que significa consistência?

Consistência pode significar coisas diferentes em engenharia de nuvem/software, dependendo do contexto. No caso de ACID, o "C" provavelmente foi adicionado para fazer a abreviação funcionar.

Consistência no contexto de ACID significa consistência nos dados, que é definida pelo criador do banco de dados. O termo técnico para consistência de dados é integridade referencial. A integridade referencial é um método para garantir que os relacionamentos entre as tabelas permaneçam consistentes. Geralmente, é aplicada por meio do uso de chaves estrangeiras.

Para entender a integridade referencial, considere o seguinte.

Imagine um sistema de biblioteca com dois tipos de cartões: um cartão de livro e um cartão de usuário.

  • O cartão de livro lista todos os livros disponíveis na biblioteca.
  • O cartão de usuário rastreia quais livros estão emprestados para quais membros.
82ea7c95-f55f-4cb6-86ea-47c11399b5c7_2324x1316
Um cartão de livro e um cartão de usuário para uma biblioteca

A regra da biblioteca é que um livro só pode ser listado no cartão de um usuário se ele existir no cartão de livro. Isso é integridade referencial. Se alguém tentar listar um livro no cartão de um usuário que não está no cartão de livro (ou seja, um livro que não existe na biblioteca), o sistema não permitirá.

Embora a atomicidade, o isolamento e a durabilidade sejam propriedades intrínsecas ao próprio banco de dados, a consistência dos dados, ou integridade referencial, não é uma propriedade intrínseca ao banco de dados.

A consistência é definida pelo criador do banco de dados. A aplicação que chama o banco de dados depende das propriedades de atomicidade e isolamento do banco de dados para manter essa consistência.

O que significa isolamento?

Isolamento é uma garantia de que transações executadas simultaneamente não devam interferir umas nas outras. Concorrência aqui se refere a duas ou mais transações tentando modificar ou ler os mesmos registros do banco de dados ao mesmo tempo.

Existem três níveis de isolamento de transação. Vou apenas explicar os dois principais abaixo, organizados do menos rígido para o mais rígido.

Leitura confirmada (read committed)

Isso oferece duas garantias. Impede leituras sujas e gravações sujas.

Sem leituras sujas (no dirty reads): ler dados de outra transação que ainda não foi confirmada é chamado de "leitura suja". Com o nível de isolamento de leitura confirmada, você verá apenas os dados que foram confirmados por outra transação.

Sem gravações sujas (no dirty writes): sobrescrever dados que já foram gravados por outra transação, mas que ainda não foram confirmados, é chamado de "gravação suja".

Para entender como funciona o isolamento de leitura confirmada, considere o seguinte exemplo.

Imagine um restaurante de fast-food com apenas um último hambúrguer especial disponível e dois clientes famintos, Marie e Marko, tentando comprá-lo simultaneamente.

ec0fd185-63c9-4821-bcac-b0140f2f4183_2360x1322
Dois clientes pedindo um hambúrguer ao mesmo tempo
  1. Marie verifica a disponibilidade de hambúrgueres e vê o último disponível. Sem que ela saiba, o pedido de Marko está sendo processado, mas ainda não foi finalizado no sistema, pois ele não pagou. Como seu pedido ainda não foi finalizado, Marie não sabe que seu pedido está em conflito com o dela. Isso é semelhante a uma transação lendo os dados confirmados mais recentemente, onde ela não vê as alterações não confirmadas (como o pedido pendente de Marko).
  2. Marie faz um pedido com base nessas informações incompletas, pensando que um hambúrguer está disponível.
  3. Assim que Marko paga, o sistema é atualizado para mostrar que não há mais hambúrgueres restantes. Isso é semelhante a uma transação sendo confirmada.
  4. O pedido de Marie terá que ser negado, pois não há mais hambúrgueres restantes.

O ponto principal aqui é a etapa nº 3. Se o pagamento de Marko falhar nesta fase, a transação não será confirmada e ainda haverá um hambúrguer disponível para Marie.

Nesse exemplo, o isolamento de leitura confirmada garante que Marie não tenha a compra do hambúrguer prematuramente negada apenas porque outra pessoa disse que o queria. Somente transações confirmadas podem ser lidas. Portanto, o hambúrguer está disponível para ser pedido, desde que ninguém o tenha pago.

Leitura repetitiva (repeatable read)

A leitura repetitiva é um nível de isolamento mais rígido, pois oferece as mesmas garantias que o isolamento de leitura confirmada – além de garantir que as leituras sejam repetitivas.

Uma leitura repetitiva garante que, se uma transação ler uma linha de dados, quaisquer leituras subsequentes dessa mesma linha de dados dentro da mesma transação produzirão o mesmo resultado, independentemente das alterações feitas por outras transações. Essa consistência é mantida durante toda a duração da transação.

Quando uma transação lê os mesmos dados duas vezes, mas vê um valor diferente em cada leitura porque uma transação confirmada atualizou o valor entre as duas leituras, isso é chamado de leitura difusa. O nível de isolamento de leitura repetitiva impede leituras difusas.

Leituras difusas não são inerentemente boas nem ruins. Tudo depende do que você está tentando alcançar.

Leituras difusas são ruins para transações de longa duração somente leitura, pois novas gravações podem ocorrer durante a transação e isso pode causar inconsistências nos dados. Exemplos de transações de longa duração somente leitura são um back-up de banco de dados e consultas analíticas normalmente usadas em um data warehouse.

Leituras repetitivas são geralmente implementadas pelo DBMS lendo de um instantâneo do banco de dados que permanece inalterado durante a transação, ignorando assim quaisquer novas gravações confirmadas nesse período.

O que significa durabilidade?

Durabilidade é uma garantia de que as alterações feitas por uma transação confirmada não devem ser perdidas. Todas as transações confirmadas devem ser persistidas em armazenamento durável e não volátil, ou seja, em disco. Isso garante que quaisquer transações confirmadas sejam protegidas, mesmo se o banco de dados travar.

Naturalmente, a durabilidade não pode proteger contra a destruição do disco que armazena os dados. Redundância adicional pode ser adicionada, tendo back-ups do seu banco de dados armazenados separadamente do original.

Juntando tudo

O ACID (Atomicidade, Consistência, Isolamento e Durabilidade) fornece um conjunto de garantias ao trabalhar com um DBMS. Embora a maioria dos DBMS relacionais sejam compatíveis com ACID, a implementação dessa conformidade pode variar.

A atomicidade garante que todas ou nenhuma das partes de uma transação sejam concluídas. Falhas parciais não são permitidas.

A consistência, ou integridade referencial, garante que os dados permaneçam precisos e confiáveis, aderindo a regras predefinidas. Ao contrário das outras prioridades, a consistência não é intrínseca ao próprio DBMS. Em vez disso, a aplicação que chama o banco de dados depende das propriedades de atomicidade e isolamento do banco de dados para manter a consistência.

O isolamento é uma garantia de que transações executadas simultaneamente não devem interferir umas nas outras. Essa é, indiscutivelmente, a propriedade mais importante, porque um DBMS pode frequentemente ter diferentes níveis de isolamento padrão, que podem precisar ser alterados com base no que é necessário para a sua aplicação.

Por fim, a durabilidade é uma garantia de que as alterações feitas por uma transação confirmada não devem ser perdidas.