Sebenta · NoSQL (UC00664)
- Introdução
- 1. SQL vs NoSQL — quando usar cada um
- 2. Tipos de NoSQL
- 3. MongoDB — em profundidade
- 4. Redis — em profundidade
- 5. Elasticsearch — search
- 6. Modelagem em document DBs
- 7. Performance e escala
- 8. Backup e segurança
- 9. Quando NÃO usar NoSQL
- 10. Boas práticas
- 11. Próximos passos
- Glossário
- Recursos
Introdução
Bases de dados SQL (PostgreSQL, MySQL) dominaram 40 anos. Servem tudo bem na maioria dos casos. Mas a partir do início dos anos 2000, surgiram necessidades novas: web em escala (Google, Facebook), real-time (chats), big data (logs, IoT), latência mínima (cache). SQL tradicional teve dificuldades.
NoSQL ("Not Only SQL") emergiu como família de soluções para esses casos. Não é "melhor" que SQL — é diferente, com trade-offs próprios. Saber quando usar SQL, quando usar NoSQL, e como modelar bem em cada um, é skill essencial em backend moderno.
Esta sebenta cobre os principais sistemas NoSQL: MongoDB (document store, mais usado), Redis (key-value, cache), Elasticsearch (search). Foca em uso prático: setup, CRUD, modelagem, integração com Python.
1. SQL vs NoSQL — quando usar cada um
SQL (relacional) — pontos fortes
- Schemas rígidos = garantia de estrutura.
- JOINs poderosos entre tabelas.
- ACID transactions garantidas.
- Decades-mature — tooling, livros, conhecimento.
- BI / reporting fácil (SQL é o padrão).
- Casos óbvios: bancos, ERPs, e-commerce, qualquer app onde estrutura é estável.
NoSQL — pontos fortes
- Schemas flexíveis ou ausentes.
- Escala horizontal (sharding nativo).
- Latência baixíssima (cache em memória).
- Throughput alto (milhões req/s).
- Specializations (search, graph, time-series).
- Casos óbvios: sessions, cache, logs, real-time chat, social graph.
Stack moderno típico
Empresas raramente usam só um. Stack comum:
- PostgreSQL — core transacional (utilizadores, orders, billing).
- Redis — cache + sessions.
- MongoDB — dados não-transacionais (logs apps, CMS, user activity).
- Elasticsearch — busca + logs (ELK).
- S3 — ficheiros / arquivo.
Polyglot persistence: ferramenta certa para cada job.
ACID vs BASE
ACID (SQL): - Atomicity: tudo ou nada. - Consistency: BD sempre válida. - Isolation: transactions concorrentes não interferem. - Durability: gravado = ficou gravado.
BASE (NoSQL): - Basically Available: sistema disponível mesmo com falhas. - Soft state: estado pode mudar mesmo sem inputs (replicação). - Eventually consistent: vai ficar consistente eventualmente.
CAP theorem: num sistema distribuído, escolhes 2 de 3: Consistency, Availability, Partition tolerance. Maior parte dos NoSQL sacrifica C para ter A+P.
2. Tipos de NoSQL
Document stores
Cada record = documento JSON-like (flexível, aninhado).
- MongoDB — dominante, BSON, queries ricas.
- CouchDB — sync-first, replicação multi-master.
- Firebase Firestore — Google, real-time, cloud-only.
- DynamoDB — AWS document/key-value híbrido.
Casos: CMS, user-generated content, catálogos, prototipagem.
Key-value
Mapeamento chave → valor (simples ou estruturado).
- Redis — in-memory, structures avançadas, sub-millisecond.
- Memcached — só string, mais simples.
- DynamoDB — managed, escala AWS.
- etcd — distributed, configuração.
Casos: cache, sessions, rate limiting, leaderboards.
Wide-column
Tabelas com famílias de colunas, esparsas. Escala para PB.
- Cassandra — write-heavy, Netflix, Apple.
- HBase — Hadoop ecosystem.
- ScyllaDB — Cassandra-compatible, mais rápido.
Casos: time-series, IoT, fraud detection, logging.
Graph
Nodes + relationships first-class.
- Neo4j — dominante, Cypher query lang.
- ArangoDB — multi-model.
- Amazon Neptune.
Casos: redes sociais, recomendações, knowledge graphs, fraud detection.
Search
Optimizado para full-text + analytics.
- Elasticsearch — dominante, parte do ELK stack.
- OpenSearch — fork AWS.
- Solr — old timer, Apache.
- Algolia — SaaS premium.
- Meilisearch / Typesense — leves, modernos.
Casos: search em sites, logging, observability.
Time-series
Especializadas para data temporal.
- InfluxDB.
- TimescaleDB (extensão Postgres).
- Prometheus (monitoring).
Casos: métricas, IoT, monitoring.
3. MongoDB — em profundidade
Setup
Docker (mais rápido):
docker run -d -p 27017:27017 --name mongo -v mongo-data:/data/db mongo:7
MongoDB Atlas (cloud, free tier):
1. atlas.mongodb.com → sign up.
2. New cluster → free tier (M0).
3. Add database user.
4. Network access → allow your IP.
5. Connection string: mongodb+srv://....
Clientes: - MongoDB Compass (GUI desktop). - mongosh (CLI). - Drivers: pymongo (Python), mongoose (Node), reactive (Java).
Hierarquia
mongod (server)
└── Database (ex: "loja")
└── Collection (ex: "products")
└── Document (BSON)
- Database = "BD".
- Collection = "tabela" (sem schema imposto).
- Document = "linha" (estrutura JSON-like, com
_idúnico).
CRUD básico (mongosh)
// Conectar
use loja
// CREATE
db.products.insertOne({
name: "Caneta",
price: 1.50,
stock: 100,
category: "Escolar"
});
db.products.insertMany([
{name: "Caderno", price: 5.00, stock: 50, category: "Escolar"},
{name: "Mochila", price: 35.00, stock: 20, category: "Acessórios"},
{name: "Estojo", price: 8.00, stock: 30, category: "Acessórios"},
]);
// READ
db.products.find();
db.products.findOne({name: "Caneta"});
// UPDATE
db.products.updateOne(
{name: "Caneta"},
{$set: {price: 1.80}}
);
db.products.updateMany(
{category: "Acessórios"},
{$inc: {price: 1.00}} // adiciona 1€
);
// DELETE
db.products.deleteOne({name: "Estojo"});
db.products.deleteMany({stock: 0});
Query operators
// Comparação
db.products.find({price: {$gt: 10}}); // >
db.products.find({price: {$gte: 10}}); // >=
db.products.find({price: {$lt: 100}}); // <
db.products.find({price: {$ne: 5}}); // !=
db.products.find({category: {$in: ["A","B"]}}); // IN
db.products.find({stock: {$nin: [0, 1]}}); // NOT IN
// Existência
db.products.find({description: {$exists: true}});
db.products.find({deleted: {$exists: false}});
// Type
db.products.find({stock: {$type: "int"}});
// Regex
db.products.find({name: /caneta/i}); // case insensitive
// Logical
db.products.find({
$and: [{price: {$gt: 5}}, {stock: {$gt: 0}}]
});
db.products.find({
$or: [{price: {$lt: 5}}, {category: "Escolar"}]
});
db.products.find({$not: {price: {$gt: 100}}});
// Array
db.products.find({tags: "novo"}); // contém
db.products.find({tags: {$all: ["a", "b"]}}); // contém ambos
db.products.find({tags: {$size: 3}}); // length = 3
Update operators
// $set — define
db.products.updateOne({_id: ID}, {$set: {price: 10}});
// $unset — remove field
db.products.updateOne({_id: ID}, {$unset: {oldField: ""}});
// $inc — incrementa
db.products.updateOne({_id: ID}, {$inc: {stock: -1, views: 1}});
// $mul — multiplica
db.products.updateMany({}, {$mul: {price: 1.10}}); // sobe 10%
// $push — adiciona a array
db.products.updateOne({_id: ID}, {$push: {tags: "promocao"}});
// $pull — remove de array
db.products.updateOne({_id: ID}, {$pull: {tags: "antigo"}});
// $addToSet — push sem duplicar
db.products.updateOne({_id: ID}, {$addToSet: {tags: "novo"}});
// $pop — remove primeiro/último
db.products.updateOne({_id: ID}, {$pop: {tags: 1}}); // último
Cursor methods
db.products
.find({category: "Escolar"})
.sort({price: -1}) // desc
.skip(10)
.limit(5);
// Projection (campos específicos)
db.products.find(
{category: "Escolar"},
{name: 1, price: 1, _id: 0} // só name e price
);
// Count
db.products.countDocuments({stock: {$gt: 0}});
// Distinct values
db.products.distinct("category");
Aggregation pipeline
Sistema poderoso de transformações encadeadas.
// Total vendas por mês
db.orders.aggregate([
// Stage 1: filtrar
{$match: {status: "completed"}},
// Stage 2: agrupar
{$group: {
_id: {
year: {$year: "$date"},
month: {$month: "$date"}
},
total: {$sum: "$amount"},
count: {$sum: 1},
avg: {$avg: "$amount"}
}},
// Stage 3: ordenar
{$sort: {"_id.year": -1, "_id.month": -1}},
// Stage 4: limitar
{$limit: 12}
]);
Stages comuns:
- $match — filtro (como WHERE).
- $group — agregação (como GROUP BY).
- $sort — ordenação.
- $project — projecção (escolher fields).
- $lookup — join com outra collection.
- $unwind — explode array em multiple docs.
- $limit, $skip.
- $addFields, $set — adicionar fields computed.
Aggregation é onde MongoDB compete seriamente com SQL.
Indexes
// Single field
db.products.createIndex({name: 1}); // ascending
db.products.createIndex({price: -1}); // descending
// Composto
db.products.createIndex({category: 1, price: -1});
// Unique
db.users.createIndex({email: 1}, {unique: true});
// Text (full-text search básico)
db.products.createIndex({name: "text", description: "text"});
db.products.find({$text: {$search: "caneta azul"}});
// TTL — auto-delete após X segundos
db.sessions.createIndex({created: 1}, {expireAfterSeconds: 3600});
// Verificar uso
db.products.find({name: "X"}).explain("executionStats");
Regra: indexes em campos usados frequentemente em queries / sorts. Cada index tem custo (write slower, espaço).
Python — pymongo
pip install pymongo
from pymongo import MongoClient, ASCENDING, DESCENDING
client = MongoClient("mongodb://localhost:27017/")
# Ou: MongoClient("mongodb+srv://user:pass@cluster.mongodb.net/")
db = client.loja
products = db.products
# Insert
products.insert_one({
"name": "Caneta",
"price": 1.50,
"stock": 100
})
# Multiple
products.insert_many([
{"name": "Caderno", "price": 5.00},
{"name": "Lápis", "price": 0.50},
])
# Find
for p in products.find({"price": {"$gt": 1}}):
print(p["name"], p["price"])
# Update
products.update_one(
{"name": "Caneta"},
{"$set": {"price": 2.00}}
)
# Aggregate
results = products.aggregate([
{"$group": {"_id": None, "total": {"$sum": "$price"}}}
])
for r in results:
print(r)
# Index
products.create_index([("name", ASCENDING)])
Validação de schema (opcional)
db.createCollection("products", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "price"],
properties: {
name: {bsonType: "string"},
price: {bsonType: "number", minimum: 0},
stock: {bsonType: "int", minimum: 0}
}
}
}
});
Embora MongoDB seja schemaless, podes impor schema quando útil.
Transactions (multi-document)
MongoDB 4.0+ suporta ACID em transactions multi-doc:
with client.start_session() as session:
with session.start_transaction():
accounts.update_one(
{"name": "Ana"},
{"$inc": {"balance": -100}},
session=session
)
accounts.update_one(
{"name": "Bruno"},
{"$inc": {"balance": 100}},
session=session
)
# commit automático se chega aqui
Funciona só em replica sets (não standalone).
4. Redis — em profundidade
Para que serve
Redis é uma structured in-memory data store. Usado para:
- Cache — resultados caros guardados temporariamente.
- Session store — login state.
- Rate limiting — quantas requests por user/min.
- Pub/sub — real-time messaging.
- Queues — background jobs (com Sidekiq, Celery).
- Leaderboards — sorted sets.
- Geospatial — proximity queries.
In-memory = microssegundos de latência. 100k+ ops/seg single thread.
Setup
docker run -d -p 6379:6379 --name redis redis:7
# CLI
docker exec -it redis redis-cli
# ou se tens redis local: redis-cli
Tipos de dados
String (valores até 512MB):
SET nome "Ana"
GET nome
SETEX session 3600 "{user:...}" # com TTL 1h
INCR contador # +1
INCRBY contador 5
EXPIRE nome 60 # TTL em segundos
TTL nome # quanto resta
DEL nome
Hash (objeto):
HSET user:1 name "Ana" age 25 email "ana@x.pt"
HGET user:1 name
HGETALL user:1
HINCRBY user:1 age 1
HDEL user:1 email
List (deque):
LPUSH queue "task1" # adiciona ao início
RPUSH queue "task2" # ao fim
LPOP queue # retira do início
RPOP queue # do fim
LRANGE queue 0 -1 # all
LLEN queue # length
Set (únicos, sem ordem):
SADD tags "python" "web" "ai"
SMEMBERS tags
SISMEMBER tags "python" # 0 ou 1
SCARD tags # count
SREM tags "ai"
SUNION tags1 tags2 # union
SINTER tags1 tags2 # intersect
Sorted set (score → member, ordenado):
ZADD leaderboard 100 "Ana" 200 "Bruno" 150 "Carla"
ZRANGE leaderboard 0 -1 WITHSCORES # asc
ZREVRANGE leaderboard 0 -1 WITHSCORES # desc
ZRANK leaderboard "Ana" # posição (0-based)
ZINCRBY leaderboard 50 "Ana" # incrementa score
Python — redis-py
pip install redis
import redis
import json
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
# Strings
r.set("name", "Ana")
r.get("name") # "Ana"
r.setex("session", 3600, "data") # TTL 1h
r.incr("counter")
# Hash
r.hset("user:1", mapping={"name": "Ana", "age": "25"})
r.hgetall("user:1") # {"name": "Ana", "age": "25"}
# List (queue)
r.lpush("queue", "task1", "task2")
task = r.rpop("queue")
# Sorted set (leaderboard)
r.zadd("leaderboard", {"Ana": 100, "Bruno": 200})
top10 = r.zrevrange("leaderboard", 0, 9, withscores=True)
Cache pattern
def get_user(user_id):
key = f"user:{user_id}"
# 1. Try cache
cached = r.get(key)
if cached:
return json.loads(cached)
# 2. Cache miss → fetch
user = fetch_from_db(user_id)
# 3. Store in cache (1h TTL)
r.setex(key, 3600, json.dumps(user))
return user
def update_user(user_id, data):
update_in_db(user_id, data)
# Invalidate cache
r.delete(f"user:{user_id}")
Rate limiting
def is_rate_limited(user_id, limit=100, window=60):
key = f"rate:{user_id}"
count = r.incr(key)
if count == 1:
r.expire(key, window)
return count > limit
Pub/Sub
# Publisher
r.publish("channel-1", "Hello!")
# Subscriber (em outro processo)
ps = r.pubsub()
ps.subscribe("channel-1")
for msg in ps.listen():
if msg["type"] == "message":
print(msg["data"])
5. Elasticsearch — search
Setup
docker run -d -p 9200:9200 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
--name es elasticsearch:8.12.0
# Verificar
curl http://localhost:9200
Conceitos
- Index = "database" (ex: products, logs).
- Document = "row" (JSON).
- Mapping = "schema" (tipos de fields).
- Shard = partição (default 1).
- Replica = cópia para HA.
CRUD
# Index document (auto-cria index)
curl -X POST "localhost:9200/products/_doc" \
-H 'Content-Type: application/json' \
-d '{"name": "Caneta azul Bic", "price": 1.50, "category": "Escolar"}'
# Get by ID
curl "localhost:9200/products/_doc/ID"
# Search
curl -X GET "localhost:9200/products/_search" \
-H 'Content-Type: application/json' \
-d '{"query": {"match_all": {}}}'
# Full-text search
curl -X GET "localhost:9200/products/_search" \
-H 'Content-Type: application/json' \
-d '{"query": {"match": {"name": "caneta azul"}}}'
Queries
// Match (full-text com analysis)
{"query": {"match": {"name": "caneta azul"}}}
// Term (exact match, sem analysis)
{"query": {"term": {"category.keyword": "Escolar"}}}
// Range
{"query": {"range": {"price": {"gte": 5, "lte": 50}}}}
// Bool (combinar)
{
"query": {
"bool": {
"must": [{"match": {"name": "caneta"}}],
"filter": [{"term": {"category.keyword": "Escolar"}}],
"must_not": [{"range": {"price": {"gt": 100}}}]
}
}
}
// Multi-match
{"query": {"multi_match": {
"query": "caneta azul",
"fields": ["name", "description"]
}}}
Aggregations
Equivalentes a GROUP BY:
{
"size": 0,
"aggs": {
"by_category": {
"terms": {"field": "category.keyword"},
"aggs": {
"avg_price": {"avg": {"field": "price"}}
}
}
}
}
Python client
pip install elasticsearch
from elasticsearch import Elasticsearch
es = Elasticsearch("http://localhost:9200")
# Index
es.index(index="products", document={
"name": "Caneta",
"price": 1.50
})
# Search
results = es.search(
index="products",
query={"match": {"name": "caneta"}}
)
for hit in results["hits"]["hits"]:
print(hit["_source"], hit["_score"])
ELK stack
Elasticsearch + Logstash + Kibana = stack popular para: - Centralizar logs de apps. - Análise + dashboards (Kibana). - Alertas.
Alternativas: Splunk (pago), Grafana Loki, OpenSearch (fork AWS).
6. Modelagem em document DBs
Princípio: design para queries
Em SQL normalizas (forma normal). Em document DBs, modelas para as queries que vais fazer.
Embed vs Reference
Embed (dados aninhados):
{
"_id": "post123",
"title": "Hello",
"author": {"name": "Ana", "avatar": "..."},
"comments": [
{"text": "Nice!", "by": "Bruno"},
{"text": "Cool", "by": "Carla"}
]
}
✅ 1 query, atómico. ❌ Doc cresce (limite 16MB). Duplicação se autor mudar.
Reference (links por ID):
// posts
{"_id": "post123", "title": "Hello", "authorId": "user1"}
// users
{"_id": "user1", "name": "Ana"}
// comments
{"_id": "c1", "postId": "post123", "text": "Nice"}
✅ Sem duplicação, docs pequenos.
❌ 2+ queries (ou $lookup).
Regras de polegar
- 1:1 ou 1:few lido sempre junto → embed.
- 1:many com many >> 100 → reference.
- N:M → reference.
- Dados mutáveis e duplicados → reference.
- Dados imutáveis snapshot (ex: address na altura da order) → embed.
Exemplo: blog
Posts (embed comments até 100)
- _id
- title, body, author (embed simples user)
- comments: [...] ← se < 100
Users (separado)
- _id, name, email, avatar
Posts (reference comments se grande)
- _id
- commentIds: [...]
Comments
- _id, postId, text, author
Anti-patterns
- Tudo num só doc gigante (bloated, hits limite 16MB).
- N+1 queries sem
$lookupou embed. - Hardcode array sizes (não suporta crescimento).
- Schema-less sem rigor → caos de tipos.
- Não usar indexes → scans lentos.
7. Performance e escala
Indexes
Críticos para performance. Cada query frequente precisa de index apropriado.
// Single
db.products.createIndex({sku: 1});
// Compound (order matters!)
db.orders.createIndex({userId: 1, date: -1});
// Análise
db.products.find({sku: "X"}).explain("executionStats");
// procurar "stage": "IXSCAN" (bom) vs "COLLSCAN" (mau)
Trade-offs: - Indexes aceleram reads. - Mas slow downs writes (precisa actualizar index). - Custam espaço.
Replica sets
3+ nodes: - Primary: aceita writes. - Secondaries: replicam primary. - Failover automático se primary cair.
High availability + read scalability.
Sharding
Para datasets enormes (TB+): - Shard key divide data entre nodes. - Queries com shard key → 1 node. - Sem shard key → scatter-gather (lento).
Setup complexo — normalmente cloud-managed (Atlas, ElastiCache).
Connection pooling
Drivers (pymongo, redis-py) fazem auto. Não criar nova connection por request:
# ✅
client = MongoClient(...) # módulo-level, reusa pool
def get_user(id):
return client.db.users.find_one({"_id": id})
8. Backup e segurança
Backup MongoDB
# Dump
mongodump --uri="mongodb://localhost:27017" --out=/backup/$(date +%F)
# Restore
mongorestore --uri="mongodb://localhost:27017" /backup/2026-05-26
Atlas: backups automáticos integrados, point-in-time recovery.
Backup Redis
# RDB snapshot (default)
SAVE # síncrono
BGSAVE # background
# AOF (append-only log) — mais resiliente
# config em redis.conf: appendonly yes
Segurança
MongoDB:
- Auth obrigatório em produção (--auth).
- Users com roles (read, readWrite, dbAdmin).
- TLS para connections.
- Network: nunca expor 27017 públicamente.
use admin
db.createUser({
user: "admin",
pwd: "strong-pass",
roles: [{role: "root", db: "admin"}]
});
Redis: - AUTH password. - TLS (Redis 6+). - Bind localhost ou VPC.
# redis.conf
requirepass strong-pass
bind 127.0.0.1
GDPR
- Right to be forgotten: soft delete + scrubbing.
- Encriptação at rest (FDE, ou field-level encryption).
- Audit logs: quem acedeu o quê quando.
9. Quando NÃO usar NoSQL
- Relações complexas que beneficiam de JOINs SQL.
- Transactions multi-tabela ACID críticas (Postgres > MongoDB).
- Reporting / BI com SQL tools (Tableau, PowerBI).
- Equipa só conhece SQL e prazo curto.
- Dados estruturados estáveis (CRM, ERP, billing).
Para a maioria dos apps web médios: PostgreSQL chega. NoSQL é addon especializado, não default.
10. Boas práticas
MongoDB
- Define schemas mentais mesmo sem rigid validation.
- Indexes para queries comuns.
- Aggregation pipelines em vez de lógica Python.
_idpara business keys quando faz sentido.- Cuidado com docs > 1MB.
- Usar Atlas para produção (managed).
Redis
- TTLs em quase tudo (não usar como BD primária).
- Não guardar dados críticos sem backup.
- Pipelining para batches.
- Cluster para HA.
Elasticsearch
- Mappings explícitos (não auto-detect em prod).
- Shard count cuidadoso (não mudável sem reindex).
- ILM (Index Lifecycle Management) para logs.
11. Próximos passos
- MongoDB Atlas managed em produção.
- Cassandra / ScyllaDB para escala extrema.
- Neo4j para graph use cases.
- Time-series: InfluxDB, TimescaleDB.
- Vector DBs (Pinecone, Weaviate, Qdrant) para AI/RAG.
- CockroachDB / Yugabyte — SQL distribuído (alternativa a NoSQL).
Glossário
- BSON: Binary JSON usado por MongoDB.
- Document: record num document DB.
- Collection: tabela em MongoDB.
- Aggregation pipeline: queries multi-stage em MongoDB.
- Replica set: cluster com primary + secondaries.
- Sharding: divisão horizontal de data.
- TTL: Time To Live (auto-expirar).
- Eventual consistency: dados sync entre nodes com delay.
- ACID vs BASE: filosofias de consistência.
- Index: estrutura que acelera queries.
- Embed vs Reference: estratégias de modelagem.
Recursos
- MongoDB University (gratuito) — cursos oficiais.
- Redis docs (redis.io/docs).
- Elastic docs (elastic.co/guide).
- NoSQL Distilled (Martin Fowler) — livro essencial.
- High Performance MongoDB.
- Designing Data-Intensive Applications (Kleppmann) — bíblia.
NoSQL não é "anti-SQL". É adição ao toolkit. Sabe SQL bem primeiro (UC0XXXX), depois aprende quando NoSQL faz sentido. Em backend moderno, vais usar ambos — e ferramentas como Redis para cache quase sempre. MongoDB para CMS, logs ou data flexível. Elasticsearch quando busca importa. Cada uma tem o seu lugar.