Ir al contenido

Capacidad

Hay dos fuentes de números, y conviene no mezclarlas:

  • La carga sintética (make carga): el motor real con el driver falso, sin WhatsApp. Mide la API, la base, el outbox, las entregas y los streams, con fallos inyectados. Está en docs/capacidad.md del repositorio.
  • Las mediciones reales (caso A88 y las pruebas de historial): el motor con números de WhatsApp de verdad, entre el 21 y el 23 de octubre de 2026, con hasta 5 instancias conectadas.

Son los que usan la carga y su prueba de regresión (TestMW12bRegresionDeCapacidad, en make integracion). Son unas tres veces lo medido a 10 instancias: una fuga o un atraso que no se recupera los pasa; el ruido de la máquina, no.

Qué Umbral
Memoria residente (RSS) del proceso hasta 400 MB
Goroutines hasta 150 + 40 por instancia en cada fase
Goroutines con la base congelada además, 128 por instancia (los pedidos que esperan la base)
Goroutines al terminar cada fase de vuelta bajo 150 + 40 por instancia
Lo pendiente al terminar cada fase (entregas y comandos) vuelve a 0 en menos de 3 minutos
Entrantes aceptados que no llegan al webhook 0
Envíos sin un estado final 0

Medida el 15/10/2026 con el motor 0.1.0 en una sola máquina (Intel Core i7-12700F, 32 GB; la base, MinIO, el motor y el generador juntos). El perfil, igual para 1, 3 y 10 instancias: reposo; la inicialización con 300 mensajes de historial y 3 imágenes por instancia; 45 s con 5 entrantes y 2 envíos de texto por segundo por instancia; y los fallos, en orden, con el tráfico corriendo: reconexiones repetidas, un receptor de webhooks lento (300 ms) y otro caído (503), la red de WhatsApp caída, Postgres congelado 15 s y un SIGKILL del motor.

Instancias Entrantes aceptados Perdidos Envíos enviados RSS máx. Goroutines máx. Con la base congelada Vaciado máx.
1 575 0 275 109 MB 81 (umbral 190) 126 (umbral 318) 20,9 s
3 1723 0 827 119 MB 148 (umbral 270) 322 (umbral 654) 26,5 s
10 5742 0 2757 140 MB 154 (umbral 550) 919 (umbral 1830) 27,5 s

Las tres corridas quedaron dentro de los umbrales. Ninguna entrega llegó dos veces y todo envío terminó con un estado final. Lo que se lee en las tablas:

  • La memoria y las goroutines quedan planas y vuelven a su base después de cada fallo.
  • Un receptor lento o caído no frena la ingesta: las entregas se acumulan en el outbox (cientos a 10 instancias) y se vacían en menos de 30 s cuando vuelve.
  • La base congelada frena todo lo que la necesita: las latencias llegan a los 15 s de la congelación, pero nada confirmado se pierde y lo pendiente vuelve a cero.
  • Después de un SIGKILL, el motor vuelve a responder en menos de un segundo y retoma lo que estaba en la base.
  • El pool de 12 conexiones alcanza a 10 instancias, con esperas: unas 29 000 esperas en 45 s de carga sostenida, con POST …/messages en 71 / 98 / 111 ms (p50 / p95 / p99).

Para reproducirla:

Ventana de terminal
make carga # 1, 3 y 10 instancias (unos 10 minutos)
make carga CARGA="--rapido" # un perfil corto
make carga CARGA="--instancias 3 --sin-fallos"

Levanta un Postgres y un MinIO efímeros, deja el reporte en .carga/reporte/ y borra lo que levantó. Sale con error si una corrida pasa los umbrales.

Con números de WhatsApp reales y hasta 5 instancias conectadas. Todas quedaron dentro de los umbrales (RSS menor que 400 MB; goroutines menos de 150 + 40 × 5 = 350) y con 0 esperas del pool.

