Summary
Every (re)connect attempt on an instance whose WhatsApp session was logged out (pairing removed on the phone → whatsmeow LoggedOut) leaks a Postgres connection pool: a new whatsmeow client + sqlstore is created per attempt, the login fails with LoggedOut (expected), and the abandoned sql.DB pool is never Close()d. The idle connections stay parked in Postgres forever.
With any periodic reconnect source, this grows unbounded until the whole Postgres cluster hits max_connections and refuses every client. Aggravated by evolution-go connecting as the postgres superuser by default, which also exhausts superuser_reserved_connections — locking the DBA out during the incident.
We hit this twice in production in two days, through two different trigger paths:
Variant A — external POST /instance/connect (day 1)
Our supervisor retried /instance/connect every 2 min on a logged-out instance.
- Postgres log: 742 ×
FATAL: sorry, too many clients already, origin user=postgres, db=evogo_users
- Connection count of the evolution-go process (
nsenter … ss -tn | grep :5432): 167 (healthy baseline for the same deployment: ~15–20)
- Exact correlation: 88 authenticated
POST /instance/connect (HTTP 200) since container start ≈ the accumulated leaked connections (+1 per attempt). 401 attempts (invalid token) do not leak — the leak is in the authenticated path that instantiates the client/sqlstore.
docker restart evolution-go instantly dropped the count 167 → 3 and the cluster recovered.
Variant B — internal whatsmeow auto-reconnect (day 2, no API calls at all)
After we stopped retrying via API, a logged-out instance kept cycling through whatsmeow's internal auto-reconnect:
- 25 ×
LoggedOut connection events in 6h for one instance, only 2 authenticated /instance/connect in the same window (so not API-triggered)
- 121 idle connections accumulated in
db=evogo_auth (session store), ~7/hour, all state=idle, oldest from the moment the cycling started
- Same fix: restart frees them instantly; the leak resumes while the logged-out instance keeps retrying.
Suspected root cause
On the connect path, a new whatsmeow client + sqlstore container (its own database/sql pool) is created per attempt. When login fails with LoggedOut, the client is discarded but the store/pool is not closed. An abandoned Go sql.DB keeps its idle connections open indefinitely.
Suggested fix
- Close the sqlstore/
sql.DB on the connect error path (or reuse the instance's existing client/pool instead of creating a new one per attempt);
- Treat
LoggedOut as terminal in the reconnect loop: stop auto-retrying a session that requires re-pairing (surface "re-pair required" instead);
- Independently: document/support running with a non-superuser DB role — with the default superuser connection, any leak escalates to a full-cluster outage including the reserved slots.
Workarounds we use meanwhile
- Supervisor treats
LoggedOut as terminal and stops calling /instance/connect;
docker restart evolution-go when the count grows;
- Planning a dedicated DB role with
CONNECTION LIMIT as a blast-radius guard.
Happy to provide more logs/details if useful. 🇧🇷 Podemos detalhar em PT-BR também, se preferirem.
Summary
Every (re)connect attempt on an instance whose WhatsApp session was logged out (pairing removed on the phone → whatsmeow
LoggedOut) leaks a Postgres connection pool: a new whatsmeow client + sqlstore is created per attempt, the login fails withLoggedOut(expected), and the abandonedsql.DBpool is neverClose()d. The idle connections stay parked in Postgres forever.With any periodic reconnect source, this grows unbounded until the whole Postgres cluster hits
max_connectionsand refuses every client. Aggravated by evolution-go connecting as thepostgressuperuser by default, which also exhaustssuperuser_reserved_connections— locking the DBA out during the incident.We hit this twice in production in two days, through two different trigger paths:
Variant A — external
POST /instance/connect(day 1)Our supervisor retried
/instance/connectevery 2 min on a logged-out instance.FATAL: sorry, too many clients already, originuser=postgres, db=evogo_usersnsenter … ss -tn | grep :5432): 167 (healthy baseline for the same deployment: ~15–20)POST /instance/connect(HTTP 200) since container start ≈ the accumulated leaked connections (+1 per attempt). 401 attempts (invalid token) do not leak — the leak is in the authenticated path that instantiates the client/sqlstore.docker restart evolution-goinstantly dropped the count 167 → 3 and the cluster recovered.Variant B — internal whatsmeow auto-reconnect (day 2, no API calls at all)
After we stopped retrying via API, a logged-out instance kept cycling through whatsmeow's internal auto-reconnect:
LoggedOutconnection events in 6h for one instance, only 2 authenticated/instance/connectin the same window (so not API-triggered)db=evogo_auth(session store), ~7/hour, allstate=idle, oldest from the moment the cycling startedSuspected root cause
On the connect path, a new whatsmeow client + sqlstore container (its own
database/sqlpool) is created per attempt. When login fails withLoggedOut, the client is discarded but the store/pool is not closed. An abandoned Gosql.DBkeeps its idle connections open indefinitely.Suggested fix
sql.DBon the connect error path (or reuse the instance's existing client/pool instead of creating a new one per attempt);LoggedOutas terminal in the reconnect loop: stop auto-retrying a session that requires re-pairing (surface "re-pair required" instead);Workarounds we use meanwhile
LoggedOutas terminal and stops calling/instance/connect;docker restart evolution-gowhen the count grows;CONNECTION LIMITas a blast-radius guard.Happy to provide more logs/details if useful. 🇧🇷 Podemos detalhar em PT-BR também, se preferirem.