Design patterns para escalabilidade
1. Fundamentos da Escalabilidade com Design Patterns
Escalabilidade é a capacidade de um sistema lidar com cargas crescentes sem degradação de desempenho. Existem duas abordagens principais: escalabilidade vertical (adicionar recursos a uma única máquina) e escalabilidade horizontal (adicionar mais instâncias). Design patterns específicos são essenciais para viabilizar a escalabilidade horizontal, que é a abordagem mais resiliente e econômica em nuvem.
Os patterns clássicos do GoF (Gang of Four) foram criados para sistemas monolíticos e não abordam problemas de distribuição, concorrência ou tolerância a falhas em larga escala. Para escalabilidade moderna, precisamos de patterns que respeitem princípios como GRASP (baixo acoplamento, alta coesão) e YAGNI (não adicionar complexidade desnecessária). A escolha correta de patterns deve equilibrar o custo da implementação com o ganho real de escala.
2. Padrões de Particionamento e Distribuição
Sharding Pattern
Divide dados em partições independentes (shards) para distribuir carga de escrita e leitura.
# Exemplo de sharding por ID de usuário
def get_shard(user_id):
shard_count = 4
shard_id = user_id % shard_count
return f"db_shard_{shard_id}"
# Roteamento de consulta
def save_user(user):
shard = get_shard(user.id)
shard.save(user)
CQRS (Command Query Responsibility Segregation)
Separa operações de escrita (commands) e leitura (queries) em modelos distintos.
# Command: escreve no banco principal
class CreateOrderCommand:
def execute(order_data):
db_primary.orders.insert(order_data)
event_bus.publish("order_created", order_data)
# Query: lê de uma réplica otimizada
class GetOrderQuery:
def execute(order_id):
return db_read_replica.orders.find_by_id(order_id)
Event Sourcing
Persiste eventos como fonte única de verdade, permitindo reconstrução de estado e escalabilidade assíncrona.
# Evento armazenado
event = {
"type": "ORDER_CREATED",
"data": {"order_id": 123, "items": ["book"]},
"timestamp": "2024-01-01T10:00:00Z"
}
event_store.append(event)
3. Padrões de Cache e Aceleração
Cache-Aside
A aplicação gerencia o cache manualmente, consultando-o antes do banco.
def get_user(user_id):
user = cache.get(f"user:{user_id}")
if not user:
user = db.users.find_by_id(user_id)
cache.set(f"user:{user_id}", user, ttl=3600)
return user
Read-Through / Write-Through
O cache atua como proxy transparente para o banco de dados.
# Read-Through: cache carrega automaticamente
user = cache.load("user:123", lambda: db.users.find(123))
# Write-Through: cache atualiza banco
cache.set("user:123", new_data, write_through=True)
Cache Invalidation
Estratégias para manter consistência: TTL, notificação por eventos e write-behind.
# TTL simples
cache.set("product:456", data, ttl=300)
# Invalidação por evento
def on_product_updated(event):
cache.delete(f"product:{event.product_id}")
4. Padrões de Desacoplamento e Resiliência
Queue-Based Load Leveling
Usa filas para suavizar picos de requisição, processando em ritmo controlado.
# Produtor envia para fila
queue.send("order_queue", order_data)
# Consumidor processa em lotes
def process_orders():
batch = queue.receive("order_queue", max_messages=10)
for order in batch:
process_order(order)
Circuit Breaker
Protege contra falhas em cascata em sistemas distribuídos.
circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=30)
def call_external_service():
if circuit.is_open():
return fallback_response()
try:
result = external_api.call()
circuit.record_success()
return result
except Exception:
circuit.record_failure()
raise
Bulkhead
Isola recursos por pool ou thread para evitar colapso total.
# Pools separados para diferentes serviços
pool_a = ThreadPoolExecutor(max_workers=10) # Serviço crítico
pool_b = ThreadPoolExecutor(max_workers=3) # Serviço não crítico
pool_a.submit(critical_task)
pool_b.submit(non_critical_task)
5. Padrões de Processamento Assíncrono
Event-Driven Architecture
Mensageria e streams para escalar processamento de forma desacoplada.
# Publicador de eventos
event_bus.publish("user.registered", {"user_id": 123})
# Consumidor assíncrono
@event_handler("user.registered")
def send_welcome_email(event):
email_service.send(event.data["user_id"])
Saga Pattern
Coordena transações distribuídas sem bloqueio, com compensações em caso de falha.
class OrderSaga:
def execute(self):
step1 = reserve_inventory(order)
if step1.failed:
return compensate_all()
step2 = charge_payment(order)
if step2.failed:
compensate_step1()
return
step3 = confirm_order(order)
saga_complete()
Outbox Pattern
Garante entrega de eventos sem perda de consistência.
# Transação atômica: salva dados + evento
def create_order(order):
with db.transaction():
db.orders.insert(order)
db.outbox.insert({"type": "order_created", "data": order})
# Worker processa outbox
def process_outbox():
events = db.outbox.select_pending()
for event in events:
message_queue.send(event)
db.outbox.mark_sent(event.id)
6. Padrões de Escalabilidade em Nível de Aplicação
Stateless Services
Remove estado da aplicação para escalar horizontalmente.
# Serviço sem estado: sessão armazenada externamente
def handle_request(request):
session_data = redis.get(request.session_id)
result = process(session_data, request.body)
return result
Sidecar Pattern
Componentes auxiliares (logs, monitoria) sem acoplar ao core.
# Sidecar para logging
sidecar:
- name: log-collector
image: fluentd
volumeMounts:
- mountPath: /var/log/app
name: app-logs
Throttling e Rate Limiting
Controla uso para evitar sobrecarga.
# Rate limiter por IP
limiter = RateLimiter(max_requests=100, window_seconds=60)
def api_endpoint(request):
if not limiter.allow(request.ip):
return HTTP 429 Too Many Requests
return process_request(request)
7. Padrões de Infraestrutura e Observabilidade
Service Discovery
Registro e descoberta dinâmica de instâncias.
# Registro automático
service_registry.register("user-service", "192.168.1.10:8080")
# Descoberta
instances = service_registry.discover("order-service")
target = instances[0] # Load balancing simples
Health Check Patterns
Readiness e liveness probes para orquestração.
# Readiness: serviço pronto para receber tráfego
@app.route("/health/ready")
def readiness():
if db.is_connected() and cache.is_connected():
return "OK", 200
return "Not Ready", 503
# Liveness: serviço ainda vivo
@app.route("/health/live")
def liveness():
return "OK", 200
Distributed Tracing
Rastreamento de requisições entre serviços para debug de gargalos.
# Inicia trace
trace_id = generate_trace_id()
span = tracer.start_span("process_order", trace_id=trace_id)
# Propagação entre serviços
headers = {"trace_id": trace_id}
response = http_client.get("http://payment-service", headers=headers)
Referências
- Microsoft - Cloud Design Patterns: Sharding — Documentação oficial da Microsoft sobre o padrão Sharding para escalabilidade horizontal de bancos de dados.
- Martin Fowler - CQRS — Artigo fundamental de Martin Fowler explicando o padrão Command Query Responsibility Segregation.
- AWS - Event Sourcing Pattern — Guia prescritivo da AWS sobre implementação de Event Sourcing para sistemas escaláveis.
- Netflix Tech Blog - Circuit Breaker Pattern — Artigo técnico da Netflix sobre uso de Circuit Breaker para resiliência em larga escala.
- Redis - Cache-Aside Pattern — Tutorial oficial da Redis sobre implementação do padrão Cache-Aside para aceleração de consultas.
- Uber Engineering - Distributed Tracing — Case da Uber sobre implementação de tracing distribuído para observabilidade em microsserviços.
- Google Cloud - Queue-Based Load Leveling — Arquitetura de referência do Google Cloud para suavização de picos com filas.