PRODPiattaforma BaaS europea sovranaApri Dashboard →

Prestazioni · 11 lettura minima

PostgREST: benchmark e limiti reali nella produzione

Affane Daylami · Fondateur · 18 maggio 2026

Torniamo al blog

Lo stesso PostgREST non è quasi mai il collo di bottiglia. Sulle istanze Aurabase dedicate, una replica viene eseguita con da 50 a 250 millicore di CPU e da 64 a 128 MB di RAM. È un binario Haskell leggero che traduce le richieste HTTP in SQL, niente di più. I veri limiti che appaiono nella produzione sono altrove. Quattro di questi emergono più spesso: il budget per le connessioni Postgres consumate dalle sue repliche e il costo di un COUNT esatto in MVCC. Anche un troncamento della risposta può rimanere invisibile nelle intestazioni, così come una finestra di latenza dopo ogni migrazione dello schema.

Questo testo inglese è stato generato automaticamente dall'originale francese e non è stato ancora rivisto.
Questa pagina è stata tradotta automaticamente. Fa fede la versione inglese.

Il nostro articolo sulla compatibilità PostgREST descrive in dettaglio ciò che il server copre funzionalmente (filtri, incorporamento, RPC, RLS) e cosa lascia a te. Questo viene da qualche altra parte. Documenta, con il codice Aurabase e la documentazione ufficiale PostgREST come fonti, dove e perché PostgREST effettivamente si stabilizza su larga scala. Non riproduciamo qui un banco di carico che non abbiamo gestito noi stessi. La nostra metodologia di benchmark spiega perché un dato isolato, senza un protocollo pubblicato, non ci sembra affidabile.

L'essenziale

  • PostgREST in sé è leggero: da 50 a 250 millicore di CPU, da 64 a 128 MB di RAM per replica su istanze Aurabase dedicate. Il throughput HTTP grezzo non è quasi mai il fattore limitante nella produzione.
  • Il vero tetto è il budget di connessione Postgres: PGRST_DB_POOL × repliche. Verificato nel codice Aurabase: 20 connessioni per progetto sul livello dedicato (10×2), 4 sul livello condiviso (2×2). Questa è una scelta deliberata per ospitare più inquilini sullo stesso max_connections.
  • Prefer: count=exact forza la costosa scansione MVCC su tabelle di grandi dimensioni. PostgREST documenta due alternative più economiche: count=planned e count=estimated, con un costo totale approssimativo.
  • Un limite db-max-rows (1000 linee per impostazione predefinita in Aurabase) tronca una risposta SENZA riportarla in Content-Range (misurato in condizioni reali, dettagliato di seguito).
  • Dopo una migrazione DDL, la cache dello schema PostgREST viene ricaricata in modo asincrono. Il gateway Aurabase riprova fino a 8 volte (circa 3,5 secondi cumulativi nel caso peggiore) prima di arrendersi, un comportamento documentato direttamente nel codice.
#
Metodologia

Cosa misura un benchmark PostgREST e cosa non misura

Un test di throughput HTTP su PostgREST misura principalmente Postgres, raramente PostgREST. Il server è un sottile strato di traduzione davanti alla base. Nella stragrande maggioranza dei carichi reali, il tempo di risposta è dominato dalla query SQL eseguita, non dal processo che l'ha generata.

Il progetto PostgREST mantiene un repository dedicato per questo argomento, PostgREST/postgrest-benchmark su GitHub, che tiene traccia delle variazioni di throughput da un rilascio all'altro anziché pubblicare una cifra di marketing isolata. Non l'abbiamo né eseguito né ripubblicato qui. I risultati dipendono dall'hardware, dalle dimensioni dello schema e dallo scenario testato, esattamente le variabili che il nostro protocollo di benchmark richiede che siano documentate prima di citare una cifra.

Sotto PostgREST, c'è pgbench che misura il livello che conta davvero: il tempo di transazione SQL sotto carico simultaneo. Questo è lo strumento di benchmark ufficiale di PostgreSQL (postgresql.org/docs/current/pgbench.html, accesso il 24 agosto 2026). Invece di riprodurre qui questo protocollo, questo articolo documenta quattro limitazioni architettoniche concrete di PostgREST in produzione, ciascuna verificata nel codice sorgente Aurabase o nella documentazione ufficiale del progetto.

#
Codice registrato

L'impronta effettiva di un'istanza PostgREST su Aurabase

Ogni progetto del motore Aurabase Postgres riceve due repliche PostgREST dedicate, co-localizzate con il suo cluster. Il manifest Kubernetes che li distribuisce imposta risorse modeste.

