Stack: Django/DRF · Proyecto: TicketFlow Estado: Publicada — réplicas, particionado y servicios sin estado Prerrequisito: Lección 54 — Resiliencia
Objetivos
- Aplicar las tres leyes del escalado: estado fuera (stateless), réplicas de lectura para el browse, y el cuello real (la BD de escritura) con su plan.
- Particionar lo que el índice no arregla: el shard por evento y el límite de la partición.
- Conocer la escalera completa (vertical → réplicas → caché → partición → servicios) con el costo de cada peldaño antes de subirlo.
1. La ley cero: sin estado
El servicio SIN ESTADO es el prerrequisito de todo escalado horizontal: cualquier réplica puede servir cualquier request — el estado (sesión, datos, ficheros) vive FUERA (la BD del 10, Redis del 12, el bucket del 42). La auditoría de TicketFlow: (1) la sesión del 18 ya es token (sin session-store en memoria: las réplicas no comparten memoria); (2) los ficheros del export (23) ya viven en el bucket; (3) el local-memory cache de dev NO va a prod (cada réplica tendría su caché divergente: la 38); (4) el estado del proceso (el breaker del 54 en memoria) es aceptable (por réplica, divergencia tolerada) — pero el lock de corrida (31) ya vive en Postgres/Redis. La prueba de la ley cero: mata una réplica EN MEDIO de un checkout con otra réplica viva: el usuario no nota (la sesión del token sigue) — el load balancer (43) reparte y el estado es externo.
La trampa del estado escondido: el lru_cache de Python en un módulo (la config del 27 cacheada por proceso: las réplicas divergen hasta el restart — tolerable si el TTL del despliegue lo acota, un bug si no), los globals mutables (el contador en memoria del 12: por réplica, NO global), y el file-system compartido asumido (el /tmp de la réplica no es el de la otra).
2. Réplicas de lectura: el browse escala, la escritura no
Postgres con réplicas de lectura (42: el gestionado lo regala): las lecturas (el browse del 36: el 70% del tráfico) van a réplicas, las escrituras al primario. La regla de Django:
DATABASE_ROUTERS = ["core.routers.ReadWriteRouter"]
class ReadWriteRouter:
def db_for_read(self, model, **hints):
return "replica" if not hints.get("for_write") else "default"
def db_for_write(self, model, **hints):
return "default"El problema que la réplica introduce (el precio del peldaño): el lag de replicación — el comprador crea la reserva (primario) y el listado de "mis reservas" (réplica) NO la ve durante los ~50-500 ms del lag: el "acabo de comprar y no aparece" del ticket de soporte. La cura: el read-your-writes — las operaciones POST-log-in del mismo usuario van al primario (el hint del router por request: la vista marca for_write o el cookie del checkout fuerza el primario 10 s). La regla de asignación: la réplica para lo público-anónimo (browse, catálogo), el primario para lo personal-escrito y lo que el usuario ACABA de escribir. El número del 36: el browse (290 ms p95) baja a ~120 ms con réplicas dedicadas y el primario respira — pero el cuello del checkout (el lock del asiento) es de ESCRITURA: las réplicas no lo tocan (la lección del 36 que aquí se cierra).
3. El cuello de escritura: el plan antes del peldaño
La escritura escala peor: el primario es UNO (el multi-primario es el peldaño caro y peligroso del §5). El plan del cuello de escritura de TicketFlow (el lock del asiento): (1) reducir el trabajo por escritura: el bulk UPDATE del 36 (medido: −40%), el índice mínimo en la tabla caliente (09: cada índice es una escritura extra); (2) encolar lo no-crítico: el email/analytics ya están en cola (29) — el primario solo hace el negocio; (3) acortar los locks: la TX corta (32: la pasarela fuera de la TX), el skip_locked (10); (4) particionar (§4) si la contención es por el MISMO recurso; (5) el escalado vertical (más CPU/RAM al primario: el peldaño barato hasta su límite — el siguiente es el shard). La métrica que decide: pg_stat_activity en el pico (36) + el lock-wait del primario: el plan se activa por números, no por miedo.
4. La partición: cuando la contención es por recurso
El caso del 36: la arena con 20k asientos y 500 compradores simultáneos: el lock serializa los compradores del MISMO evento mientras los de OTRO evento esperan detrás (la contención por recurso: el nodo del plan). La partición (por evento en el gateway/lógica, por rango/hash en la BD):
-- partición por evento (lista) o por rango de fecha: el caso del curso
CREATE TABLE reservations (...) PARTITION BY LIST (event_id);
CREATE TABLE reservations_event_01H... PARTITION OF reservations FOR VALUES IN ('01H...');
-- cada partición: su índice (09), su vacuum, su lock-wait independienteEl beneficio: la contención del evento A no toca al B (el lock-wait se reparte por partición); el costo: las queries SIN el filtro de partición escanean TODAS (el plan del 09 exige el WHERE event_id — el contrato del particionado: toda query nombra la partición), y las particiones se crean/mantienen (el job del 31 crea la del próximo evento). La regla del peldaño: particiona cuando la contención es POR RECURSO y el índice ya no la arregla — el índice primero (09), la partición después: la partición prematura es el sobre-diseño del 51.
5. La escalera completa: peldaño, costo y el siguiente
La escalera del backend (de abajo arriba, cada peldaño con su costo):
| Peldaño | Qué compra | Costo | Trigger |
|---|---|---|---|
| Vertical (más CPU/RAM) | Todo un poco | Dinero, límite físico | el primero: siempre |
| Réplicas de lectura | El browse (lecturas) | Lag de replicación (§2) | lecturas > 70% del tráfico |
| Caché (38) | Las lecturas calientes | Invalidación | el listado dominate el p95 |
| Colas (29) | Lo no-crítico fuera del request | Eventualidad | el email en el p95 |
| Partición (§4) | La contención por recurso | Contrato de queries | lock-wait por recurso |
| Sharding (multi-primario) | La escritura global | La complejidad ×10 | el primario al límite REAL |
| Servicios (53) | Equipos/ritmos divergentes | La red y la saga | Conway |
El anti-patrón del escalador ansioso: subir peldaños sin trigger (el sharding de la app de 50k ventas: el 51 lo marca). La métrica del peldaño: cada uno se activa por SU número (el lock-wait, el lag, el p95 del dash del 46) y se re-mide tras subirlo (36): la escalera se sube midiendo, no adivinando.
Autoevaluación
- ¿Qué tres formas de estado escondido matan la ley cero y cuál es la prueba (¿la réplica muerta a mitad de checkout)?
- ¿Qué compra la réplica de lectura, qué problema introduce (¿el lag?) y cuál es la cura del read-your-writes?
- Enumera 4 reducciones del cuello de escritura ANTES de particionar/shardear. ¿Qué métrica decide?
- ¿Cuándo la partición y qué contrato impone (¿el WHERE del plan)? ¿Por qué el índice primero?
- Recorre la escalera: ¿qué compra, cuesta y dispara cada peldaño? ¿Cuál es el anti-patrón del escalador ansioso?
Continúa con los ejercicios. Las solutions.md solo tras intentarlo.