Pular para conteúdo

RFC-016: Performance Monitoring e Optimization

Campo Valor
Status Draft
Author Time de Tecnologia
Created 2026-02-05
Updated 2026-02-05
Reunião A ser agendada
Deciders Todo o time técnico

Status

🟡 Draft - RFC em elaboração, aguardando apresentação

Contexto e Problema

Performance mencionada em documentação mas sem estratégia formal:

  • Performance budgets não definidos
  • Load testing não é feito sistematicamente
  • Database query optimization é reativa
  • Caching strategy não está documentada
  • APM (Application Performance Monitoring) não está configurado

Por que resolver isso agora?

  • Usuários reportam lentidão ocasional
  • Falta visibilidade de performance bottlenecks
  • N+1 queries não são detectados
  • Load testing é manual e raro
  • Não sabemos capacity limits

Impacto de não resolver

  • Performance degrada gradualmente
  • Bottlenecks não detectados até ficarem críticos
  • Capacity planning impossível
  • Bad user experience
  • Custos aumentam (over-provisioning por falta de dados)

Documentação relacionada: - docs/observability/monitoring.md - Métricas atuais - docs/architecture/overview.md - Arquitetura - docs/infrastructure/database/overview.md - Performance DB - docs/architecture/aws-services.md - CloudFront, ElastiCache - docs/observability/cloudwatch.md - X-Ray tracing

Proposta de Solução

Performance budgets, load testing semanal, query optimization obrigatória, e caching strategy clara.

Performance Budgets

API Latency: - Target: P50 < 200ms, P95 < 500ms, P99 < 1s - Hard limit: P99 < 2s (blocker)

Page Load (Frontend): - Target: First Contentful Paint < 1.5s - Target: Time to Interactive < 3s - Hard limit: TTI < 5s

Database Queries: - Target: < 100ms per query - Hard limit: < 500ms per query - N+1 queries: Zero tolerance

Lambda Cold Start: - Target: < 1s - Mitigation: Provisioned concurrency para críticos

API Throughput: - Target: Suportar 100 RPS (requests per second) - Peak: 500 RPS (com auto-scaling)

Load Testing

Cadência: - Semanal: Smoke load tests (baseline) - Pre-release: Full load tests - Quarterly: Stress tests (encontrar limites)

Ferramentas: Locust

# locustfile.py
from locust import HttpUser, task, between

class APIUser(HttpUser):
    wait_time = between(1, 3)
    host = "https://staging.seuapp.com"

    def on_start(self):
        """Login once per user."""
        response = self.client.post("/api/v1/auth/login", json={
            "email": "load-test@example.com",
            "password": "test123"
        })
        self.token = response.json()["access_token"]
        self.headers = {"Authorization": f"Bearer {self.token}"}

    @task(3)
    def list_products(self):
        """List products (most common operation)."""
        self.client.get("/api/v1/products", headers=self.headers)

    @task(1)
    def create_order(self):
        """Create order (less frequent)."""
        self.client.post("/api/v1/orders", headers=self.headers, json={
            "product_id": 123,
            "quantity": 1
        })

    @task(2)
    def get_user_profile(self):
        """Get user profile."""
        self.client.get("/api/v1/users/me", headers=self.headers)

Executar load tests:

# Baseline (semanal)
locust -f locustfile.py \
  --headless \
  --users 50 \
  --spawn-rate 5 \
  --run-time 5m \
  --html report.html

# Stress test (quarterly)
locust -f locustfile.py \
  --headless \
  --users 500 \
  --spawn-rate 50 \
  --run-time 30m \
  --html stress-report.html

CI/CD integration:

# .github/workflows/load-test.yml
name: Weekly Load Test

on:
  schedule:
    - cron: '0 2 * * 1'  # Monday 2am

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run load test
        run: |
          pip install locust
          locust -f locustfile.py \
            --headless \
            --users 50 \
            --spawn-rate 5 \
            --run-time 5m \
            --csv=results

      - name: Check performance budgets
        run: |
          # Parse results.csv
          # Fail if P95 > 500ms or error rate > 1%
          python scripts/check_performance_budgets.py results_stats.csv

Query Optimization

N+1 Detection:

# Django: django-silk or django-debug-toolbar
# settings.py (staging only)
if DEBUG:
    MIDDLEWARE += ['silk.middleware.SilkyMiddleware']
    INSTALLED_APPS += ['silk']

# Detecta N+1 queries automaticamente

Obrigatório: select_related / prefetch_related

# ❌ Bad: N+1 queries
users = User.objects.all()
for user in users:
    print(user.profile.bio)  # Query per user!

# ✅ Good: 1 query
users = User.objects.select_related('profile').all()
for user in users:
    print(user.profile.bio)  # No extra query

# ✅ Good: prefetch_related para many-to-many
users = User.objects.prefetch_related('orders').all()

FastAPI (SQLAlchemy):

# ✅ Joinedload para relationships
from sqlalchemy.orm import joinedload

users = await db.execute(
    select(User).options(joinedload(User.profile))
)

# ✅ Selectinload para collections
users = await db.execute(
    select(User).options(selectinload(User.orders))
)

Database Indexes:

# Django
class Product(models.Model):
    name = models.CharField(max_length=200, db_index=True)  # Single index

    class Meta:
        indexes = [
            models.Index(fields=['name', 'category']),  # Composite index
            models.Index(fields=['-created_at']),       # DESC index
        ]

Slow Query Monitoring:

-- PostgreSQL: habilitar pg_stat_statements
CREATE EXTENSION pg_stat_statements;

-- Queries mais lentas
SELECT 
  query,
  calls,
  total_time,
  mean_time,
  max_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 20;

Caching Strategy