50-250m
CPU PER REPLICA
richieste → limiti
64-128
MB DI RAM PER REPLICA
richieste → limiti
2
REPLICHE PER PROGETTO
alta disponibilità (P22)

Ciò che realmente consumano queste repliche non è la CPU: sono connessioni al primario Postgres. Ogni istanza PostgREST si connette direttamente al primario (-rw), senza passare attraverso il pooler PgBouncer distribuito per il tenant. Questa scelta è già dettagliata nel nostro articolo sulla compatibilità PostgREST: il meccanismo di ricaricamento dello schema LISTEN/NOTIFY richiede una connessione persistente, incompatibile con un pooler in modalità transazione. Cosa aggiunge questo articolo: quanto costa effettivamente, in termini di connessioni, e dove raggiunge i picchi.

La dimensione di questo pool per replica (PGRST_DB_POOL) varia deliberatamente a seconda del livello del progetto, verificato in k8s_tenant.rs, la funzione che crea il manifest PostgREST per ciascun progetto:

CuscinettoPGRST_DB_POOL / replicaReplicheConnessioni/progetto risvegliato
Dedicato (premium, A1)10 (impostazione predefinita PostgREST)220
Condiviso (flotta, gratuito/pro/team)2 (default Aurabase, abbassato)24
deploy/cnpg/tenant-postgrest.yaml (estratto reale, valore sostituito dal prestatore)yaml
# Impronta digitale delle connessioni per replica sul primario.
env:
  - { name: PGRST_DB_POOL, value: "PGRST_DB_POOL_VALUE" }  # 10 (dedicati) o 2 (condivisi)

A livello dedicato, il vincolo è allentato: un progetto ha il proprio cluster CNPG, quindi il proprio max_connections, senza vicini da risparmiare. Sul piano condiviso, più progetti della stessa organizzazione condividono un unico cluster: è questo contesto che rende decisivo il budget delle connessioni, sviluppato nella sezione successiva.

#
Il vero soffitto

Il budget delle connessioni decide quanti tenant sono in esecuzione contemporaneamente

Su un cluster Postgres condiviso, non è il throughput HTTP a limitare il numero di progetti attivi contemporaneamente. Questo è il numero di connessioni che le loro istanze PostgREST mantengono aperte sul primario, rispetto alle max_connectionsdisponibili.

Aurabase ricava questo budget direttamente dai limiti effettivi del cluster, controllati in fleet.rs: (max_connections − réserve superuser/CNPG − connexions réservées au pooler) ÷ connexions par projet éveillé, minimo a 1. La riserva fissa è di 10 connessioni (superutente, gestore di istanze CNPG, esportatore di metriche, margine di amministrazione del provisioner). Sui difetti consegnati (pool di 2 per replica, 2 repliche o 4 connessioni per progetto risvegliato), il calcolo fornisce tre budget diversi a seconda del livello di dimensionamento del cluster.

Budget per progetti attivi contemporaneamente per livello, cluster Postgres condivisoLivello gratuito: 5 progetti attivi simultanei (max_connections 50, pooler 20). Livello Pro: 7 (max_connections 100, pooler 60). Livello squadra: 10 (max_connections 200, pooler 150). Formula derivata dal codice Aurabase (fleet.rs::derive_wake_budget), riserva fissa di 10 connessioni, 4 connessioni per progetto sveglio.024681012gratuito (max_connessioni 50)5 progettipro (max_connessioni 100)7 progettisquadra (max_connections 200)10 progetti

Fonte: derivato da fleet.rs::derive_wake_budget e wake_budget_for_org_plan, codice Aurabase, riletto il 24 agosto 2026.

Questo budget non è una quota di progetti posseduti: un'organizzazione team può contenere 50 progetti, la maggior parte dei quali sono dormienti. Questo è un limite di concorrenza: il numero di progetti che possono mantenere connessioni aperte contemporaneamente sul primario. Un risveglio oltre il budget non fallisce, viene rinviato fino a quando un progetto fratello non torna a dormire, controllato nello stesso file. L'argomento del dimensionamento stesso di max_connections è approfondito nel nostro articolo sull'ottimizzazione di max_connectionse sul compromesso dedicato/mutualizzato nel suo insieme in base dedicata e condivisa.

#
Costo nascosto

Perché preferire: count=exact rallenta una query su una tabella di grandi dimensioni

La richiesta di un totale esatto costringe Postgres a contare le righe visibili del risultato filtrato con ogni query, un costo che cresce con la tabella, non un'operazione gratuita.

