Módulo 12 · Nivel empresarial (integración)

Lección 55 — Escalado

Réplicas de lectura, particionado y servicios sin estado.

Publicada
En esta lección
  1. Objetivos
  2. 1. La ley cero: sin estado
  3. 2. Réplicas de lectura: el browse escala, la escritura no
  4. 3. El cuello de escritura: el plan antes del peldaño
  5. 4. La partición: cuando la contención es por recurso
  6. 5. La escalera completa: peldaño, costo y el siguiente
  7. Autoevaluación

Stack: Django/DRF · Proyecto: TicketFlow Estado: Publicada — réplicas, particionado y servicios sin estado Prerrequisito: Lección 54 — Resiliencia


Objetivos

  1. 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.
  2. Particionar lo que el índice no arregla: el shard por evento y el límite de la partición.
  3. 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:

python
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):

sql
-- 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 independiente

El 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ñoQué compraCostoTrigger
Vertical (más CPU/RAM)Todo un pocoDinero, límite físicoel primero: siempre
Réplicas de lecturaEl browse (lecturas)Lag de replicación (§2)lecturas > 70% del tráfico
Caché (38)Las lecturas calientesInvalidaciónel listado dominate el p95
Colas (29)Lo no-crítico fuera del requestEventualidadel email en el p95
Partición (§4)La contención por recursoContrato de querieslock-wait por recurso
Sharding (multi-primario)La escritura globalLa complejidad ×10el primario al límite REAL
Servicios (53)Equipos/ritmos divergentesLa red y la sagaConway

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

  1. ¿Qué tres formas de estado escondido matan la ley cero y cuál es la prueba (¿la réplica muerta a mitad de checkout)?
  2. ¿Qué compra la réplica de lectura, qué problema introduce (¿el lag?) y cuál es la cura del read-your-writes?
  3. Enumera 4 reducciones del cuello de escritura ANTES de particionar/shardear. ¿Qué métrica decide?
  4. ¿Cuándo la partición y qué contrato impone (¿el WHERE del plan)? ¿Por qué el índice primero?
  5. 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.