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á endocs/capacidad.mddel 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.
Los umbrales
Sección titulada «Los umbrales»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 |
Carga sintética
Sección titulada «Carga sintética»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 …/messagesen 71 / 98 / 111 ms (p50 / p95 / p99).
Para reproducirla:
make carga # 1, 3 y 10 instancias (unos 10 minutos)make carga CARGA="--rapido" # un perfil cortomake 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.
Mediciones reales
Sección titulada «Mediciones reales»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.
La importación de historial
Sección titulada «La importación de historial»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ócompletadounos 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 historial por tandas
Sección titulada «Pedir historial por tandas»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_respuestaa 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.
Recomendaciones
Sección titulada «Recomendaciones»- 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_connectionsde Postgres. - Disco temporal: la multimedia en curso vive en
/tmp(untmpfsde 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).