El rendimiento de los bordes. Sin solutions.md hasta entregar.
Ejercicio 1 — La aritmética del pool
- Calcula tu presupuesto de conexiones: gunicorn (workers × threads × réplicas), workers Celery (concurrency), beat, commands. Escríbelo como tabla con el total y compáralo con
SHOW max_connections. - Configura CONN_MAX_AGE + pool de psycopg 3 (min 2 / max 10) y mide con
pg_stat_activityantes y después de la meseta k6 (36): ¿cuántas conexiones enidle? ¿Cuántas enactive? - El desastre controlado: en un entorno de test, baja
max_connectionsa 20 y lanza la meseta de 200 VUs. ¿Qué error aparece y dónde (app, worker, ambos)? Documenta el síntoma EXACTO para el runbook del 47.
Ejercicio 2 — El cursor
- Implementa la paginación por cursor en el listado de eventos (encode/decode del uuid,
limitcon techo 100) y el test de contrato: 3 páginas consecutivas sin solapamiento,next_cursor: nullal final, orden estable. - La medición honesta: con seed de 2M filas, mide (EXPLAIN ANALYZE, 09) la página 1 vs la página 1000 con offset y con cursor: pega los dos planes. ¿Cuánto cuesta el offset profundo?
- La ventana móvil: inserta 5 eventos entre la lectura de página 2 y la petición de página 3 con offset: ¿se repite o se salta algo? Repite con cursor: ¿qué pasa? Pega la evidencia de ambos.
- El
totaleliminado: reescribe el front-contrato (35) sintotaly añade el test de que la respuesta YA NO lo trae. ¿Qué endpoint SÍ conserva total y por qué (¿el admin con filtro? ¿cacheado, 38?)?
Ejercicio 3 — Los límites del cliente
- Añade el techo de
limit(min(limit, 100)) y el test:?limit=100000devuelve 100 items SIN error (¿o 400? decide y justifica: ¿silencioso o explícito, 13?). - El ataque de ancho: con k6 (36), lanza 50 VUs pidiendo
limit=100continuo y compara p95/ancho de la BD contralimit=20. ¿Qué techo protege más? Escribe la decisión en la docs de la API.
Ejercicio 4 — La compresión
- Mide el listado de 400 eventos: bytes sin comprimir, con gzip 4, con gzip 9, y el tiempo de CPU de cada nivel (el cProfile del 37 o timeit). ¿Dónde está la zona del trade-off?
- Configura gzip en nginx (o gunicorn si no hay proxy):
gzip_comp_level 4,gzip_min_length 1024,gzip_types application/json. Test con curl--compressed: el headerContent-Encodingy los bytes reales. - El caché y el Vary: con el ETag del 38 activo, sirve una respuesta comprimida y otra sin comprimir al mismo recurso: ¿el caché las confunde sin
Vary: Accept-Encoding? Demuéstralo con el test del 304 (el cliente sin soporte gzip debe recibir SIN comprimir).
Ejercicio 5 — El informe de bordes
- Escribe
docs/perf/2027-09-bordes.md: la aritmética del pool, el plan del cursor (antes/después de la página 1000), el techo de limit, y la decisión de compresión con números. - La regla del proyecto para TODO listado nuevo: cursor por defecto, techo de limit, sin COUNT, gzip en el proxy. ¿Dónde vives esa regla (¿un mixin/base view? ¿un test de contrato parametrizado sobre todos los listados?)? Implémentala.
Entrega
Pega la tabla de conexiones, los planes EXPLAIN de página 1000, la evidencia de la ventana móvil y el informe de bordes. Después: Lección 40 — Docker (módulo 9).