← todos os artigos

Databases

SQL ou NoSQL? Como eu penso sobre essa escolha

A pergunta não é qual database é melhor. É se eu conheço meus padrões de acesso de antemão, e se o dado tem relacionamentos que eu preciso que o próprio database proteja.

SQL ou NoSQL? Como eu penso sobre essa escolha

Por muito tempo achei que isso era uma pergunta sobre escala: SQL para aplicações normais, NoSQL quando você fica grande. Não é bem isso. Os dois projetos onde eu de fato fiz essa escolha deliberadamente me ensinaram que é sobre outra coisa completamente diferente.

A pergunta que eu de fato faço primeiro

Eu sei como esse dado vai ser lido antes de desenhá-lo?

Isso soa ao contrário, já que em SQL você desenha o schema em volta do dado e depois faz query dele de qualquer jeito que precisar mais tarde. Essa flexibilidade é todo o ponto de um database relacional, e é por isso que é o default certo quando eu ainda não sei todas as formas em que o dado vai ser usado.

NoSQL basicamente inverte isso. Você desenha em volta das queries que vai rodar, e o formato do storage segue do padrão de acesso.

Onde isso tomou a decisão por mim

O URL shortener que construí é o exemplo mais claro. Ele só faz uma coisa: dado um short code, devolver a URL original. Um lookup, por uma key, sempre. Sem joins, sem queries de relatório, sem "me mostre todas as URLs criadas na última terça agrupadas por domínio."

Esse é um padrão de acesso que eu conhecia completamente antes de escrever uma linha de código, o que é exatamente o caso em que Cassandra faz sentido. Ele é feito para lookups com key e espalha o dado entre nodes conforme cresce.

O payment orchestrator foi na direção oposta, em SQL, e o motivo também não foi escala. Um pagamento tem attempts, e fraud flags, e esses relacionamentos precisam ser aplicados. Um attempt que referencia um pagamento que não existe é dado corrompido, e eu quero que o database o rejeite em vez de confiar que meu código nunca vai cometer esse erro.

Relacionamentos e transactions são a linha divisória real

Aquele exemplo de pagamento é a versão geral. Quando a corretude do meu dado depende de várias linhas mudando juntas ou nenhuma mudando, um database relacional me dá essa garantia diretamente.

O caso de transferência de dinheiro é o óbvio, onde debitar uma conta e creditar outra têm que ter sucesso as duas ou falhar as duas. Mas é mais comum do que isso. Criar um pedido e seus itens de linha, atualizar um pagamento e registrar o attempt que o mudou. Em qualquer lugar onde "metade disso aconteceu" é um estado inaceitável, transactions estão fazendo trabalho de verdade para mim.

Foreign keys são a mesma ideia aplicada a relacionamentos. O database recusa me deixar criar um órfão, então essa classe inteira de bug nunca chega em produção independentemente do que meu código de aplicação faz.

Onde eu genuinamente recorreria a NoSQL

Alguns casos onde acho que claramente faz sentido, além do de lookup por key:

Dado sem formato fixo, onde registros diferentes legitimamente têm campos diferentes e forçá-los em colunas significa ou uma tabela muito larga cheia de nulls ou uma pilha de tabelas quase vazias unidas por join.

Volume de escrita muito alto onde as escritas são independentes umas das outras, tipo event logs ou telemetria, onde nada precisa estar consistente com nada mais no momento em que é escrito.

Caching, que na verdade é uma coisa separada mas fica na mesma família. Redis na frente do URL shortener não é uma escolha de database, é uma camada segurando dado quente que o database de verdade também tem.

O que eu diria para mim mesmo há um ano

Vá de SQL por default. Não porque é melhor, mas porque é o que não exige que você esteja certo sobre seus padrões de acesso com antecedência, e como desenvolvedor júnior num projeto novo eu geralmente não estou.

Recorra a NoSQL quando você conseguir nomear o motivo específico. "Escala melhor" não é um motivo, já que um database relacional num server normal aguenta muito mais tráfego do que a maioria dos projetos jamais vê. "Isso é um lookup por uma key e eu preciso disso espalhado entre nodes" é um motivo. "Esses registros genuinamente não compartilham um formato" é um motivo.

O erro que eu teria cometido sem construir os dois é assumir que a escolha é sobre tamanho. É sobre se eu conheço os padrões de acesso de antemão, e se o dado tem relacionamentos que eu quero que o próprio database proteja.