- Entorno evaluado: producción
homedir.opensourcesantiago.io. - Versión desplegada:
3.348.0(commit0f37f1b). - Fecha/hora base del levantamiento:
2026-02-17 15:20 UTC.
- CPU:
4 vCPU - RAM:
15.99 GBtotales (~1.03 GBusados al momento del snapshot) - Swap:
0 - Disco
/:193 GBtotal,21 GBusados (11%) - Load average:
0.03 / 0.02 / 0.00
- Imagen:
quay.io/sergio_canales_e/homedir:3.348.0 - Límite de memoria/CPU en contenedor: sin límite explícito (
mem_limit=0,nano_cpus=0) - Política de reinicio:
always - Límite de procesos:
2048 - Memoria estable (idle reciente):
~273-316 MB - Memoria luego de prueba de carga:
~520 MB(memory.peak ~522 MB) - Proceso JVM observado:
VmRSS ~527 MB(post stress)Threadsde~44a~118durante carga
Muestra sobre últimas 1200 líneas de nginx/access.log:
653requests a/ws/global-notifications(handshake websocket,101)202requests no-websocket- Top rutas no-websocket:
/(38)/about(15)/comunidad(12)/api/community/content(10)
- Códigos HTTP:
200: 159301/302/303: 35404: 6400: 2101: 653(websocket upgrade)
GET /-> p505.8ms, p9510.2msGET /comunidad-> p506.7ms, p959.1msGET /api/community/content?view=featured&limit=10-> p50214.3ms, p95221.3ms
Prueba: 12000 requests, concurrencia 120:
- Resultado:
12000 OK,0 fail - Tiempo total:
8.534s - CPU consumida por cgroup del contenedor:
16.416 CPU-seconds - Consumo promedio durante prueba:
1.924 cores(sobre 4 vCPU =48.1%)
- Existe rate limit API global por IP:
rate.limit.api.limit = 120requests/min (ventana 60s).- Bajo prueba intensa a
/api/community/contentaparecieron429 Too Many Requests.
- No hay límites explícitos de CPU/MEM del contenedor.
/api/community/contentes significativamente más costoso que páginas públicas.
Supuestos para estimación:
- 1 websocket por usuario conectado.
- Mezcla de tráfico: navegación pública + consumo de APIs (
community/events/cfp). - Escenarios por tasa promedio de requests por usuario:
- Conservador:
0.05 rps/user - Medio:
0.10 rps/user - Alto:
0.20 rps/user
- Conservador:
| Escenario | RPS total (400 users) | CPU estimada | RAM estimada | Riesgo principal |
|---|---|---|---|---|
| Conservador | 20 rps | 15-25% de 4 vCPU | 0.7-0.9 GB | Bajo |
| Medio | 40 rps | 25-45% de 4 vCPU | 0.9-1.2 GB | Medio (API bursts) |
| Alto | 80 rps | 45-75% de 4 vCPU | 1.2-1.8 GB | Alto (429/latencia API) |
Notas:
- La landing tiene margen alto (>= 1k rps internos en prueba puntual), pero no representa todo el mix real.
- El límite de
120 req/minpor IP puede bloquear tráfico legítimo si muchos usuarios quedan detrás de la misma IP efectiva para backend.
- Ajustar rate limiting por endpoint y por tipo de cliente:
- Mantener agresivo para auth.
- Subir bucket API público o separar bucket para
/api/community/content.
- Fijar límites de contenedor para estabilidad:
- Ejemplo inicial:
--memory=2g --cpus=3(o equivalente systemd/podman).
- Ejemplo inicial:
- Activar benchmark reproducible en staging (k6/oha/wrk) con escenario de 400 usuarios.
- Monitorear permanentemente:
- p95/p99 por endpoint
- tasa de
429 memory.current,memory.peak,cpu.stat- cola de persistencia (
writesOk/writesFail/depth) desde admin metrics.
Con el estado actual, el despliegue soporta holgadamente el tráfico observado hoy y debería sostener 400 usuarios concurrentes en escenario conservador/medio, pero requiere ajuste de rate limiting API y validación formal con prueba de carga realista para evitar rechazos 429 y degradación en picos.
- Se creó release formal:
v3.348.0. - Se ajustó el rate limiting para Community API:
- Nuevo bucket dedicado para
/api/community/content. - Nueva propiedad:
rate.limit.api.community-content.limit(default600 req/min).
- Nuevo bucket dedicado para
- Se mejoró la identificación de cliente detrás de proxy/CDN:
- Nuevo fallback a
CF-Connecting-IPcuandoX-Forwarded-Forno está presente.
- Nuevo fallback a
- Se optimizó
GET /api/community/contentpara concurrencia:view=new: calcula agregados de votos solo para la página solicitada.view=featured: calcula agregados solo para candidatos de ventana destacada (7 días por default).
- Se agregó cache corto para agregados de votos:
community.votes.aggregate-cache-ttl(defaultPT10S), para reducir consultas repetitivas en tráfico de lectura.
- Se agregó telemetría de rate limiting en admin metrics:
GET /private/admin/metrics/persistenceincluye estado/config/totales por bucket.
- Se habilitó hardening runtime en deploy script:
CONTAINER_MEMORY_LIMIT(default2g)CONTAINER_CPU_LIMIT(default3)CONTAINER_PIDS_LIMIT(default2048)
- Se agregó herramienta reproducible de carga:
tools/load-test/community_capacity_probe.py
Fecha de ejecución: 2026-02-17 (UTC).
Restricción validada: por ahora las pruebas de carga salen desde un único cliente hacia el VPS.
users=20,duration=45s:- error rate
0.00% /api/community/content?view=featured&limit=10: p951553.6ms
- error rate
users=40,duration=45s:- error rate
6.10% /api/community/content?view=featured&limit=10:429=138
- error rate
users=80,duration=45s:- error rate
20.98% /api/community/content?view=featured&limit=10:429=1199
- error rate
Conclusión de esta ruta:
- Desde un solo origen público se activa rate limiting por IP en Community API, por lo que no representa bien un patrón multiusuario real.
Target: http://127.0.0.1:8080 (sin Cloudflare), usando el mismo probe.
users=80,duration=45s, probe normal:- error rate
26.90% /api/community/content?view=featured&limit=10:429=2365,-1(timeout)=28
- error rate
users=80,duration=45s, IP emulada por request (XFF/CF-Connecting-IP random):- error rate
2.23% /api/community/content?view=featured&limit=10: p958008.2ms,-1(timeout)=71, sin429
- error rate
users=120,duration=45s, IP emulada por request:- error rate
12.01% /api/community/content?view=featured&limit=10: p958011.7ms,-1(timeout)=374
- error rate
Durante la ronda más exigente (users=120, IP emulada):
- CPU contenedor observada por
podman stats: ~47%a53%(sobre límite de3 CPUconfigurado). - Memoria contenedor: pico observado ~
1.40GB/2.147GB. - Estado host (
top) al terminar: CPU global ociosa (sin saturación de nodo).
Conclusión:
- El cuello de botella actual es el endpoint
/api/community/content?view=featuredbajo concurrencia alta. - No se observó saturación de máquina/host en esta ventana.
400 usuarios/hora equivale a ~6.67 usuarios/min, muy por debajo de las rondas de estrés ejecutadas.
Con base en los resultados:
- Capacidad para carga esperada actual: suficiente con margen.
- Riesgo principal en picos: latencia/timeouts de
community featured(no CPU/mem del host).
Acciones recomendadas para siguiente iteración:
- Materializar cache de agregados/votos (TTL corto) para reducir costo por request en
featured. - Añadir cache de respuesta para
featured(5-15s) invalidada por nuevos votos. - Separar completamente el ranking de
featureden un snapshot periódico (ej. cada 30-60s), servido desde memoria.
Implementación posterior en backend:
CommunityFeaturedSnapshotService:- snapshot de ranking
featureden memoria por filtro (all/internet/members); - refresh programado cada
30s(community.content.featured.snapshot-refresh-every); - control de antigüedad para refresh on-demand (
community.content.featured.snapshot-max-age=PT45S).
- snapshot de ranking
- Cache de respuesta para páginas de
featured:- TTL corto
PT10S(community.content.featured.response-cache-ttl); - sirve páginas desde memoria para reducir trabajo repetido de ranking.
- TTL corto
- Invalidación por voto:
- al votar (
PUT /api/community/content/{id}/vote) se limpia cache de respuesta y se gatilla refresh async del snapshot.
- al votar (
Resultado esperado:
- menos recalculo por request en
/api/community/content?view=featured; - mejor estabilidad de latencia en picos de lectura;
- consistencia rápida tras cambios de votos.