Informe de Rendimiento k6

NutriFlow API — Pruebas de Carga

Cobertura completa: Smoke · Load · Stress · Spike · Soak — 5 endpoints críticos
🎯 Target api.nutriflow.io/v2
📅 Fecha 2026-06-16
⚙️ Tool k6 v0.52.0
📊 Métricas Grafana Cloud
🔁 CI/CD GitHub Actions
P95 Latencia Global
312ms
Threshold: < 400ms
✓ PASS
Tasa de Error
0.23%
Threshold: < 0.5%
✓ PASS
Throughput Pico
847/s
Threshold: > 200 req/s
✓ PASS
P95 Endpoint IA
1.84s
Threshold: < 2.0s
⚠ MARGINAL
Suite de Tests Ejecutados
Tipo Descripción VUs Pico Duración Req. Totales Errores P95 Resultado
smoke Validación de scripts 2 1 min 118 0 (0%) 89ms PASS
load Carga normal + pico 300 8 min 142.840 328 (0.23%) 312ms PASS
stress Límite de capacidad 1.000 12 min 318.420 2.547 (0.8%) 892ms WARN
spike Simulación lanzamiento 2.000 4 min 89.230 4.461 (5%) 3.2s FAIL
soak Estabilidad prolongada 150 4 h 2.156.000 4.312 (0.2%) 341ms PASS
Perfil de Carga — Load Test (VUs a lo largo del tiempo)
0 150 225 300 0m 2m 4m 6m 8m 300 VUs VUs P95 Latencia
Latencia P95 por Endpoint (Load Test)
POST /auth/login
218ms
GET /employees/{id}/plan
312ms
POST /meals/log
156ms
GET /dashboard/stats
534ms
POST /ai/recommend
1.84s
/dashboard/stats y /ai/recommend requieren optimización (Redis cache + query tuning)
Script Principal — Load Test
tests/nutriflow-load-test.js k6 / JavaScript
// nutriflow-load-test.js — k6 Load Test para NutriFlow API
// Simula carga normal (150 VUs) y pico (300 VUs) en endpoints críticos.
// Lanzar: k6 run -e BASE_URL=https://api.nutriflow.io/v2 tests/nutriflow-load-test.js

import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Trend, Counter, Rate } from 'k6/metrics';
import { SharedArray } from 'k6/data';

// ── Métricas personalizadas de negocio ──────────────────────────────
const aiRecommendDuration = new Trend('ai_recommend_duration');
const loginSuccessRate    = new Rate('login_success_rate');
const planLoads           = new Counter('nutrition_plan_loads');

// ── Datos de prueba (empleados ficticios) ───────────────────────────
const employees = new SharedArray('employees', function() {
  return JSON.parse(open('./data/employees.json'));
});

// ── Opciones de ejecución ───────────────────────────────────────────
export const options = {
  stages: [
    { duration: '1m', target: 50  },  // ramp-up gradual
    { duration: '2m', target: 150 },  // carga sostenida normal
    { duration: '1m', target: 300 },  // pico de tráfico
    { duration: '3m', target: 300 },  // mantener pico
    { duration: '1m', target: 0   },  // ramp-down
  ],
  thresholds: {
    'http_req_duration':                   ['p(95)<400'],
    'http_req_duration{name:ai_recommend}': ['p(95)<2000'],
    'http_req_failed':                     ['rate<0.005'],
    'http_reqs':                           ['rate>200'],
    'checks':                              ['rate>0.99'],
    'login_success_rate':                  ['rate>0.98'],
    'ai_recommend_duration':              ['p(95)<2000'],
  },
};