Levels:

  1. Browser (CloudFront): Static assets (1 ano)
  2. CDN (CloudFront): API responses públicas (5 min)
  3. Application (Redis): Hot data (5-60 min)
  4. Database (PostgreSQL): Query cache, shared buffers

Redis caching patterns:

# Cache-aside pattern
async def get_product(product_id: int):
    # 1. Try cache
    cached = await redis.get(f'product:{product_id}')
    if cached:
        return json.loads(cached)

    # 2. Fetch from DB
    product = await db.get(Product, product_id)

    # 3. Cache for 5 min
    await redis.set(
        f'product:{product_id}',
        json.dumps(product.dict()),
        ex=300
    )

    return product

# Cache invalidation
async def update_product(product_id: int, data: dict):
    product = await db.get(Product, product_id)
    # Update...
    await db.commit()

    # Invalidate cache
    await redis.delete(f'product:{product_id}')

CloudFront caching:

# SAM template
CloudFrontDistribution:
  Type: AWS::CloudFront::Distribution
  Properties:
    DistributionConfig:
      CacheBehaviors:
        - PathPattern: /api/v1/products*
          TargetOriginId: api-gateway
          CachePolicyId: !Ref APIGatewayCachePolicy
          AllowedMethods: [GET, HEAD, OPTIONS]
          CachedMethods: [GET, HEAD]

APIGatewayCachePolicy:
  Type: AWS::CloudFront::CachePolicy
  Properties:
    CachePolicyConfig:
      MinTTL: 0
      MaxTTL: 300      # 5 min
      DefaultTTL: 60   # 1 min
      ParametersInCacheKeyAndForwardedToOrigin:
        QueryStringsConfig:
          QueryStringBehavior: all
        HeadersConfig:
          HeaderBehavior: whitelist
          Headers: [Authorization]

APM (Application Performance Monitoring)

AWS X-Ray:

from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all

# Patch libraries
patch_all()  # boto3, requests, httpx, psycopg2

# Manual subsegments
@xray_recorder.capture('get_user_with_orders')
async def get_user_with_orders(user_id: int):
    # Automatically traced
    user = await db.get(User, user_id)
    orders = await db.query(Order).filter_by(user_id=user_id).all()
    return user, orders

# Metadata
xray_recorder.put_metadata('user_id', user_id)
xray_recorder.put_annotation('is_premium', user.is_premium)

CloudWatch Insights queries:

-- P95 latency por endpoint
fields @timestamp, request_path, duration_ms
| filter request_path like /^\/api\/v1\//
| stats pct(duration_ms, 95) as p95_latency by request_path
| sort p95_latency desc

-- Slowest database queries
fields @timestamp, query, duration_ms
| filter event = "database_query"
| sort duration_ms desc
| limit 20

Performance Testing em CI/CD

# .github/workflows/performance-test.yml
name: Performance Test

on:
  pull_request:
    branches: [main]

jobs:
  performance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to perf test environment
        run: sam deploy --config-env perf-test

      - name: Run load test
        run: |
          locust -f locustfile.py \
            --headless \
            --users 50 \
            --spawn-rate 10 \
            --run-time 2m \
            --csv=results

      - name: Check budgets
        run: |
          # Fail if P95 > 500ms
          python scripts/check_performance_budgets.py results_stats.csv

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = fs.readFileSync('results_stats.csv', 'utf8');
            // Parse and comment metrics on PR

Alternativas Consideradas

Opção 1: k6 ao invés de Locust

Prós: - Mais performático (Go) - Scripting em JavaScript

Contras: - Time conhece Python melhor

Por que não escolhemos: Locust em Python é mais acessível para o time.

Opção 2: Memcached ao invés de Redis

Prós: - Mais simples - Apenas caching

Contras: - Redis tem mais features (pub/sub, sorted sets) - Redis é padrão atual

Por que não escolhemos: Redis oferece mais value.

Opção 3: Datadog APM ao invés de X-Ray

Prós: - Interface melhor - Mais features

Contras: - Custo alto - X-Ray já está disponível

Por que não escolhemos: X-Ray é suficiente e sem custo adicional.

Análise de Impacto

Impacto Técnico

  • Performance budgets enforcement
  • Load testing semanal automatizado
  • Query optimization obrigatória
  • Caching em múltiplos níveis

Impacto em Negócio

  • ✅ User experience consistentemente boa
  • ✅ Capacity planning baseado em dados
  • ✅ Custos otimizados
  • ✅ SLAs cumpridos
  • ⚠️ Overhead de load testing

Riscos

Risco: Load tests causam custos AWS

Mitigação: Ambiente separado para perf testing, limites configurados.

Plano de Implementação

Fase 1: Budgets (Semana 1)

  • Definir performance budgets
  • Medir baseline atual
  • Documentar

Fase 2: Load Testing (Semana 2)

  • Configurar Locust
  • Criar scenarios de teste
  • CI/CD integration

Fase 3: Query Optimization (Semana 3-4)

  • Audit queries atuais
  • Adicionar índices faltantes
  • Refatorar N+1 queries

Fase 4: Caching (Semana 5-6)

  • Implementar Redis caching
  • CloudFront para static
  • Cache invalidation strategy

Fase 5: APM (Semana 7)

  • Configurar X-Ray
  • Instrumentar código crítico
  • Dashboards

Métricas de Sucesso

Após 1 mês: - ✅ Performance budgets definidos e medidos - ✅ Load testing semanal rodando - ✅ P95 latency < 500ms

Após 3 meses: - ✅ Zero N+1 queries - ✅ Cache hit rate > 80% - ✅ Custos AWS otimizados (-20%)

Após 6 meses: - ✅ P99 latency < 1s consistentemente - ✅ Capacity planning baseado em dados - ✅ User satisfaction com performance (+30%)

Referências