System Design

SQL vs NoSQL

A escolha do tipo de banco de dados impacta modelagem, consistência, escala e operação. Bancos relacionais e não-relacionais não são opostos — são ferramentas com trade-offs distintos que se adequam a problemas diferentes.

Intenção

Escolher o banco de dados certo é uma decisão de modelo, não de escala. SQL (relacional) e NoSQL (não-relacional) resolvem problemas diferentes: SQL brilha em dados estruturados com relacionamentos complexos e necessidade de consistência transacional; NoSQL oferece flexibilidade de modelagem, performance especializada ou escala horizontal para casos de uso específicos.

A falsa dicotomia "SQL vs NoSQL" leva a decisões ruins. A pergunta correta é: qual modelo de dados reflete melhor a estrutura do problema? Quais garantias de consistência o domínio exige? Qual padrão de acesso domina — leituras por chave primária, queries ad-hoc, traversal de grafos, séries temporais? A resposta define qual banco — ou combinação de bancos — é a ferramenta adequada.

Problema

Sistemas com requisitos diferentes de dados frequentemente usam o mesmo banco para tudo porque é o padrão estabelecido na equipe, não porque é a melhor ferramenta. Isso gera atritos concretos:

  • Modelagem forçada: dados hierárquicos e variáveis (um produto com atributos diferentes por categoria) modelados em tabelas relacionais exigem schemas complexos com joins profundos ou o anti-pattern de colunas genéricas (attr1, attr2, attr3).
  • Performance inadequada ao padrão de acesso: um banco relacional lendo dados por chave primária para uma API de sessão faz joins e index scans desnecessários. Um banco de documentos tentando fazer joins entre coleções diferentes na aplicação é lento e difícil de manter.
  • Escala inadequada ao modelo: escalar horizontalmente um banco relacional ACID é complexo. Usar um banco NoSQL eventually consistent para um sistema financeiro que exige consistência forte introduz riscos de integridade.
  • Schema rígido travando iteração rápida: em fases iniciais de produto, o schema muda frequentemente. Migrations em SQL com dados em produção podem ser lentas e arriscadas; um banco de documentos permite evoluir o schema por documento sem bloquear o sistema.

Como funciona

SQL — bancos relacionais

Bancos relacionais organizam dados em tabelas com schema rígido, relações declaradas entre tabelas (chaves estrangeiras) e SQL como linguagem padrão de consulta. A propriedade fundamental é ACID:

  • Atomicity: uma transação é tudo ou nada — ou todas as operações são aplicadas ou nenhuma é.
  • Consistency: uma transação leva o banco de um estado válido a outro estado válido, respeitando todas as restrições definidas.
  • Isolation: transações concorrentes se comportam como se fossem executadas sequencialmente — cada uma enxerga o banco em estado consistente.
  • Durability: uma vez confirmada, a transação persiste mesmo diante de falhas de sistema.

Exemplos: PostgreSQL (padrão para aplicações modernas), MySQL/MariaDB (amplamente usado em web), SQLite (embedded, sem servidor), SQL Server, Oracle.

NoSQL — quatro modelos principais

"NoSQL" não é um único tipo de banco — é um rótulo para bancos não-relacionais com modelos de dados radicalmente diferentes, cada um otimizado para um padrão de uso específico.

Diagrama comparativo: a mesma entidade em SQL e Documento

  PEDIDO — SQL (relacional)               PEDIDO — Documento (MongoDB)

  tabela: pedidos                         documento na coleção "pedidos":
  ┌────┬────────────┬────────┐            {
  │ id │ cliente_id │ status │              "_id": "ped-001",
  ├────┼────────────┼────────┤              "cliente_id": "cli-42",
  │  1 │     42     │  pago  │              "status": "pago",
  └────┴────────────┴────────┘              "itens": [
                                              { "produto": "Teclado", "qtd": 1, "preco": 350 },
  tabela: itens_pedido                        { "produto": "Mouse",   "qtd": 2, "preco": 89  }
  ┌────┬───────────┬─────────┬───┬───────┐  ],
  │ id │ pedido_id │ produto │qtd│ preco │  "total": 528,
  ├────┼───────────┼─────────┼───┼───────┤  "criado_em": "2026-06-30T10:00:00Z"
  │  1 │     1     │ Teclado │ 1 │  350  │}
  │  2 │     1     │ Mouse   │ 2 │   89  │
  └────┴───────────┴─────────┴───┴───────┘  Pedido e itens chegam juntos.
                                             Uma leitura, sem JOIN.
  JOIN necessário para reconstruir           Schema flexível por documento.
  o pedido com seus itens.

Documento (MongoDB, CouchDB)

Armazena dados em formato JSON/BSON. Schema flexível — cada documento pode ter campos diferentes. Ideal para dados hierárquicos que chegam e são lidos como uma unidade (pedido com itens, artigo com comentários, perfil com endereços). Queries dentro de um documento são eficientes; queries que precisam de dados de múltiplas coleções exigem lookup (semelhante a joins) ou desnormalização prévia.

Chave-Valor (Redis, DynamoDB)

