Supabase 'max clients reached in session mode' en Vercel, y el arreglo que lo empeoró
El error es (EMAXCONNSESSION) max clients reached in session mode - max clients are limited to pool_size: 15. Nuestra API empezó a devolverlo mientras la web de marketing seguía perfectamente, porque la web de marketing no toca Postgres. Por eso mismo este tipo de fallo pasa desapercibido. Las partes que parecen bien son las que no necesitan la base de datos.
Aquí tienes qué significa el error, los dos errores que cometimos al arreglarlo y la cadena de conexión que de verdad funciona desde Vercel.
Dos poolers, dos puertos
Por qué el serverless agota el modo sesión
Cada instancia de función en caliente tiene su propio pool pequeño. El nuestro tenía tres conexiones. Un pool en modo sesión no devuelve nunca las conexiones mientras la instancia sigue en caliente. Cinco instancias en caliente son quince conexiones, que es el límite. La sexta petición, venga de donde venga, falla.
Un daemon de flota que consultaba tres endpoints cada pocos segundos mantenía las instancias en caliente a todas horas. No hay nada raro en eso. Es pura aritmética, y el modo sesión pierde.
Error uno: arreglar primero la capa equivocada
El arreglo de urgencia fue max: 1 por instancia y un idle timeout de veinte segundos, todavía en modo sesión. Funcionó. Las instancias que se quedaban quietas devolvían su hueco, y la API se recuperó en minutos. Después pasamos la cadena de conexión al modo transacción, que era lo correcto. Dejamos max: 1, que no lo era.
Error dos: un pool de una conexión en modo transacción
En modo transacción, una sola conexión por instancia pone en fila todas las consultas de esa instancia detrás de un único canal. Las consultas del daemon se acumulaban detrás, las peticiones superaban su plazo de veinte segundos y el síntoma pasó de errores 500 a bloqueos. La web de marketing seguía rápida. La API dejó de responder del todo.
El modo transacción devuelve la conexión tras cada transacción, así que necesita un pool de tamaño normal. Pusimos ocho y los timeouts desaparecieron.
Cómo verlo venir
El pooler no te dice nada hasta que te rechaza. Postgres sí te lo dice, si se lo preguntas.
select application_name, state, count(*) from pg_stat_activity where datname = 'postgres' group by 1, 2 order by 3 desc;
Ejecútalo en una conexión de sesión mientras la app tiene su carga normal. Si el recuento se acerca poco a poco al límite y casi todas las filas están en idle, estás a un minuto de mucho tráfico del error. En modo transacción, la misma consulta muestra conexiones que se reciclan en lugar de acapararse.
La cadena de conexión que de verdad funciona
postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres
- El host es el pooler, no
db.<ref>.supabase.co. El panel de Supabase puede mostrar el host directo bajo un encabezado de transaction pooler. Sigue siendo el host directo. - El usuario es
postgres.<project-ref>en el pooler.postgresa secas solo funciona en el host directo. - El host directo es solo IPv6, salvo que compres el complemento de IPv4. Tiene un registro AAAA y ningún registro A. Las funciones de Vercel no llegan a él, y además el puerto 6543 no está abierto ahí.
- No hace falta query string con postgres.js. Pon
prepare: false. El flag?pgbouncer=truees una convención de Prisma. - Deja las migraciones en el 5432. Drizzle y herramientas parecidas quieren una conexión de sesión. Apunta la app desplegada al 6543 y tu entorno local de migraciones al 5432.
$ dig +short A db.<project-ref>.supabase.co # (nothing) $ dig +short AAAA db.<project-ref>.supabase.co # 2600:1f18:... $ nc -z aws-0-us-east-1.pooler.supabase.com 6543 # succeeded
El tamaño, en una tabla
Una última cosa que conviene saber. Mientras pasaba todo esto, el daemon que no llegaba a la API concluyó que todas las máquinas que gestionaba estaban muertas. Se preparó para cortarles la corriente. Esa historia está en cómo un límite de conexiones a la base de datos casi reinicia los Mac de mis clientes.
Preguntas
- ¿Qué puerto de Supabase debe usar una app en Vercel?
- El 6543, el pooler en modo transacción. El modo sesión en el 5432 mantiene una conexión de servidor por cliente y limita el proyecto a 15, y el serverless las agota enseguida.
- ¿Por qué db.<project-ref>.supabase.co no conecta desde Vercel?
- El host directo solo tiene dirección IPv6, salvo que pagues el complemento de IPv4, y las funciones de Vercel no llegan a él por IPv6. Usa el host del pooler, aws-0-<region>.pooler.supabase.com.
- ¿Qué nombre de usuario necesita el pooler?
- postgres.<project-ref>, con la referencia del proyecto añadida. postgres a secas solo funciona en el host directo.
- ¿Necesito ?pgbouncer=true en la cadena de conexión?
- Con Prisma, sí. Con postgres.js, pon prepare: false en las opciones del cliente. El modo transacción no admite prepared statements.
- ¿Qué tamaño de pool debe usar cada modo?
- Modo sesión: una o dos por instancia, con un idle timeout corto, porque el límite es quince. Modo transacción: un pool normal, de cinco a diez, porque las conexiones se devuelven tras cada transacción.
Haz tus propias cuentas con la calculadora o alquila un runner.