Nuestro artículo sobre compatibilidad con PostgREST detalla lo que cubre funcionalmente el servidor (filtros, incrustación, RPC, RLS) y lo que deja a usted. Éste viene de otro lugar. Documenta, con el código de Aurabase y la documentación oficial de PostgREST como fuentes, dónde y por qué PostgREST realmente se estanca a escala. No estamos reproduciendo aquí un banco de carga que no hayamos gestionado nosotros mismos. Nuestra metodología de benchmark explica por qué una cifra aislada, sin un protocolo publicado, no nos parece fiable.
Lo esencial
- PostgREST en sí es liviano: de 50 a 250 milicores de CPU, de 64 a 128 MB de RAM por réplica en instancias dedicadas de Aurabase. El rendimiento HTTP sin procesar casi nunca es el factor limitante en la producción.
- El límite real es el presupuesto de conexión de Postgres:
PGRST_DB_POOL× réplicas. Verificado en el código de Aurabase: 20 conexiones por proyecto en el nivel dedicado (10×2), 4 en el nivel compartido (2×2). Esta es una elección deliberada para incluir más inquilinos en el mismomax_connections. Prefer: count=exactfuerza un costoso escaneo MVCC en tablas grandes. PostgREST documenta dos alternativas más económicas:count=plannedycount=estimated, con un costo total aproximado.- Un límite
db-max-rows(1000 líneas por defecto en Aurabase) trunca una respuesta SIN reportarla enContent-Range(medido en condiciones reales, detallado a continuación). - Después de una migración DDL, la caché del esquema PostgREST se recarga de forma asincrónica. La puerta de enlace de Aurabase lo reintenta hasta 8 veces (alrededor de 3,5 segundos acumulativos en el peor de los casos) antes de darse por vencido, un comportamiento documentado directamente en el código.
Qué mide un benchmark PostgREST y qué no mide
Una prueba de rendimiento HTTP en PostgREST mide principalmente Postgres, rara vez PostgREST. El servidor es una fina capa de traducción delante de la base. En la gran mayoría de las cargas del mundo real, el tiempo de respuesta lo domina la consulta SQL ejecutada, no el proceso que la generó.
El proyecto PostgREST mantiene un repositorio dedicado a este tema, PostgREST/postgrest-benchmark en GitHub, que rastrea las variaciones de rendimiento de una versión a otra en lugar de publicar una cifra de marketing aislada. No lo hemos realizado ni republicado aquí. Sus resultados dependen del hardware, el tamaño del esquema y el escenario probado, exactamente las variables que nuestro propio protocolo de referencia requiere documentar antes de citar una cifra.
Debajo de PostgREST, está pgbench que mide la capa que realmente importa: el tiempo de transacción SQL bajo carga concurrente. Esta es la herramienta de referencia oficial de PostgreSQL (postgresql.org/docs/current/pgbench.html, consultada el 24 de agosto de 2026). En lugar de reproducir este protocolo aquí, este artículo documenta cuatro limitaciones arquitectónicas concretas de PostgREST en producción, cada una verificada en el código fuente de Aurabase o en la documentación oficial del proyecto.
La huella real de una instancia de PostgREST en Aurabase
Cada proyecto del motor Aurabase Postgres recibe dos réplicas PostgREST dedicadas, ubicadas junto con su clúster. El manifiesto de Kubernetes que los implementa establece recursos modestos.
Lo que realmente consumen estas réplicas no es CPU: son conexiones al primario de Postgres. Cada instancia de PostgREST se conecta directamente a la instancia principal (-rw), sin pasar por el agrupador PgBouncer implementado para el inquilino. Esta elección ya se detalla en nuestro artículo sobre Compatibilidad con PostgREST: el mecanismo de recarga del esquema LISTEN/NOTIFY requiere una conexión persistente, incompatible con un pooler en modo transacción. Qué agrega este artículo: cuánto cuesta realmente, en términos de conexiones, y dónde alcanza su punto máximo.
El tamaño de este grupo por réplica (PGRST_DB_POOL) difiere deliberadamente según el nivel del proyecto, verificado en k8s_tenant.rs, la función que crea el manifiesto PostgREST para cada proyecto:
| Rodamiento | PGRST_DB_POOL/réplica | Réplicas | Conexiones / proyecto despertado |
|---|---|---|---|
| Dedicado (premium, A1) | 10 (predeterminado PostgREST) | 2 | 20 |
| Compartido (flota, gratis/pro/equipo) | 2 (predeterminado de Aurabase, reducido) | 2 | 4 |
En el nivel dedicado, la restricción se relaja: un proyecto tiene su propio clúster CNPG y, por lo tanto, su propio max_connections, sin vecinos de sobra. A nivel compartido, varios proyectos de una misma organización comparten un único cluster: es este contexto el que hace decisivo el presupuesto de conexiones, que se desarrolla en el siguiente apartado.
El presupuesto de conexiones decide cuántos inquilinos se están ejecutando al mismo tiempo.
En un clúster de Postgres compartido, no es el rendimiento HTTP lo que limita la cantidad de proyectos activos simultáneamente. Esta es la cantidad de conexiones que sus instancias PostgREST mantienen abiertas en la principal, en comparación con las max_connectionsdisponibles.
Aurabase deriva este presupuesto directamente de los límites reales del clúster, marcados en fleet.rs: (max_connections − réserve superuser/CNPG − connexions réservées au pooler) ÷ connexions par projet éveillé, piso en 1. La reserva fija es 10 conexiones (superusuario, administrador de instancias CNPG, exportador de métricas, margen de administrador del aprovisionador). En cuanto a los defectos entregados (grupo de 2 por réplica, 2 réplicas o 4 conexiones por proyecto activado), el cálculo arroja tres presupuestos diferentes según el nivel de tamaño del clúster.
Fuente: derivado de fleet.rs::derive_wake_budget y wake_budget_for_org_plan, código de Aurabase, releído el 24 de agosto de 2026.
Este presupuesto no es una cuota de proyectos propios: una organización team puede tener 50 proyectos, la mayoría de los cuales están inactivos. Este es un límite de concurrencia : la cantidad de proyectos que pueden mantener conexiones abiertas al mismo tiempo en el primario. Un despertar por encima del presupuesto no falla, se aplaza hasta que un proyecto hermano vuelva a dormir, comprobado en el mismo archivo. El tema del tamaño de max_connections en sí se amplía en nuestro artículo sobre el ajuste de de max_connectionsy la compensación dedicada/mutualizada en su conjunto en base dedicada vs. compartida.
Por qué preferir: count=exact ralentiza una consulta en una tabla grande
Solicitar un total exacto obliga a Postgres a contar las filas visibles del resultado filtrado con cada consulta, un costo que crece con la tabla, no es una operación gratuita.
PostgreSQL no mantiene ningún contador de filas indexadas listo para usar. En MVCC, la visibilidad de una fila depende de la transacción que la lee. Por lo tanto, un COUNT(*) exacto debe visitar las filas candidatas en lugar de leer un valor precalculado. Esta es una limitación estructural bien documentada en el ecosistema de Postgres, incluidos los proveedores de análisis como ClickHouse, que comparan sus propios contadores aproximados con el comportamiento transaccional de Postgres.
PostgREST documenta estas tres estrategias de conteo de forma nativa (postgrest.org, consultado el 24 de agosto de 2026). La estrategia exact garantiza un total al precio del escaneo. planned devuelve una estimación casi gratuita del planificador de consultas. estimated cambia automáticamente entre los dos según un umbral. La elección no es cosmética: una paginación que requiere count=exact en una tabla de varios millones de filas paga por este escaneo en cada página, incluso cuando el usuario nunca consulta la última.
El truncamiento de db-max-rows es invisible sin count=exact
Un límite de fila puede truncar una respuesta PostgREST sin ninguna indicación en el cuerpo o los encabezados, a menos que se solicite explícitamente un total exacto. Lo medimos en condiciones reales en una instancia dedicada de Aurabase, no asumida.
En una tabla de prueba de 10 filas con PGRST_DB_MAX_ROWS=5, PostgREST v12.2.3 representa exactamente el mismo encabezado Content-Range para dos situaciones muy diferentes:
| Consulta | Líneas renderizadas | Rango de contenido | meta (Aurabase) |
|---|---|---|---|
| ?límite=50 (sin contar) | 5/10 reales | 0-4/* | {} |
| ?límite=50&count=exacto | 5/10 reales | 0-4/10 | {total: 10} |
Sin count=exact, la respuesta de 5 filas es indistinguible de una tabla que en realidad solo contendría 5: Content-Range: 0-4/* describe las filas representadas, nunca el límite aplicado. El límite real vigente no aparece en ninguna parte en este caso, medido directamente en la ruta PostgREST del SDK de Aurabase.
Si su implementación de PostgREST establece un db-max-rows (Aurabase tiene como valor predeterminado 1000), un cliente que compare data.length con el límite solicitado para detectar una página completa puede estar equivocado. El error aparece tan pronto como el límite del servidor es inferior a este límite. La única señal confiable es comparar el número de líneas recibidas con el total devuelto por count=exact, lo que pone en juego directamente la compensación de costos descrita en la sección anterior.
Recargar la caché de esquema después de una migración
PostgREST mantiene el esquema de Postgres en la memoria al inicio. Después de un DDL (crear tabla, agregar columna), este caché debe recargarse antes de que responda la nueva ruta, y esta recarga es asíncrona.
Una escritura que llega a esta ventana puede recibir un 404 transitorio (caché aún no actualizado), aunque la tabla exista en el lado de Postgres. La puerta de enlace de Aurabase absorbe esto con un bucle de reintento limitado, verificado en postgrest_proxy.rs: hasta 8 intentos, retroceso creciente (250 ms más 100 ms por intento), 3,5 segundos acumulativos en el peor de los casos. Este mecanismo sólo afecta a las escrituras, nunca a las lecturas.
La pasarela no emite ninguna señal de recarga: sólo espera. El único desencadenante real es un pg_notify('pgrst', 'reload schema') emitido por el servicio de base de datos en la ruta DDL. Si una ruta de migración olvida emitir esta señal, los 8 intentos se agotan en un caché que nunca cambiará, un riesgo documentado tal como está en el comentario del código, no disfrazado.
Para una implementación de PostgREST autohospedada, la lección se generaliza. Cada ruta DDL en su aplicación debe activar la recarga, a través de NOTIFY o una señal SIGUSR1 al proceso. De lo contrario, una migración produce un pico de latencia p99 disfrazado de errores intermitentes inmediatamente después de la implementación.
Lo que divide la arquitectura, no el rendimiento bruto
Las cuatro limitaciones documentadas aquí tienen una cosa en común: ninguna se observa en una prueba de rendimiento HTTP aislada, sin embargo, las cuatro determinan si una implementación de PostgREST escala a producción.
- Presupuesto de conexión: limita la cantidad de inquilinos activos simultáneamente en un clúster compartido, independientemente del rendimiento por inquilino.
- Costo exacto COUNT: crece con la mesa, no con la carga; se omite con
planned/estimated. - Truncamiento silencioso: un límite de fila configurado correctamente aún puede interrumpir la paginación mal instrumentada.
- Recarga del esquema: una ventana de latencia después de cada migración, limitada si la señal de recarga está bien conectada, ilimitada en caso contrario.
Ya sea que elija entre PostgREST autohospedado, una capa GraphQL estilo Hasura o una API personalizada, estos cuatro ejes son un mejor punto de comparación que una cifra de solicitudes aisladas. Vea nuestra comparación PostgREST vs Hasura vs API personalizada. La elección del pooler que se encuentra frente a su base de datos es igualmente importante: nuestra comparación PgBouncer vs Supavisor vs PgCat detalla por qué PostgREST no puede pasar por un pooler en modo de transacción.
Preguntas frecuentes
que recordar
PostgREST casi nunca falla solo con la carga HTTP: su arquitectura es demasiado simple para eso. Lo que rompe en la producción es lo que la rodea: cuántas conexiones mantienen abiertas sus réplicas, cuánto cuesta un total exacto. Esto también incluye si un truncamiento permanece visible y cuánto dura la ventana después de una migración.
Estas cuatro limitaciones no son específicas de Aurabase: se aplican a cualquier implementación de PostgREST, autohospedada o administrada. Lo que este código muestra es cómo una implementación multiinquilino los hace explícitos en lugar de dejarlos sorprendentes en producción.