O que é ACID?

O que é ACID?

alt text

Imagine um sistema de compra de ingressos, onde você precisa comprar um ingresso para um show. Você clica em “Comprar”, o sistema verifica se tem ingressos disponíveis, reserva um para você, debita o valor do seu cartão de crédito e gera o ingresso. Agora imagine que, no meio desse processo, o sistema falha. O que acontece com o seu ingresso? E com o seu dinheiro?

Para garantir que isso não aconteça, os bancos de dados relacionais implementam o ACID, que é um conjunto de propriedades que garantem que as transações sejam processadas de forma confiável.

Atomicidade

Na Atomicidade uma transação ou é executada por completo ou não é executada. Não existe meio termo. No nosso exemplo, se o sistema falhar no meio do processo, o dinheiro não será debitado do cartão e o ingresso não será gerado. A transação será revertida por completo.

O exemplo clássico é uma transferência entre duas contas, onde o dinheiro sai de uma conta e entra na outra.

ActiveRecord::Base.transaction do
  david.withdrawal(100)
  maria.deposit(100)
end

Esse exemplo só levará dinheiro de David e dará para Maria se nem withdrawal nem deposit levantarem uma exceção. As exceções forçarão um ROLLBACK que retorna o banco de dados ao estado anterior ao início da transação.

Mas, se nem withdrawal nem deposit levantarem uma exceção, então a transação será concluída com sucesso. Ou seja, o dinheiro sairá da conta de David e entrará na conta de Maria.

Consistência

A Consistência garante que a transação leve o banco de dados de um estado
válido para outro estado válido. Ou seja, se uma transação não for concluída com sucesso, o banco de dados não será alterado.

No exemplo dos ingressos, isso significa que não é possível comprar um ingresso com uma quantidade negativa ou com dados obrigatórios em branco. O banco rejeita qualquer operação que violaria suas regras.

ticket = Ticket.last
ticket.update(id:nil)
# =>
# PG::NotNullViolation: ERROR:  null value in column "id" of relation "tickets" 
# violates not-null constraint (ActiveRecord::NotNullViolation)
# DETAIL:  Failing row contains (null, 2026-04-29 06:01:26.693314, 173335, 
# 2026-05-06 19:20:28.89082, 90, 5).

O banco recusa a operação e permanece exatamente como estava, nenhum estado inválido é persistido.

Isolamento

No Isolamento é garantido que transações não interfiram umas nas outras. Vamos voltar ao exemplo dos ingressos. Maria e João estão comprando o último ingresso disponível.
Se o isolamento não estiver ativo, João pode ver que há um ingresso disponível e iniciar a compra também. Nesse caso, ambos podem comprar o ingresso, o que não deveria acontecer. Para evitar isso, o isolamento garante que as transações não interfiram umas nas outras. A compra de Maria deve ser concluída antes que a de João possa ser iniciada.

# app/services/ticket_purchase_service.rb
class TicketPurchaseService
  class TicketUnavailableError < StandardError; end

  def self.purchase(ticket_id:, buyer_name:)
    ActiveRecord::Base.transaction(isolation: :serializable) do
      # WITH LOCK garante que apenas uma transação por vez acesse esse registro
      ticket = Ticket.lock("FOR UPDATE").find(ticket_id)

      raise TicketUnavailableError, "Ingresso não disponível!" unless ticket.available?

      # Simula o tempo de processamento (onde a corrida aconteceria sem isolamento)
      sleep(0.1)

      ticket.update!(available: false, owner_name: buyer_name)

      puts "[#{buyer_name}] ✅ Compra realizada com sucesso!"
      ticket
    end
  rescue TicketUnavailableError => e
    puts "[#{buyer_name}] ❌ #{e.message}"
    nil
  end
end

Demonstração do problema SEM isolamento (race condition)

def demo_sem_isolamento(ticket_id)
  puts "n=== SEM ISOLAMENTO ==="

  # Maria e João leem ao mesmo tempo que o ingresso está disponível
  thread_maria = Thread.new do
    ticket = Ticket.find(ticket_id)
    sleep(0.05) # simula processamento
    if ticket.available?
      ticket.update!(available: false, owner_name: "Maria")
      puts "[Maria] ✅ Compra realizada! (mas João também pode comprar)"
    end
  end

  thread_joao = Thread.new do
    ticket = Ticket.find(ticket_id)
    sleep(0.05) # lê o mesmo estado "disponível"
    if ticket.available?
      ticket.update!(available: false, owner_name: "João")
      puts "[João] ✅ Compra realizada! (ingresso duplicado 💥)"
    end
  end

  [thread_maria, thread_joao].each(&:join)
end

Com isolamento via FOR UPDATE + transaction serializable

def demo_com_isolamento(ticket_id)
  puts "n=== COM ISOLAMENTO ==="

  thread_maria = Thread.new do
    TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "Maria")
  end

  thread_joao = Thread.new do
    TicketPurchaseService.purchase(ticket_id: ticket_id, buyer_name: "João")
  end

  [thread_maria, thread_joao].each(&:join)
end

# Resultado esperado com isolamento:
# [Maria] ✅ Compra realizada com sucesso!
# [João]  ❌ Ingresso não disponível!

Durabilidade

Na Durabilidade é garantido que uma vez que uma transação seja concluída com sucesso, ela não será desfeita, mesmo em caso de falha do sistema. No exemplo dos ingressos, uma vez que Maria compra o ingresso, ele não pode ser vendido novamente.

Isso significa que mesmo que o servidor reinicie logo após a compra, o registro da transação de Maria já está gravado permanentemente no banco.

ActiveRecord::Base.transaction do
  ticket.update!(available: false, owner_name: "Maria")
end

# Mesmo que o servidor caia aqui, a compra de Maria está salva.
ticket.reload
puts ticket.owner_name  # => "Maria"
puts ticket.available   # => false

Conclusão

Voltando ao cenário inicial: você clica em “Comprar”, o sistema processa tudo, mas no meio do caminho algo falha. Graças ao ACID, você não perde dinheiro sem
receber o ingresso (Atomicidade), o banco não aceita dadosinválidos no processo (Consistência), Maria e João não compram o mesmo ingresso ao mesmo tempo (Isolamento), e uma vez confirmada a compra, ela não some (Durabilidade). As quatro propriedades juntas são o que torna um banco de dados confiável para
transações do mundo real.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Day 1 — I’m Homeless. I Just Shipped an Autonomous Multi-Agent System.

Related Posts