🧠FFVAcademy
⚖️

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

ModeloExemplosMelhor paraTrade-off
DocumentoMongoDB, Firestore, CouchDBCatálogos, conteúdo variável, APIs RESTJOINs ruins, duplicação de dados
Chave-ValorRedis, DynamoDB, MemcachedCache, sessão, rate limiting, flagsQueries complexas impossíveis
ColunarCassandra, HBase, DynamoDBTime series, analytics, alta escritaModelo de acesso rígido, sem JOINs
GrafoNeo4j, Amazon Neptune, RedisGraphRedes sociais, recomendações, grafosBaixa 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