PostgreSQL non mantiene alcun contatore di righe indicizzate pronto all'uso. In MVCC, la visibilità di una riga dipende dalla transazione che la legge. Un COUNT(*) esatto deve quindi visitare le righe candidate anziché leggere un valore precalcolato. Questa è una limitazione strutturale ben documentata nell'ecosistema Postgres, compresi i fornitori di analisi come ClickHouse, che confrontano i propri contatori approssimativi con il comportamento transazionale di Postgres.

terminalbash
# Costoso su una tabella di grandi dimensioni: forza una scansione MVCC del risultato filtrato
curl "https://<gateway>/v1/db/<project_id>/orders?status=eq.paid" \
  -H "apikey: <clé>" -H "Prefer: count=exact"

# Alternative meno costose, documentate da PostgREST
  -H "Prefer: count=planned"   # stima tramite pianificatore
  -H "Prefer: count=estimated" # pianificato oltre una soglia, esattamente sotto

PostgREST documenta queste tre strategie di conteggio in modo nativo (postgrest.org, accesso il 24 agosto 2026). La strategia exact garantisce il totale al prezzo della scansione. planned restituisce una stima quasi gratuita dal query planner. estimated passa automaticamente tra i due in base a una soglia. La scelta non è cosmetica: un'impaginazione che richiede count=exact su una tabella di diversi milioni di righe paga questa scansione su ogni pagina, anche quando l'utente non consulta mai l'ultima.

#
Misurato in tempo reale

Il troncamento di db-max-rows è invisibile senza count=exact

Un limite di riga può troncare una risposta PostgREST senza alcuna indicazione nel corpo o nelle intestazioni, a meno che non venga richiesto esplicitamente un totale esatto. L'abbiamo misurato in condizioni reali su un'istanza Aurabase dedicata, non scontata.

Su una tabella di test a 10 righe con PGRST_DB_MAX_ROWS=5, PostgREST v12.2.3 esegue esattamente la stessa intestazione Content-Range per due situazioni molto diverse:

