7

O banco de dados que nunca esquece: como o Nubank usa o Datomic (e por que ele não faz UPDATE)

Esse post é a versão aprofundada de um vídeo que fiz no meu Instagram, o @bueno_dev, onde posto conteúdo tech todos os dias. Aqui eu vou mais fundo, com código e trade-offs.


Num banco relacional tradicional, quando um cliente muda de endereço, você roda algo assim:

UPDATE clientes SET endereco = 'Rua B' WHERE id = 42;

E pronto: a "Rua A" sumiu. Se amanhã alguém perguntar "qual era o endereço desse cliente no dia 1º de março?", a resposta é "não sei", a não ser que você tenha criado uma tabela de histórico, triggers ou um log de auditoria à parte.

O Datomic, banco que o Nubank usa desde 2014, parte de uma ideia diferente: ele não sobrescreve nada.

Fatos, não linhas

No Datomic, a unidade básica não é uma linha, é um datom, um fato com cinco partes:

[entidade  atributo           valor    transação  adicionado?]
[42        :cliente/endereco  "Rua A"  1001       true]

Quando o cliente muda de endereço, o Datomic não apaga o fato antigo. Ele registra novos fatos:

[42  :cliente/endereco  "Rua A"  1001  true]    ; 15/02: passou a valer
[42  :cliente/endereco  "Rua A"  2050  false]   ; 20/03: deixou de valer
[42  :cliente/endereco  "Rua B"  2050  true]    ; 20/03: novo valor

Os fatos são imutáveis. O que muda é qual deles vale agora. O "estado atual" é só uma visão sobre o acúmulo de fatos.

Em Clojure, a escrita fica assim:

;; tx 1001
@(d/transact conn [{:cliente/id 42 :cliente/endereco "Rua A"}])

;; tx 2050 — não é um UPDATE destrutivo, é um novo fato
@(d/transact conn [{:cliente/id 42 :cliente/endereco "Rua B"}])

Viagem no tempo de graça

Como nada é apagado, dá para consultar o banco como ele era em qualquer momento:

(-> (d/as-of (d/db conn) #inst "2026-03-01")
    (d/pull [:cliente/endereco] [:cliente/id 42]))
;; => {:cliente/endereco "Rua A"}

E dá para ver toda a história de um atributo:

(d/q '[:find ?endereco ?tx ?adicionado
       :where [?c :cliente/id 42]
              [?c :cliente/endereco ?endereco ?tx ?adicionado]]
     (d/history (d/db conn)))

Isso não é uma feature que você precisa montar. É consequência direta do modelo de dados.

"Um Git para os dados"

Edward Wible, cofundador do Nubank, já descreveu o Datomic como um Git para os dados. A analogia funciona bem: cada transação é como um commit, com quando aconteceu e o que mudou. Você nunca reescreve o passado, só adiciona em cima.

$ git log
● tx 2050 · 20/03   endereço: Rua A → Rua B
● tx 1001 · 15/02   endereço: Rua A

Por que isso importa tanto num banco

1. Reconstruir estado a partir dos eventos. Um saldo é o resultado de débitos e créditos aplicados em ordem: +100, -30, -20, +50 → R$ 100. Com o histórico completo e ordenado, dá para reprocessar e chegar ao mesmo resultado, ou descobrir exatamente onde algo divergiu.

2. Auditoria sem esforço extra. Quando um regulador, ou o time de fraude, pergunta "quem mudou o quê e quando?", a resposta já está no banco. Não depende de alguém ter lembrado de logar.

3. Debug de produção. Dá para rodar uma query contra o banco como ele estava no momento do bug, sem montar um snapshot.

Nem tudo são flores (trade-offs)

  • O armazenamento cresce sempre. Dado que muda muito (um contador, uma posição de GPS a cada segundo) costuma ser candidato ruim. Para esses casos é comum usar :db/noHistory no atributo ou guardar em outro lugar.
  • "Nunca apagar" vs. LGPD. Às vezes você precisa apagar de verdade. O Datomic tem excision para remover dados permanentemente, mas é uma operação pesada e deliberada, não um DELETE do dia a dia.
  • Escrita centralizada. As escritas passam por um único processo (o transactor), o que simplifica a consistência, mas limita o throughput de escrita comparado a bancos distribuídos.
  • Curva de aprendizado. Datalog e Clojure não são o caminho mais comum no mercado brasileiro, e isso pesa na hora de contratar.

A linha do tempo

  • 2014: o Nubank escolhe o Datomic.
  • 2020: o Nubank adquire a Cognitect, empresa por trás do Datomic e da linguagem Clojure.
  • 2023: o Datomic passa a ser gratuito para todo mundo.

Ou seja: dá para testar hoje, sem pagar nada.

Conclusão

A lição que fica, mesmo que você nunca use Datomic: às vezes o histórico vale mais que o valor atual. Vale pensar nisso antes de escrever o próximo UPDATE sobre um dado que alguém vai querer auditar daqui a um ano.

E você, já precisou reconstruir o histórico de um dado que tinha sido sobrescrito? Como resolveu?


Se curtiu, o vídeo com as animações desse conceito (os datoms, a viagem no tempo e o saldo sendo reprocessado) está no @bueno_dev.

Carregando publicação patrocinada...
2

Sobre o "nunca apagar vs. LGPD", o roteiro típico é: chega um pedido de eliminação e o time descobre o dado no banco, no histórico, nos backups e num dump de staging esquecido.

Sobre o excision, dois detalhes pegam muita gente:

  • só existe no Datomic Pro, via peer API. No Cloud e no Datomic Local, :db/excise não é suportado;
  • mesmo no Pro, a remoção só acontece no próximo indexing job. A doc sugere no máximo alguns milhares de datoms por vez em produção e recomenda backup antes. Resultado: mais uma cópia do dado que você queria apagar.

Por isso uma saída bem comum é crypto-shredding: os atributos pessoais vão cifrados com uma chave por titular, guardada fora do banco (KMS, vault), e "apagar" vira destruir a chave. Histórico e backups continuam lá, mas ilegíveis; o que já saiu decifrado pra log ou dump, não. O preço é perder query por valor: buscar por CPF exige um índice à parte (tipo um HMAC), que também precisa ficar fora do Datomic, senão sobra no histórico.

Em fintech ainda tem o art. 16: muita coisa fica guardada por obrigação legal ou regulatória, então o art. 18 raramente significa apagar tudo.

Alguém já levou crypto-shredding pro DPO e foi aceito como eliminação, ou pediram limpeza dos backups também?

1
1