Medición Fecha RSS Heap Goroutines Pool
Arranque en frío, 4 instancias, a los ~4 min 22/10 95,7 MB 27–31 MB 31 6 abiertas
Reposo de 10 min, 5 instancias, ~14 h de motor 21/10 124–134 MB (baja, no crece) 29–49 MB 65–69 5–6 abiertas, 2 en uso
Tráfico moderado (30 textos en 60 s) 21/10 134 → 136 MB 32–51 MB 66–68 6 abiertas
Pico al vincular con historial (722 mensajes importados) 22/10 133 MB (de 112) 50 MB 57 8 abiertas, 6 en uso
Pico al vincular con historial (5000 importados, el tope) 23/10 167 MB (de 101) 80 MB 51 7 abiertas, 5 en uso

Al terminar el tráfico, lo pendiente (comandos y eventos sin entregar) volvió a 0 en menos de un minuto. Después de cada pico, el proceso volvió a reposo (119 y 122 MB) sin seguir creciendo.

El historial es lo que más memoria pide. Dos vinculaciones con la importación encendida (caso A88):

  • 22/10: 1846 mensajes vistos, 722 importados en 104 chats, 735 duplicados, 114 fuera de la fecha y 275 de tipos que el motor no importa. Pico de 133 MB.
  • 23/10: 31 744 mensajes vistos, 5000 importados en 60 chats (el tope de historial.mensajes_por_trabajo), 14 687 omitidos por esa cuota, 2562 duplicados, 47 fuera de la fecha y 3800 de tipos que no importa. Los lotes llegaron en un minuto y el trabajo cerró completado unos 12 minutos después. Pico de 167 MB.

La cuota por trabajo es lo que acota la importación: con 5000 (el valor de fábrica) el pico fue de 167 MB. Si la subes, mide antes.

Pedir los mensajes anteriores de un chat va de a 50 por pedido (el máximo de cantidad; 100 es un 422). En la prueba de límites del 21/10, sobre un mismo chat 1:1:

  • 195 tandas atendidas, con 9604 mensajes vistos y 9577 importados, sin repetir ni saltar mensajes; alcanzó unos 3 meses hacia atrás.
  • Mediana de 3 s por tanda; 2546 s en total (unos 42 min), con algunas demoras de 79 a 476 s. La tanda 196 quedó sin_respuesta a los 605 s: el teléfono dejó de contestar y todavía quedaba historia.
  • La base creció 26 MB (de 224 a 250 MB). Sin bajar medios del historial.

Los límites de WhatsApp de este pedido están en Limitaciones de WhatsApp y el uso, en Historial.

  • Memoria del contenedor: el compose de ejemplo pone mem_limit: 1g. Con lo medido (picos de 167 MB con 5 instancias y una importación de 5000 mensajes) sobra margen. Pon Ajustes → Operación → Memoria del proceso algo por debajo del límite del contenedor.
  • Conexiones a la base: el pool de 12 alcanza hasta unas 10 instancias con tráfico, con esperas. Desde ahí, sube Ajustes → Operación → Conexiones a la base. Dos conexiones las retiene el proceso todo el tiempo; cada una cuenta contra el max_connections de Postgres.
  • Disco temporal: la multimedia en curso vive en /tmp (un tmpfs de 2 GB en el compose). Ajusta Espacio libre mínimo y Conversiones simultáneas en Ajustes → Multimedia según el tamaño de tus archivos.
  • Espacio de la base: lo que más crece es el ledger, las entregas, los mensajes importados y el store de whatsmeow. La retención de lo entregado (30 días por omisión) acota lo primero; un destino de webhooks caído hace crecer el outbox hasta que se entrega o vence.
  • Clientes de la API con plazo: con Postgres sin responder, los pedidos esperan una conexión hasta que su cliente corta, y las goroutines suben mientras dure. El motor no limita cuántos pedidos admite: pon plazo a tus pedidos y espera entre reintentos.
  • El ritmo de envío lo fija WhatsApp más que el motor: los Ajustes de fábrica son 1 mensaje por segundo por instancia con una ráfaga de 2 (Cuotas y límites).