DomandaLinee renderizzateIntervallo di contenutimeta (Aurabase)
?limit=50 (senza conteggio)5/10 reale0-4/*{}
?limite=50&conteggio=esatto5/10 reale0-4/10{totale: 10}

Senza count=exact, la risposta di 5 righe è indistinguibile da una tabella che in realtà ne conterrebbe solo 5: Content-Range: 0-4/* descrive le righe renderizzate, mai il limite applicato. Il limite effettivo in effetti non appare da nessuna parte in questo caso, misurato direttamente sul percorso PostgREST dell'SDK Aurabase.

Conseguenza per qualsiasi impaginazione su PostgREST

Se la tua distribuzione PostgREST imposta un db-max-rows (Aurabase ha il valore predefinito su 1000), un client che confronta data.length con il limite richiesto per rilevare una pagina intera potrebbe sbagliarsi. L'errore appare non appena il limite massimo del server è inferiore a questo limite. L'unico segnale affidabile è confrontare il numero di linee ricevute con total restituito da count=exact, che mette direttamente in gioco il compromesso sui costi descritto nella sezione precedente.

#
Latenza differita

Ricaricamento della cache dello schema dopo una migrazione

PostgREST mantiene lo schema Postgres in memoria all'avvio. Dopo un DDL (crea tabella, aggiungi colonna), questa cache deve essere ricaricata prima che la nuova route risponda e questo ricaricamento è asincrono.

Una scrittura che arriva in questa finestra potrebbe ricevere un transitorio 404 (cache non ancora aggiornata), anche se la tabella esiste effettivamente sul lato Postgres. Il gateway Aurabase lo assorbe con un ciclo di tentativi limitato, verificato in postgrest_proxy.rs: fino a 8 tentativi, backoff crescente (250 ms più 100 ms per tentativo), 3,5 secondi cumulativi nel caso peggiore. Questo meccanismo influisce solo sulle scritture, mai sulle letture.

Un dettaglio che il codice stesso documenta

Il gateway non emette alcun segnale di ricarica: aspetta solo. L'unico vero trigger è un pg_notify('pgrst', 'reload schema') emesso dal servizio database sul percorso DDL. Se un percorso di migrazione si dimentica di emettere questo segnale, gli 8 tentativi si esauriscono in una cache che non cambierà mai, un rischio documentato così com'è nel commento del codice, non mascherato.

Per una distribuzione PostgREST self-hosted, la lezione è di carattere generale. Ogni percorso DDL nell'applicazione dovrebbe attivare il ricaricamento, tramite NOTIFY o un segnale SIGUSR1 al processo. In caso contrario, una migrazione produce un picco di latenza p99 mascherato da errori intermittenti subito dopo la distribuzione.

#
Sommario

Ciò che l'architettura suddivide, non il throughput grezzo

Le quattro limitazioni qui documentate condividono una cosa in comune: nessuna viene rilevata in un test di throughput HTTP isolato, ma tutte e quattro determinano se una distribuzione PostgREST si adatta alla produzione.

  • Budget di connessione: limita il numero di tenant attivi contemporaneamente su un cluster condiviso, indipendentemente dal throughput per tenant.
  • Costo del COUNT esatto: cresce con la tabella, non con il carico; viene bypassato con planned/estimated.
  • Troncamento silenzioso: un limite di riga configurato correttamente può comunque interrompere il paging scarsamente strumentato.
  • Ricarica dello schema: una finestra di latenza dopo ogni migrazione, limitata se il segnale di ricarica è ben cablato, illimitata altrimenti.

Sia che tu scelga tra PostgREST self-hosted, un livello GraphQL in stile Hasura o un'API personalizzata, questi quattro assi rappresentano un punto di confronto migliore rispetto a una cifra req/s isolata. Guarda il nostro confronto PostgREST vs Hasura vs API personalizzata. La scelta del pooler che si trova di fronte al tuo database è altrettanto importante: il nostro confronto PgBouncer vs Supavisor vs PgCat spiega in dettaglio perché PostgREST non può passare attraverso un pooler in modalità transazione.

#
Domande frequenti

Domande frequenti

PostgREST è abbastanza veloce per la produzione su larga scala?+
PostgREST stesso è un processo leggero. Sulle istanze Aurabase dedicate, una replica viene eseguita con da 50 a 250 millicore di CPU e da 64 a 128 MB di RAM, verificati nel manifest Kubernetes del progetto. Il throughput HTTP grezzo non è quasi mai il fattore limitante nella produzione. È il budget della connessione Postgres, il costo di un COUNT esatto e la cache dello schema a determinare se l'intera cosa è scalabile, non la velocità del solo binario PostgREST.
Come faccio a sapere se la mia risposta PostgREST è stata troncata da db-max-rows?+
L'intestazione Content-Range restituita da PostgREST non lo dice mai. Una risposta limitata a 5 righe per db-max-rows è indistinguibile da una tabella che in realtà ne contiene solo 5, misurata in condizioni reali su un'istanza Aurabase dedicata. L'unico modo affidabile per rilevarlo è confrontare il numero di righe ricevute con il totale restituito da Prefer: count=exact, senza questa intestazione il troncamento rimane invisibile.
Il COUNT esatto rallenta sempre una richiesta PostgREST?+
Preferisci: count=exact forza Postgres a contare le righe visibili del risultato filtrato con ogni query, un costo che aumenta con la dimensione della tabella a causa di MVCC. Postgres non mantiene un contatore di righe indicizzato pronto all'uso. PostgREST offre due alternative meno costose, count=planned (stima tramite lo scheduler) e count=estimated (passaggio automatico oltre una soglia), documentate nella sua documentazione ufficiale.
Quante connessioni Postgres consuma PostgREST?+
Dipende interamente da PGRST_DB_POOL moltiplicato per il numero di repliche. Verificato nel codice Aurabase: un'istanza dedicata (livello premium) apre per impostazione predefinita 10 connessioni per replica, o 20 in totale su 2 repliche. Il livello condiviso riduce volontariamente questo pool a 2 per replica o 4 connessioni per progetto risvegliato, per accogliere più tenant con lo stesso budget max_connections del cluster condiviso.
Esiste un benchmark PostgREST ufficiale?+
Il progetto mantiene un repository dedicato, PostgREST/postgrest-benchmark su GitHub, che tiene traccia delle variazioni di throughput da un rilascio all'altro anziché pubblicare una cifra di marketing isolata. Non l'abbiamo né eseguito né ripubblicato qui. Questo articolo documenta le limitazioni architettoniche verificate nel nostro codice e nella documentazione ufficiale PostgREST, non in un banco che abbiamo riprodotto noi stessi.
#
Conclusione

Cosa ricordare

PostgREST non si rompe quasi mai sotto il solo carico HTTP: la sua architettura è troppo semplice per questo. Ciò che interrompe la produzione è ciò che la circonda: quante connessioni tengono aperte le sue repliche, quanto costa un totale esatto. Ciò include anche se un troncamento rimane visibile e quanto tempo dura la finestra dopo una migrazione.

Queste quattro limitazioni non sono specifiche di Aurabase: si applicano a qualsiasi distribuzione PostgREST, self-hosted o gestita. Ciò che questo codice mostra è come una distribuzione multi-tenant li renda espliciti anziché lasciarli sorprendenti in produzione.

PRONTO PER L'IMPLEMENTAZIONE?

Il tuo backend in cinque minuti.

Nessuna carta di credito richiesta · 500 MB gratuiti · 50.000 MAU