⚖️
Relacional vs NoSQL: quando cada um ganha
⏱ 10 min de leitura·+50 XP
"Use NoSQL para escalar" é um dos maiores mitos da tecnologia. A escolha entre banco relacional e NoSQL não é sobre escala — é sobre o modelo de dados, os padrões de acesso e as garantias de consistência que seu sistema realmente precisa.
Os 4 modelos de dados NoSQL
| Modelo | Exemplos | Melhor para | Trade-off |
|---|---|---|---|
| Documento | MongoDB, Firestore, CouchDB | Catálogos, conteúdo variável, APIs REST | JOINs ruins, duplicação de dados |
| Chave-Valor | Redis, DynamoDB, Memcached | Cache, sessão, rate limiting, flags | Queries complexas impossíveis |
| Colunar | Cassandra, HBase, DynamoDB | Time series, analytics, alta escrita | Modelo de acesso rígido, sem JOINs |
| Grafo | Neo4j, Amazon Neptune, RedisGraph | Redes sociais, recomendações, grafos | Baixa adoção, queries específicas |
PostgreSQL: o banco que faz de tudo
-- PostgreSQL suporta:
-- 1. SQL relacional completo
-- 2. JSONB para dados semi-estruturados
-- 3. Full-text search nativo
-- 4. Arrays como tipo nativo
-- 5. UUID, ranges, geospatial (PostGIS)
-- 6. LISTEN/NOTIFY para pub/sub
-- 7. Particionamento de tabelas
-- JSONB — melhor dos dois mundos
CREATE TABLE produtos (
id SERIAL PRIMARY KEY,
nome TEXT NOT NULL,
preco DECIMAL(10,2) NOT NULL,
atributos JSONB -- schema flexível aqui
);
INSERT INTO produtos VALUES
(1, 'Camiseta', 49.90, '{"cor": "azul", "tamanho": "M", "tecido": "algodão"}'),
(2, 'Laptop', 3499.00, '{"ram": "16GB", "cpu": "M3", "armazenamento": "512GB"}');
-- Buscar por campo JSONB com índice GIN:
SELECT nome, atributos->>'cor' AS cor
FROM produtos
WHERE atributos @> '{"cor": "azul"}'; -- operador "contém"
-- Índice GIN para JSONB (busca O(log n) nos campos):
CREATE INDEX idx_produtos_atributos ON produtos USING GIN (atributos);
-- Array nativo — tags sem tabela separada
CREATE TABLE artigos (
id SERIAL PRIMARY KEY,
titulo TEXT,
tags TEXT[] -- array de strings
);
SELECT titulo FROM artigos WHERE 'python' = ANY(tags);
SELECT titulo FROM artigos WHERE tags @> ARRAY['sql', 'performance'];Quando usar cada banco
-- PostgreSQL — use quando: -- ✅ Dados com relacionamentos (usuários → pedidos → itens) -- ✅ Precisa de transações ACID reais -- ✅ Queries analíticas complexas (GROUP BY, window functions) -- ✅ Schema bem definido que não muda constantemente -- ✅ Integridade referencial é crítica (não posso ter pedido sem usuário) -- MongoDB — use quando: -- ✅ Documentos naturalmente aninhados (pedido com endereço embutido) -- ✅ Schema muda frequentemente por produto (catálogo de e-commerce) -- ✅ Equipe não conhece SQL -- ❌ Evitar quando: dados têm muitos relacionamentos (N:N), precisar de JOINs -- Redis — use quando: -- ✅ Cache de resultado de queries lentas -- ✅ Sessões de usuário (SETEX com TTL automático) -- ✅ Rate limiting (INCR + EXPIRE) -- ✅ Leaderboard (ZADD/ZRANGEBYSCORE) -- ✅ Pub/sub leve (PUBLISH/SUBSCRIBE) -- ❌ Evitar: dados primários onde perda é inaceitável (persistência limitada) -- DynamoDB — use quando: -- ✅ Escala de leitura/escrita previsível e massiva (e-commerce em pico) -- ✅ Padrão de acesso conhecido e simples (busca por PK) -- ✅ Latência consistente de 1-10ms independente de volume -- ❌ Evitar: queries ad-hoc, analytics, dados relacionais complexos
O padrão mais comum: PostgreSQL + Redis
# Arquitetura típica de API
# PostgreSQL: dados primários (fonte da verdade)
# Redis: cache de leitura + sessão + rate limiting
import redis
import psycopg2
import json
r = redis.Redis()
conn = psycopg2.connect("postgresql://localhost/mydb")
def buscar_usuario(user_id: int) -> dict:
# 1. Verificar cache Redis
cache_key = f"user:{user_id}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# 2. Buscar no PostgreSQL
with conn.cursor() as cur:
cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
row = cur.fetchone()
if not row:
return None
usuario = {"id": row[0], "nome": row[1], "email": row[2]}
# 3. Salvar no cache com TTL de 5 minutos
r.setex(cache_key, 300, json.dumps(usuario))
return usuario
# Rate limiting com Redis
def verificar_rate_limit(user_id: int, limite: int = 100) -> bool:
key = f"rate:{user_id}:{int(time.time() // 60)}" # janela de 1 minuto
count = r.incr(key)
if count == 1:
r.expire(key, 60)
return count <= limite✅
Decisão prática: comece com PostgreSQL. Ele faz 95% do que você precisa com SQL poderoso, JSONB para flexibilidade, e suporte a arrays, full-text e geospatial nativamente. Adicione Redis para cache quando as queries ficarem lentas. Considere bancos especializados apenas quando PostgreSQL realmente não servir — não antes.
💡
Próximo: SELECT e JOIN — os JOINs que resolvem 90% dos problemas reais com PostgreSQL.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo