Cuando tu aplicación en Supabase apenas supera los 100.000 usuarios simultáneos, comienzas a enfrentar problemas de timeouts inexplicables. Has optimizado tus queries, mejorado índices y cacheado con Redis. Incluso invertiste en el tier Pro con la esperanza de que más recursos resolvieran el problema. Sin embargo, las conexiones siguen fallando y los usuarios reportan errores 500 esporádicos. Tu dashboard de monitoreo revela algo curioso: Postgres tiene CPU disponible, la RAM está bien, y los discos funcionan. El problema no es obvio.
Photo: Shubham Dhage on Unsplash
El verdadero cuello de botella en la escalabilidad de Supabase no es Postgres ni tu código, sino PgBouncer y su gestión del connection pooling bajo carga. Esto afecta especialmente a aplicaciones con altos volúmenes de conexiones efímeras o picos de tráfico inesperados. Mientras Firebase se veía afectado por latencias en inicialización, Supabase te da la falsa impresión de haber escalado, cuando en realidad estás saturando un intermediario que, honestamente, nunca se diseñó para manejar tantas conexiones concurrentes con lógica compleja de autenticación.
El mito de la escalabilidad transparente de Postgres
Postgres es sólido, eso es incuestionable. Empresas como Instagram, Spotify y Discord lo utilizan eficientemente para manejar millones de usuarios. Sin embargo, Supabase no te ofrece Postgres puro, sino una arquitectura multicapa que genera fricción.
El stack de Supabase es así: tu aplicación → PostgREST (API REST automática) → PgBouncer (connection pooler) → Postgres. Aquí surge el primer problema: PgBouncer opera en "transaction pooling" por defecto, liberando conexiones tras cada transacción. Suena eficiente, pero si tu aplicación usa prepared statements o session-level variables, PgBouncer se ve obligado a mantener conexiones abiertas más tiempo de lo esperado.
Este comportamiento causa un efecto dominó en producción. Una query que normalmente tomaría 50ms podría quedarse esperando por una conexión disponible en PgBouncer. Supongamos que tu aplicación maneja 500 requests por segundo con una latencia de 100ms: necesitarías al menos 50 conexiones concurrentes. Sin embargo, el tier Pro de Supabase limita las conexiones a Postgres: 60 en el estándar, 120 en el Pro. Al alcanzar ese límite, las conexiones entrantes esperan en cola, lo que, si se llena demasiado rápido, podría quebrar tu aplicación.
La trampa del Real-time y Auth
Supabase promueve el real-time como una característica casi mágica: suscripciones en tiempo real a cambios en tu base de datos sin código adicional. Sin embargo, cada suscripción activa consume una conexión persistente. Con un dashboard colaborativo y 1.000 usuarios en tiempo real, ya usaste 1.000 conexiones. A esto, añade autenticación JWT que valida cada request con consultas adicionales a auth.users, y tus 120 conexiones disponibles desaparecen en un abrir y cerrar de ojos.
El problema radica en que Supabase no ofrece métricas detalladas del uso de conexiones por features. Ves las conexiones totales, pero no cuántas ocupan suscripciones real-time, queries API o tareas de autenticación. Al intentar resolver un timeout, te preguntas: ¿dónde realmente está el cuello de botella?
La arquitectura que Supabase no documenta
Photo: Deng Xiang on Unsplash
En 2024, un equipo de backend en una fintech europea migró a una instancia gestionada de Postgres en AWS RDS. No porque Postgres no escalara, sino porque Supabase añadía capas de abstracción imposibles de optimizar bajo carga.
Su problema: notificaciones en tiempo real donde cada usuario tenía de 5 a 20 suscripciones activas. Con 10.000 usuarios conectados, eso equivale a 200.000 conexiones. Supabase maneja esto con un servidor Realtime separado usando Phoenix Channels, pero cada canal valida permisos consultando Postgres. El resultado: notificaciones "en tiempo real" con latencias de hasta 3 segundos.
La solución fue compleja: implementaron un sistema de multiplexión con Elixir, manteniendo una conexión persistente a Postgres y sirviendo cambios a miles de usuarios mediante WebSockets. Así, reconstruyeron internamente lo que Supabase prometía.
El problema con Row Level Security en escala
RLS (Row Level Security) es clave para la seguridad a nivel de datos en Supabase, definiendo políticas SQL que filtran resultados según el usuario autenticado. Aunque elegante, tiene un costo oculto: cada query RLS ejecuta subconsultas adicionales para evaluar permisos.
Una query simple como:
SELECT * FROM posts WHERE user_id = current_user_id();
Con RLS activado, internamente es:
SELECT * FROM posts
WHERE user_id = current_user_id()
AND (
SELECT has_permission('read', posts.id, auth.uid())
FROM auth.users
WHERE id = auth.uid()
);
Con 500 requests/segundo, surgen miles de subconsultas para validar permisos. PgBouncer no puede optimizar esto, ya que cada transacción es independiente. La ironía está en que RLS es clave para la seguridad en Supabase, pero también lo que impide escalar significativamente. Así, los equipos terminan trasladando la lógica de permisos a su backend, anulando el valor de Supabase.
El muro de los 200.000 MAU que nadie menciona
Conversé con tres fundadores de productos SaaS en Supabase en 2025, y todos encontraron el mismo límite: cerca de 200.000 usuarios activos mensuales. No es un límite documentado, pero surge de la combinación entre connection pooling, RLS overhead y costos de infraestructura.
Uno de ellos, fundador de una plataforma colaborativa, lo describió en cifras: con 180.000 MAU, su factura en Supabase era de $850/mes (tier Pro). Al alcanzar 220.000 MAU, los timeouts surgieron durante picos de uso. Supabase recomendó migrar al tier Enterprise con conexiones dedicadas. ¿Costo? $2.500/mes base + excedentes.
La relación costo-beneficio falló. Migraron a Railway con Postgres y Hasura para GraphQL. Costo mensual: $450, con mejor latencia y control sobre el pooling. La migración tomó cuatro semanas, pero les permitió escalar a 500.000 usuarios sin cambios estructurales.
El overhead escondido de PostgREST
PostgREST es brillante al generar automáticamente una API REST desde tu esquema de Postgres. Sin embargo, cada abstracción tiene su costo. PostgREST procesa URLs complejas y las traduce a SQL dinámico, añadiendo latencia: entre 10-30ms por request.
Con volumen bajo, es imperceptible. Pero con 100.000 requests diarios, genera casi una hora de tiempo de CPU acumulado solo en traducción de queries. Además, al situarse entre tu aplicación y PgBouncer, consume conexiones mientras parsea y valida requests.
Equipos que necesitan latencias menores a 50ms acaban creando endpoints personalizados que interactúan directamente con Postgres, eludiendo PostgREST. Pero, ¿qué queda entonces del valor de Supabase? Pagas por características que no usas porque la arquitectura no soporta tu escala.
Alternativas cuando llegas al límite
La cuestión no es si Supabase escala, sino qué hacer al llegar a ese límite sin gastar $3.000/mes en infraestructura.
Primera opción: Hasura + Postgres gestionado. Hasura ofrece GraphQL nativo con RLS comparable, métricas detalladas de conexiones y pooling configurable. Puedes usar Hasura Cloud o alojarlo en Railway/Fly.io. Costo para 200K MAU: $400-600/mes frente a $2.500+ en Supabase Enterprise.
Segunda opción: Migrar real-time fuera de Postgres. Usa Supabase para datos transaccionales y shifts real-time a Ably o Pusher, liberando miles de conexiones. Un cliente fintech redujo su uso de conexiones en 70%, manteniendo la funcionalidad.
Tercera opción: Implementar connection pooling propio con PgCat. PgCat maneja millones de conexiones concurrentes. Puedes interponer PgCat entre tu aplicación y Supabase, controlando estrategias de pooling, sin migrar tu base de datos. Requiere infraestructura adicional, pero da tiempo a planear una migración organizada.
El verdadero costo de escalar en Supabase
Las discusiones sobre Supabase suelen enfocarse en costos, pero el verdadero gasto no está en la factura mensual. Radica en horas de ingeniería depurando timeouts inexplicables, sorteando limitaciones no documentadas y, finalmente, migrando cuando podrías haber elegido una arquitectura más transparente desde el inicio.
Supabase es ideal para MVPs, proyectos medianos y aplicaciones que no requieren soportar cientos de miles de usuarios concurrentes. Pero si tu startup crece rápidamente, necesitas saber exactamente dónde se sitúa el límite antes de construir características críticas sobre una arquitectura que no podrás controlar.
La decisión que nadie quiere tomar
La cruda realidad es que Supabase funciona de maravilla hasta que no lo hace. Y cuando falla, los problemas son estructurales, no configurables. No puedes ajustar PgBouncer más allá de parámetros básicos. Reemplazar PostgREST rompería todo el stack. Migrar gradualmente real-time a otra solución implica reescribir código.
Fundadores exitosos que conozco eligieron una de dos rutas: planearon desde el inicio una arquitectura híbrida donde Supabase es solo un componente, o aceptaron que Supabase es una fase temporal antes de migrar a infraestructura que controlen.
Lo que nadie te dice es que la abstracción que hace atractivo a Supabase inicialmente, es la misma que lo limita en el largo plazo. No es un defecto, es una elección de diseño. ¿Esta decisión se alinea con tu hoja de ruta de crecimiento?
¿Tu startup ya chocó contra este límite o sigues creyendo que más RAM resolverá el problema?