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 stessomax_connections. Prefer: count=exactforza la costosa scansione MVCC su tabelle di grandi dimensioni. PostgREST documenta due alternative più economiche:count=plannedecount=estimated, con un costo totale approssimativo.- Un limite
db-max-rows(1000 linee per impostazione predefinita in Aurabase) tronca una risposta SENZA riportarla inContent-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.
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.
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.
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:
| Cuscinetto | PGRST_DB_POOL / replica | Repliche | Connessioni/progetto risvegliato |
|---|---|---|---|
| Dedicato (premium, A1) | 10 (impostazione predefinita PostgREST) | 2 | 20 |
| Condiviso (flotta, gratuito/pro/team) | 2 (default Aurabase, abbassato) | 2 | 4 |
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 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.
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.
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.
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.
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:
| Domanda | Linee renderizzate | Intervallo di contenuti | meta (Aurabase) |
|---|---|---|---|
| ?limit=50 (senza conteggio) | 5/10 reale | 0-4/* | {} |
| ?limite=50&conteggio=esatto | 5/10 reale | 0-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.
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.
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.
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.
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
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.