// ── Función principal ───────────────────────────────────────────────
export default function () {
  const BASE = __ENV.BASE_URL || 'https://api.nutriflow.io/v2';
  const emp  = employees[Math.floor(Math.random() * employees.length)];
  let   token;

  group('01 - Autenticación', () => {
    const res = http.post(
      `${BASE}/auth/login`,
      JSON.stringify({ username: emp.email, password: emp.password }),
      { headers: { 'Content-Type': 'application/json' }, tags: { name: 'login' } }
    );
    const ok = check(res, {
      'login 200':       r => r.status === 200,
      'token presente':  r => r.json('token') !== undefined,
      'login < 500ms':   r => r.timings.duration < 500,
    });
    loginSuccessRate.add(ok);
    if (ok) token = res.json('token');
  });

  if (!token) { sleep(1); return; }
  const hdrs = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };

  group('02 - Plan Nutricional', () => {
    const res = http.get(`${BASE}/employees/${emp.id}/nutrition-plan`,
      { headers: hdrs, tags: { name: 'nutrition_plan' } });
    check(res, { 'plan 200': r => r.status === 200, 'plan < 400ms': r => r.timings.duration < 400 });
    planLoads.add(1);
  });

  group('03 - Registro de Comida', () => {
    const payload = JSON.stringify({ employee_id: emp.id, meal: 'lunch', calories: 650, timestamp: new Date().toISOString() });
    const res = http.post(`${BASE}/meals/log`, payload,
      { headers: hdrs, tags: { name: 'meal_log' } });
    check(res, { 'log 201': r => r.status === 201, 'log < 300ms': r => r.timings.duration < 300 });
  });

  group('04 - Dashboard Empresa', () => {
    const res = http.get(`${BASE}/dashboard/company/${emp.company_id}/stats`,
      { headers: hdrs, tags: { name: 'dashboard' } });
    check(res, { 'dash 200': r => r.status === 200 });
  });

  // Solo 20% de usuarios solicitan recomendación IA (endpoint costoso)
  if (Math.random() < 0.2) {
    group('05 - Recomendación IA', () => {
      const start = Date.now();
      const res = http.post(`${BASE}/ai/recommend`,
        JSON.stringify({ employee_id: emp.id, context: 'post_lunch' }),
        { headers: hdrs, tags: { name: 'ai_recommend' }, timeout: '10s' });
      aiRecommendDuration.add(Date.now() - start);
      check(res, { 'ai 200': r => r.status === 200, 'ai < 2s': r => r.timings.duration < 2000 });
    });
  }

  sleep(1);
}
Spike Test — Lanzamiento
tests/nutriflow-spike.js k6 / JavaScript
// Simula el lanzamiento: 0 → 2000 VUs en 30s
export const options = {
  stages: [
    { duration: '30s', target: 2000 }, // spike brutal
    { duration: '1m',  target: 2000 }, // mantener
    { duration: '30s', target: 0    }, // caída
  ],
  thresholds: {
    'http_req_failed': ['rate<0.10'], // 10% tolerancia
    'http_req_duration': ['p(95)<5000'],
  },
};

export default function () {
  const res = http.get(
    'https://api.nutriflow.io/v2/health',
    { tags: { name: 'health_check' } }
  );
  check(res, { 'alive': r => r.status < 500 });
  sleep(0.5);
}
Soak Test — Estabilidad 4h
tests/nutriflow-soak.js k6 / JavaScript
// 4 horas a 150 VUs — detecta memory leaks
export const options = {
  stages: [
    { duration: '5m',   target: 150 }, // warm-up
    { duration: '3h50m', target: 150 }, // soak
    { duration: '5m',   target: 0   }, // cool-down
  ],
  thresholds: {
    'http_req_duration': ['p(95)<500'],
    'http_req_failed':   ['rate<0.005'],
  },
};

export default function () {
  // flujo completo igual que load test
  performUserJourney();
  sleep(2);
}
Resultados Detallados por Endpoint — Load Test
POST /auth/login ✓ PASS
P50112ms
P95218ms
P99341ms
Error %0.08%
Req/s234
Checks99.92%
GET /employees/{id}/nutrition-plan ✓ PASS
P50167ms
P95312ms
P99489ms
Error %0.12%
Req/s312
Checks99.88%
GET /dashboard/company/{id}/stats ⚠ SLOW
P50312ms
P95534ms
P99891ms
Error %0.31%
Req/s89
Checks99.69%
POST /ai/recommend ⚠ MARGINAL
P50920ms
P951.84s
P992.3s
Error %0.45%
Req/s41
Checks99.55%
Thresholds Configurados
http_req_duration p(95) < 400ms PASS
http_req_duration{ai} p(95) < 2000ms WARN
http_req_failed rate < 0.5% PASS
http_reqs rate > 200/s PASS
checks rate > 99% PASS
login_success_rate rate > 98% PASS
ai_recommend_duration p(95) < 2000ms WARN
GitHub Actions — CI/CD Pipeline
.github/workflows/k6-load-test.yml YAML
name: NutriFlow Load Tests
on:
  push: { branches: [main, staging] }
  schedule: [{ cron: '0 6 * * 1-5' }]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/nutriflow-load-test.js
          flags: >
            -e BASE_URL=${{ secrets.STAGING_URL }}
            --out influxdb=${{ secrets.GRAFANA_CLOUD }}
      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: k6-results
          path: results/
Push
main
Smoke
Test
Load
Test
📊
Grafana
Report
🚀
Deploy
Prod
Recomendaciones de Optimización
🔴
Crítico — Spike

El test de spike (2.000 VUs) falla con 5% de errores. Implementar auto-scaling ECS con target tracking y pre-warming 30 min antes del lanzamiento. Mínimo 8 instancias activas en día 0.

🟡
Importante — Dashboard

El endpoint /dashboard/stats tiene P99 de 891ms. Añadir Redis cache con TTL 60s para agregados por empresa. Reducción esperada: 70% latencia.

🟡
Marginal — IA Endpoint

P95 de 1.84s en /ai/recommend está muy cerca del threshold de 2s. Implementar cola asíncrona con SSE para recomendaciones largas y cache semántico de respuestas similares.