Máxima simplicidade: um valor associado a uma chave. Sem schema, sem relações, sem queries complexas — apenas get, set e delete. Latência extremamente baixa porque a estrutura de dados é simples e o acesso é direto. Ideal para sessões de usuário, cache, filas simples e feature flags. Redis adiciona estruturas ricas (listas, sets, hashes, sorted sets) que o tornam versátil além do simples chave-valor.

Coluna larga (Cassandra, HBase)

Organiza dados em famílias de colunas — tabelas com colunas dinâmicas que podem variar por linha. Otimizado para escrita massiva e leitura por range de chave de partição ou range temporal. Distribui dados horizontalmente por design, com replicação automática entre nós. Usado em IoT (telemetria de dispositivos), analytics de eventos em alta vazão e time-series (logs, métricas, rastreamento de atividade).

Grafo (Neo4j, Amazon Neptune)

Modela entidades como nós e relacionamentos como arestas. Relacionamentos são cidadãos de primeira classe — armazenados explicitamente com propriedades — e traversal de grafos profundos é eficiente por design. Consultas que em SQL exigiriam múltiplos self-joins recursivos são expressas de forma direta na linguagem de query do banco de grafos (Cypher, Gremlin). Usado em redes sociais (amigos de amigos), sistemas de recomendação (usuários que compraram X também compraram Y) e detecção de fraude por relacionamentos.

CAP Theorem — consistência, disponibilidade e tolerância a partição

O teorema CAP estabelece que um banco distribuído pode garantir apenas dois dos três propriedades simultaneamente:

  • Consistency (C): todas as leituras refletem a escrita mais recente.
  • Availability (A): toda requisição recebe uma resposta (mesmo que não seja a mais recente).
  • Partition Tolerance (P): o sistema continua funcionando mesmo que mensagens entre nós sejam perdidas ou atrasadas.

Em sistemas distribuídos, falhas de rede (partição) são inevitáveis — P é obrigatório. A escolha real é entre C e A durante uma partição. Bancos relacionais tradicionais (não distribuídos) priorizam CA: em um único nó, não há partição. NoSQL distribuídos frequentemente escolhem AP (Cassandra, DynamoDB com consistência eventual) ou CP (MongoDB, Redis Cluster).

ACID vs BASE

ACID (bancos relacionais) garante consistência forte e transações completas. BASE é o modelo de muitos NoSQL distribuídos:

  • Basically Available: o sistema responde sempre, mesmo com dados possivelmente desatualizados.
  • Soft state: o estado do sistema pode mudar com o tempo, mesmo sem novas escritas (por replicação em andamento).
  • Eventually consistent: após um período de tempo sem novas escritas, todos os nós convergem para o mesmo valor.

Quando usar SQL

  • Dados com relacionamentos complexos entre entidades: pedidos, clientes, produtos, faturas — entidades que se cruzam de formas variadas e precisam de joins para consultas de negócio.
  • Transações que cruzam múltiplas entidades: débito em uma conta e crédito em outra, reserva de estoque e criação de pedido simultaneamente. ACID garante que ou tudo acontece ou nada acontece.
  • Queries ad-hoc e relatórios: SQL permite consultas arbitrárias sobre os dados sem necessidade de índices pré-definidos. Ideal para analytics, relatórios operacionais e exploração de dados.
  • Domínio bem compreendido com schema estável: quando o modelo de dados é conhecido e muda com pouca frequência, o schema rígido é uma proteção contra inconsistências, não um obstáculo.

Quando usar NoSQL Documento

  • Schema variável ou em evolução rápida — campos diferentes por entidade, produto em fase inicial com modelo em mutação.
  • Dados hierárquicos que chegam e são lidos como uma unidade, sem necessidade de joins frequentes.
  • Equipes que trabalham com objetos JSON na aplicação e querem evitar a impedância objeto-relacional.

Quando usar NoSQL Chave-Valor

  • Cache de resultados de queries ou de respostas de APIs (Redis).
  • Sessões de usuário, tokens de autenticação, estados temporários.
  • Feature flags, configurações lidas com altíssima frequência.
  • Filas simples e estruturas de dados em tempo real (leaderboards com Redis Sorted Sets).

Quando usar NoSQL Coluna Larga

  • Séries temporais com volume massivo: telemetria de IoT, métricas de infraestrutura, logs de eventos.
  • Sistemas com taxa de escrita muito alta e leitura por range temporal.
  • Dados que precisam ser distribuídos globalmente com replicação multi-região por design.

Quando usar NoSQL Grafo

  • Redes sociais onde a pergunta é "quem conhece quem" e traversal de N graus é frequente.
  • Sistemas de recomendação baseados em relacionamentos entre usuários, produtos e comportamentos.
  • Detecção de fraude por análise de padrões em redes de transações.
  • Knowledge graphs e sistemas de permissões baseados em hierarquias complexas.

Prós e contras

SQL — Prós

  • ACID completo: transações com consistência forte, sem risco de dados parcialmente escritos.
  • Linguagem padrão: SQL é universalmente conhecido e suportado por ferramentas de BI, analytics e ORM.
  • Integridade referencial declarativa: chaves estrangeiras e constraints são aplicadas pelo banco, não pela aplicação.
  • Queries ad-hoc sem índices pré-definidos: é possível consultar qualquer combinação de campos com SQL.
  • Maturidade e ecossistema: décadas de otimizações, ferramentas, conhecimento e suporte disponíveis.

SQL — Contras

  • Impedância objeto-relacional: mapear objetos hierárquicos para tabelas planas exige ORMs ou queries complexas.
  • Schema rígido: alterações de schema em produção com dados existentes exigem migrations cuidadosas.
  • Scale horizontal complexo: sharding em bancos relacionais não é trivial — a maioria escala verticalmente até um limite.
  • Performance para padrões simples de acesso: um SELECT por chave primária em PostgreSQL é mais lento que um GET em Redis para os mesmos dados.

NoSQL — Prós

  • Flexibilidade de schema: adapte o modelo de dados sem migrations bloqueantes.
  • Performance especializada: cada tipo de NoSQL é otimizado para seu padrão de acesso específico.
  • Scale horizontal por design: Cassandra, DynamoDB e MongoDB foram projetados para distribuição em múltiplos nós.
  • Modelos de dados expressivos: grafos, documentos hierárquicos, séries temporais — modelados diretamente sem impedância.

NoSQL — Contras

  • Consistência eventual: muitos NoSQL distribuídos não garantem que uma leitura após uma escrita reflete o dado mais recente.
  • Sem transações cross-coleção (em geral): operações atômicas entre múltiplas entidades são mais complexas ou impossíveis dependendo do banco.
  • Menor expressividade de query: sem SQL completo, queries complexas precisam ser planejadas na modelagem ou feitas na aplicação.
  • Ecossistema menor: menos ferramentas, menos profissionais experientes, menos respostas em fóruns para problemas específicos.

Armadilhas comuns

1. Usar NoSQL para escapar de modelagem

Schema flexível não é ausência de modelagem — é modelagem diferida que vira dívida técnica. Documentos sem estrutura definida resultam em campos com nomes inconsistentes, tipos diferentes para o mesmo campo em documentos distintos e lógica de normalização espalhada pela aplicação. Ao usar um banco de documentos, o schema ainda precisa ser definido e aplicado — a diferença é que quem o aplica é a aplicação, não o banco.

Regra prática: trate o schema de um banco NoSQL como código: versione-o, documente-o e valide-o na camada de aplicação. A flexibilidade do banco não é uma licença para criar documentos arbitrários.

2. Joins no código por falta de planejamento

O anti-pattern mais comum em bancos de documentos: dados são desnormalizados de forma insuficiente — ou não desnormalizados — e a aplicação faz múltiplas queries a diferentes coleções para reconstruir uma entidade, depois junta os resultados no código. O resultado é mais lento e mais frágil do que o JOIN SQL original, sem os benefícios da desnormalização.

Modelagem em bancos de documentos deve partir do padrão de acesso, não do modelo de domínio: defina quais dados chegam juntos em uma leitura e desnormalize-os no mesmo documento.

3. Supor que NoSQL escala e SQL não

PostgreSQL com replicação de leitura, connection pooling (PgBouncer), particionamento de tabelas e índices bem planejados atende cargas enormes — a maioria das aplicações nunca vai precisar de mais do que isso. A decisão entre SQL e NoSQL é de modelo de dados e padrão de acesso, não de capacidade de escala. Usar Cassandra para um sistema com 1000 usuários porque "NoSQL escala" é over-engineering sem benefício.

4. Misturar consistência forte e eventual sem perceber

Bancos NoSQL com replicação eventual podem retornar dados desatualizados em leituras imediatamente após escritas. Ler de uma réplica de leitura em um PostgreSQL com replicação assíncrona tem o mesmo problema — mas em SQL, a consistência forte é o padrão e a eventual é opt-in. Em muitos NoSQL, a consistência eventual é o padrão e a forte é opt-in (e mais cara em latência). Conheça o padrão de consistência do seu banco e configure-o explicitamente para cada operação crítica.

Arquiteturas e padrões relacionados

Caching é frequentemente aplicado sobre bancos SQL para reduzir a carga de queries custosas: os resultados são armazenados em Redis e servidos diretamente sem tocar o banco para requisições subsequentes. Redis (NoSQL Chave-Valor) e PostgreSQL (SQL) são usados em conjunto na maioria dos sistemas modernos — não como concorrentes, mas como camadas complementares.

O CQRS permite que o modelo de escrita e o modelo de leitura usem bancos diferentes. O Write Model pode persistir em PostgreSQL (consistência ACID para as transações de negócio) enquanto o Read Model é projetado em MongoDB ou Redis (performance de leitura otimizada para o padrão de consulta). Essa combinação é um dos casos de uso mais concretos para a escolha deliberada de múltiplos tipos de banco no mesmo sistema.

Em Event-Driven Architecture, o event store (onde os eventos são persistidos de forma durável) frequentemente usa um banco relacional ou um banco especializado em append-only — enquanto as projeções derivadas dos eventos podem viver em qualquer tipo de banco adequado ao padrão de leitura: Elasticsearch para full-text, Redis para cache, Cassandra para séries temporais.