KoderKoder.ai
PrezziEnterpriseIstruzionePer gli investitori
AccediInizia ora

Prodotto

PrezziEnterprisePer gli investitori

Risorse

ContattaciAssistenzaIstruzioneBlog

Note legali

Informativa sulla privacyTermini di utilizzoSicurezzaNorme di utilizzoSegnala un abuso

Social

LinkedInTwitter
Koder.ai
Lingua

© 2026 Koder.ai. Tutti i diritti riservati.

Home›Blog›Go + Postgres: guida pratica per ottimizzare le prestazioni delle API
14 dic 2025·8 min

Go + Postgres: guida pratica per ottimizzare le prestazioni delle API

Playbook per l'ottimizzazione delle prestazioni Go + Postgres per API generate da AI: gestisci il pool di connessioni, leggi i piani di query, indicizza con criterio, usa paginazione sicura e ottimizza JSON.

Go + Postgres: guida pratica per ottimizzare le prestazioni delle API

Come si manifesta la "lentezza" per API Go su Postgres

Le API generate per AI possono sembrare veloci nei primi test. Colpisci un endpoint poche volte, il dataset è piccolo e le richieste arrivano una alla volta. Poi arriva il traffico reale: endpoint misti, carichi a raffica, cache più fredde e molte più righe del previsto. Lo stesso codice può iniziare a sembrare casualmente lento anche se nulla si è rotto.

La lentezza di solito si vede in pochi modi: picchi di latenza (la maggior parte delle richieste va bene, ma alcune impiegano 5x–50x più tempo), timeout (una piccola percentuale fallisce) o CPU alta (Postgres per lavoro di query, o Go per JSON, goroutine, logging e retry).

Uno scenario comune è un endpoint di lista con un filtro di ricerca flessibile che restituisce una grande risposta JSON. In un DB di test scansiona poche migliaia di righe e finisce in fretta. In produzione scansiona milioni di righe, le ordina e solo dopo applica un LIMIT. L'API continua a "funzionare", ma la latenza p95 esplode e alcune richieste timeout durante i picchi.

Per separare la lentezza del database da quella dell'app, mantieni il modello mentale semplice.

Se il database è lento, il tuo handler Go passa la maggior parte del tempo in attesa della query. Potresti anche vedere molte richieste "in volo" mentre la CPU di Go sembra normale.

Se l'app è lenta, la query termina in fretta, ma si perde tempo dopo la query: costruire grandi oggetti di risposta, marshaling JSON, eseguire query extra per riga o fare troppo lavoro per richiesta. CPU e memoria di Go salgono e la latenza cresce con la dimensione della risposta.

"Abbastanza buono" prima del lancio non è perfezione. Per molti endpoint CRUD, punta a una p95 stabile (non solo alla media), comportamento prevedibile sotto raffiche e nessun timeout al picco previsto. L'obiettivo è semplice: niente richieste lente a sorpresa quando dati e traffico crescono, e segnali chiari quando qualcosa degrada.

Baseline prima di tutto: i numeri che contano

Prima di ottimizzare, decidi cosa significa "bene" per la tua API. Senza baseline, è facile passare ore a cambiare impostazioni e non sapere se hai migliorato o solo spostato il collo di bottiglia.

Tre numeri di solito raccontano la maggior parte della storia:

  • p95 della latenza delle richieste (non la media)
  • tasso di errori (HTTP 5xx, timeout, richieste cancellate)
  • tempo DB per richiesta (quanto ogni richiesta aspetta Postgres)

p95 è la metrica del "giorno no". Se p95 è alto ma la media va bene, un piccolo sottoinsieme di richieste sta facendo troppo lavoro, è bloccato da lock o innesca piani lenti.

Rendi visibili le query lente presto. In Postgres, abilita il logging delle query lente con una soglia bassa per i test pre-lancio (ad esempio 100–200 ms) e registra l'intera istruzione così da poterla copiare in un client SQL. Mantieni questa pratica temporanea: registrare ogni query lenta in produzione diventa rumoroso rapidamente.

Poi testa con richieste realistiche, non solo una singola rotta "hello world". Un piccolo set è sufficiente se rispecchia cosa faranno gli utenti: una chiamata di lista con filtri e ordinamento, una pagina dettaglio con un paio di join, una create o update con validazione e una query in stile search con match parziale.

Se generi endpoint da uno spec (ad esempio con uno strumento di generazione come Koder.ai), esegui lo stesso insieme di richieste ripetutamente con input costanti. Questo rende più facili da misurare cambi come indici, modifiche di paginazione e riscritture di query.

Infine, scegli un obiettivo che puoi dire ad alta voce. Esempio: "La maggior parte delle richieste resta sotto 200 ms p95 con 50 utenti concorrenti e gli errori sotto lo 0,5%." I numeri esatti dipendono dal prodotto, ma un obiettivo chiaro evita tentativi infiniti.

Pool di connessioni che mantiene Postgres stabile

Un connection pool mantiene un numero limitato di connessioni aperte al DB e le riusa. Senza pool, ogni richiesta può aprire una nuova connessione e Postgres spreca tempo e memoria a gestire sessioni invece di eseguire query.

L'obiettivo è tenere Postgres occupato a fare lavoro utile, non a fare context-switching tra troppe connessioni. Questo è spesso il primo miglioramento significativo, specialmente per API generate che possono diventare silenziosamente chatter.

Impostazioni iniziali semplici

In Go si regolano solitamente max open connections, max idle connections e la durata delle connessioni. Un punto di partenza sicuro per molte piccole API è un piccolo multiplo dei core CPU (spesso 5–20 connessioni totali), con un numero simile tenuto idle, e riciclo delle connessioni periodico (ad esempio ogni 30–60 minuti).

Se esegui più istanze dell'API, ricorda che il pool si moltiplica. Un pool di 20 connessioni su 10 istanze sono 200 connessioni che colpiscono Postgres, ed è così che i team si scontrano inaspettatamente con i limiti di connessione.

Come capire se il pool è il problema

I problemi di pool si sentono diversi dalla SQL lenta.

Se il pool è troppo piccolo, le richieste aspettano prima ancora di raggiungere Postgres. Si vedono picchi di latenza, ma CPU del DB e tempi di query possono sembrare normali.

Se il pool è troppo grande, Postgres sembra sovraccarico: molte sessioni attive, pressione sulla memoria e latenza disomogenea tra gli endpoint.

Un modo veloce per separare i due è cronometrare le chiamate al DB in due parti: tempo speso ad aspettare una connessione vs tempo speso ad eseguire la query. Se la maggior parte del tempo è "in attesa", il pool è il collo di bottiglia. Se la maggior parte è "in query", concentra gli sforzi su SQL e indici.

Controlli rapidi utili:

  • Logga le statistiche del pool (open, in-use, idle) e osserva se in-use resta bloccato al massimo.
  • Aggiungi un timeout sull'acquisizione di una connessione così le attese falliscono velocemente in staging.
  • Monitora le connessioni attive in Postgres e quanto sei vicino a max_connections.
  • Conferma che ogni richiesta chiude le rows e rilascia le connessioni prontamente.
  • Testa il carico con lo stesso numero di istanze app che prevedi di eseguire.

pgxpool vs database/sql

Se usi pgxpool, ottieni un pool pensato per Postgres con statistiche chiare e buone impostazioni di default. Se usi database/sql, ottieni un'interfaccia standard che funziona su più DB, ma devi essere esplicito sulle impostazioni del pool e sul comportamento del driver.

Una regola pratica: se sei tutto-in su Postgres e vuoi controllo diretto, pgxpool è spesso più semplice. Se dipendi da librerie che si aspettano database/sql, resta con esso, imposta il pool esplicitamente e misura le attese.

Esempio: un endpoint che lista ordini potrebbe impiegare 20 ms, ma sotto 100 utenti concorrenti salta a 2 s. Se i log mostrano 1.9 s in attesa di una connessione, l'ottimizzazione delle query non aiuterà finché pool e connessioni totali su Postgres non sono dimensionati correttamente.

Pianificazione delle query: leggere in fretta l'output di EXPLAIN

Quando un endpoint sembra lento, controlla cosa fa effettivamente Postgres. Una lettura rapida di EXPLAIN spesso indica la soluzione in pochi minuti.

Esegui questo sulla SQL esatta che invia la tua API:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, created_at
FROM orders
WHERE user_id = $1 AND status = $2
ORDER BY created_at DESC
LIMIT 50;

Alcune righe contano più di altre. Guarda il nodo in cima (quello scelto da Postgres) e i totali in fondo (quanto ha impiegato). Poi confronta righe stimate vs reali. Grossi scarti di solito significano che il planner ha sbagliato.

Cosa significano le righe chiave

Se vedi Index Scan o Index Only Scan, Postgres sta usando un indice, che di solito è positivo. Bitmap Heap Scan può andare bene per match di dimensioni medie. Seq Scan significa che ha letto l'intera tabella, accettabile solo quando la tabella è piccola o quasi tutte le righe corrispondono.

Segnali d'allarme comuni:

  • Seq Scan su una tabella grande
  • Stime di righe lontane dalle righe reali (es. stimato 10 vs reale 10.000)
  • Sort che prende la maggior parte del tempo (spesso con ORDER BY)
  • "Filter:" che rimuove molte righe dopo una scansione
  • Molti shared read blocks in BUFFERS (molti dati letti)

Perché i piani sbagliano (e fix facili)

I piani lenti vengono quasi sempre da pochi pattern:

  • Indice mancante per il pattern WHERE + ORDER BY (es. (user_id, status, created_at))
  • Tipi non corrispondenti (es. confrontare una colonna UUID con un parametro text), che può impedire l'uso dell'indice
  • Funzioni in WHERE (es. WHERE lower(email) = $1), che costringono spesso a scansioni a meno che non aggiungi un indice sull'espressione corrispondente

Se il piano sembra strano e le stime sono molto sbagliate, spesso le statistiche sono obsolete. Esegui ANALYZE (o lascia che autovacuum faccia il suo lavoro) così Postgres apprende i conteggi e la distribuzione dei valori correnti. Questo è importante dopo grandi importazioni o quando nuovi endpoint scrivono molte righe rapidamente.

Indicizzazione per le query che realmente esegui

Possiedi il codice sorgente
Esporta il sorgente completo per eseguire pprof, aggiungere metriche e verificare l'output di EXPLAIN localmente.
Esporta codice

Gli indici aiutano solo quando corrispondono a come interroghi i dati. Se li crei a intuito, ottieni scritture più lente, storage maggiore e poco o nessun miglioramento.

Un modo pratico per pensarci: un indice è una scorciatoia per una domanda specifica. Se la tua API fa una domanda diversa, Postgres ignora la scorciatoia.

Costruisci indici attorno a filtri + ordinamento

Se un endpoint filtra per account_id e ordina per created_at DESC, un singolo indice composto spesso batte due indici separati. Aiuta Postgres a trovare le righe giuste e restituirle già ordinate con meno lavoro.

Regole empiriche che tengono spesso:

  • Indicizza le colonne su cui filtri di più, poi aggiungi la colonna di ordinamento.
  • Mantieni gli indici composti piccoli. Due colonne sono comuni; tre a volte vanno bene; più è spesso un campanello d'allarme.
  • Metti per primo il filtro più selettivo (quello che restringe maggiormente i risultati).
  • Evita indici separati che sono completamente coperti da un indice composto migliore.
  • Preferisci un buon indice ben scelto a diversi indici “forse utili”.

Esempio: se la tua API ha GET /orders?status=paid e mostra sempre i più recenti, un indice come (status, created_at DESC) è una buona scelta. Se la maggior parte delle query filtra anche per customer, (customer_id, status, created_at) può essere migliore, ma solo se è così che l'endpoint viene effettivamente usato in produzione.

Indici parziali per filtri comuni

Se la maggior parte del traffico colpisce una fetta ristretta di righe, un indice parziale può essere più economico e veloce. Per esempio, se l'app legge per lo più record attivi, indicizzare solo WHERE active = true mantiene l'indice più piccolo e più probabile che resti in memoria.

Per confermare che un indice aiuta, fai controlli rapidi:

  • Esegui EXPLAIN (o EXPLAIN ANALYZE in un ambiente sicuro) e cerca uno index scan che corrisponda alla tua query.
  • Confronta tempi e righe lette con e senza indice.
  • Guarda se "Rows Removed by Filter" resta alto. Spesso significa che l'indice non corrisponde al filtro.

Rimuovi indici inutilizzati con attenzione. Controlla le statistiche d'uso (ad esempio se un indice è stato scansionato). Elimina uno alla volta in finestre a basso rischio e tieni un piano di rollback. Gli indici inutilizzati non sono innocui: rallentano insert e update a ogni scrittura.

Pattern di paginazione che non rallentano nel tempo

La paginazione è spesso dove una API veloce inizia a sembrare lenta, anche quando il DB è sano. Tratta la paginazione come un problema di progettazione della query, non come un dettaglio UI.

Perché LIMIT/OFFSET peggiora

LIMIT/OFFSET sembra semplice, ma le pagine più profonde costano di più. Postgres deve comunque scorrere le righe da saltare (e spesso ordinarle). La pagina 1 può toccare poche dozzine di righe. La pagina 500 può costringere il DB a scansionare e scartare decine di migliaia di righe solo per restituire 20 risultati.

Può anche creare risultati instabili quando righe vengono inserite o cancellate tra richieste. Gli utenti possono vedere duplicati o perdere elementi perché il significato di "riga 10.000" cambia con le modifiche alla tabella.

Paginazione keyset (cursore) con esempio "ultimo visto"

La paginazione keyset pone una domanda diversa: "Dammi le prossime 20 righe dopo l'ultima che ho visto." Così il database lavora su una fetta piccola e consistente.

Una versione semplice usa un id incrementale:

SELECT id, created_at, title
FROM posts
WHERE id > $1
ORDER BY id
LIMIT 20;

La tua API restituisce un next_cursor pari all'ultimo id nella pagina. La richiesta successiva usa quel valore come $1.

Per ordinamenti basati sul tempo, usa un ordine stabile e risolvi i pareggi. created_at da solo non è sufficiente se due righe condividono lo stesso timestamp. Usa un cursore composto:

WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Alcune regole per evitare duplicati e elementi mancanti:

  • Includi sempre un tie-breaker unico in ORDER BY (di solito id).
  • Mantieni l'ordine identico tra le richieste.
  • Rendi il cursore opaco per i client (codifica created_at e id insieme).
  • Se gli utenti possono filtrare, applica gli stessi filtri in ogni pagina.
  • Preferisci campi di ordinamento immutabili (tempo di creazione) rispetto a quelli mutabili (status, score) quando possibile.

Modellazione JSON: risposte più veloci con payload più piccoli

Distribuisci e usa un dominio
Ospita la tua app e collega un dominio personalizzato quando sei pronto a condividerla.
Pubblica app

Un motivo sorprendentemente comune per cui un'API sembra lenta non è il database ma la risposta. Grandi JSON richiedono più tempo a costruirsi, più tempo a essere inviati e più tempo a essere parsati dai client. Il guadagno più rapido è spesso restituire meno.

Inizia dal tuo SELECT. Se un endpoint ha bisogno solo di id, name e status, chiedi solo quelle colonne. SELECT * si appesantisce silenziosamente con il tempo man mano che le tabelle guadagnano testo lungo, blob JSON e colonne di audit.

Un altro rallentamento frequente è la costruzione della risposta N+1: recuperi una lista di 50 elementi e poi esegui 50 query in più per attaccare dati correlati. Può passare i test e poi crollare sotto traffico reale. Preferisci una singola query che restituisca ciò che ti serve (join attenti) o due query dove la seconda batcha per ID.

Alcuni modi per mantenere i payload piccoli senza rompere i client:

  • Usa un flag include= (o una maschera fields=) così le liste restano snelle e le risposte di dettaglio optano per i campi extra.
  • Limita gli array annidati (ad esempio, solo gli ultimi 10 eventi) e fornisci un endpoint separato per la storia completa.
  • Non restituire colonne JSON interne se i client hanno bisogno solo di poche chiavi.
  • Usa codici brevi invece di ripetere etichette lunghe.

Costruire JSON in Postgres o in Go?

Entrambi possono essere veloci. Scegli in base a cosa stai ottimizzando.

Le funzioni JSON di Postgres (jsonb_build_object, json_agg) sono utili quando vuoi meno round-trip e forme prevedibili da una singola query. Modellare in Go è utile quando ti serve logica condizionale, riuso di struct o mantenere SQL più leggibile. Se il SQL per costruire JSON diventa difficile da leggere, diventa difficile anche da ottimizzare.

Una buona regola: lascia a Postgres filtrare, ordinare e aggregare. Poi lascia a Go la presentazione finale.

Se stai generando API rapidamente (ad esempio con Koder.ai), aggiungere flag include presto aiuta a evitare endpoint che si gonfiano col tempo. Inoltre ti dà un modo sicuro per aggiungere campi senza appesantire ogni risposta.

Una passata di tuning passo-passo prima dei primi utenti

Non ti serve un enorme laboratorio di test per intercettare la maggior parte dei problemi di performance. Una breve passata ripetibile fa emergere i problemi che si trasformano in outage quando arriva il traffico, specialmente se il punto di partenza è codice generato che intendi spedire.

Prima di cambiare qualcosa, annota un piccolo baseline:

  • p95 e p99 di latenza per i tuoi endpoint più trafficati
  • tasso di errori e timeout
  • CPU del database e connessioni attive
  • le 5 query più lente per tempo totale (non solo la singola peggiore)

La passata di tuning

Inizia in piccolo, cambia una cosa alla volta e ritesta dopo ogni modifica.

  1. Esegui un test di carico di 10–15 minuti che somigli al traffico reale. Colpisci gli stessi endpoint che avranno i primi utenti (login, liste, ricerca, create). Poi ordina le route per p95 e tempo totale.

  2. Controlla la pressione sulle connessioni prima di ottimizzare la SQL. Un pool troppo grande sovraccarica Postgres. Un pool troppo piccolo crea attese lunghe. Cerca tempo di attesa per ottenere una connessione e conteggi di connessioni che schizzano durante le raffiche. Regola i limiti del pool e gli idle prima, poi ri-esegui lo stesso test.

  3. EXPLAIN sulle query più lente e risolvi il problema più evidente. I colpevoli usuali sono scansioni su tabelle grandi, sort su insiemi di risultato grandi e join che esplodono i conteggi di righe. Scegli la query peggiore e rendila banale.

  4. Aggiungi o adatta un indice, poi ri-testa. Gli indici aiutano quando corrispondono a WHERE e ORDER BY. Non aggiungere cinque indici in una volta. Se l'endpoint lento è "lista ordini per user_id ordinati per created_at", un indice composto su (user_id, created_at) può fare la differenza tra istantaneo e doloroso.

  5. Snellisci risposte e paginazione, poi ri-testa di nuovo. Se un endpoint restituisce 50 righe con grandi blob JSON, DB, rete e client ne pagano il conto. Restituisci solo i campi necessari all'interfaccia e preferisci paginazione che non rallenti con la crescita delle tabelle.

Tieni un semplice changelog: cosa è cambiato, perché e cosa è migliorato in p95. Se una modifica non migliora la baseline, fai revert e vai avanti.

Errori comuni e trappole da evitare

Ottimizza gli endpoint tramite chat
Itera sugli endpoint lenti descrivendo il problema e raffinando query e handler in chat.
Prova Koder.ai

La maggior parte dei problemi di performance in API Go su Postgres è auto-inflitta. La buona notizia è che pochi controlli intercettano molti problemi prima che arrivi il traffico reale.

Una trappola classica è trattare la dimensione del pool come una manopola di velocità. Impostarlo "più grande possibile" spesso peggiora tutto. Postgres passa più tempo a gestire sessioni, memoria e lock, e la tua app inizia a fare timeout a ondate. Un pool più piccolo e stabile con concorrenza prevedibile spesso vince.

Un altro errore comune è "indicizzare tutto". Indici extra possono aiutare le letture, ma rallentano anche le scritture e possono cambiare i piani di query in modi sorprendenti. Se la tua API inserisce o aggiorna frequentemente, ogni indice in più aggiunge lavoro. Misura prima e dopo e ricontrolla i piani dopo aver aggiunto un indice.

Il debito di paginazione si insinua silenziosamente. La paginazione offset sembra ok all'inizio, poi la p95 aumenta con il tempo perché il DB deve scorrere sempre più righe.

La dimensione dei payload JSON è un'altra tassa nascosta. La compressione aiuta la banda, ma non rimuove il costo di costruire, allocare e parsare grandi oggetti. Riduci campi, evita annidamenti profondi e restituisci solo ciò che serve alla schermata.

Se guardi solo la latenza media, ti perdi dove il vero dolore utente inizia. p95 (e talvolta p99) è dove saturazione del pool, attese di lock e piani lenti si mostrano per primi.

Un rapido self-check pre-lancio:

  • Osserva tempo di wait del pool e conteggio connessioni Postgres durante un piccolo test di carico.
  • Confronta media vs p95 per lo stesso endpoint.
  • Verifica che la paginazione non degradi quando la tabella è 10x più grande.
  • Ispeziona le dimensioni delle risposte per gli endpoint di lista (i byte contano).
  • Riesegui EXPLAIN dopo aver aggiunto indici o cambiato filtri.

Checklist rapida e prossimi passi prima del lancio

Prima che arrivino utenti reali, vuoi prove che la tua API resti prevedibile sotto stress. L'obiettivo non è la perfezione, ma intercettare i problemi che causano timeout, picchi o un database che smette di accettare lavoro.

Esegui i controlli in uno staging che somigli a produzione (dimensione DB simile, stessi indici, stesse impostazioni di pool): misura p95 per endpoint chiave sotto carico, cattura le query più lente per tempo totale, osserva il tempo di wait del pool, esegui EXPLAIN (ANALYZE, BUFFERS) sulla query peggiore per confermare che usa l'indice atteso e verifica le dimensioni dei payload sulle route più usate.

Poi fai una prova worst-case che imiti come i prodotti si rompono: richiedi una pagina profonda, applica il filtro più ampio e prova da cold start (riavvia l'API e colpisci la stessa richiesta per prima). Se la paginazione profonda rallenta ad ogni pagina, passa a cursor-based prima del lancio.

Annota i tuoi default così il team prenda decisioni coerenti in futuro: limiti e timeout del pool, regole di paginazione (max page size, se offset è permesso, formato del cursore), regole di query (seleziona solo le colonne necessarie, evita SELECT *, limita filtri costosi) e regole di logging (soglia query lente, durata di conservazione dei campioni, come etichettare gli endpoint).

Se generi ed esporti servizi Go + Postgres con Koder.ai, fare una breve pianificazione prima del deploy aiuta a mantenere filtri, paginazione e forme di risposta intenzionali. Una volta che inizi a sintonizzare indici e forme di query, snapshot e rollback rendono più facile annullare un "fix" che aiuta un endpoint ma danneggia altri. Se vuoi un posto unico per iterare su quel workflow, Koder.ai su koder.ai è progettato per generare e rifinire quei servizi tramite chat, poi esportare il sorgente quando sei pronto.

Domande frequenti

Come capisco velocemente se la mia API Go è lenta per Postgres o per il codice?

Inizia separando il tempo di attesa DB dal tempo di lavoro dell'app.

  • Se il database è lento, l'handler resta per lo più in attesa della query. La CPU di Go spesso resta normale mentre le richieste si accumulano “in volo”.
  • Se l'app è lenta, le query tornano subito ma si perde tempo costruendo oggetti, eseguendo query extra per riga, serializzando grandi JSON o facendo logging. CPU e memoria di Go salgono con la dimensione della risposta.

Aggiungi misurazioni semplici attorno a “attesa connessione” e “esecuzione query” per vedere quale lato domina.

Quali metriche devo tracciare prima di ottimizzare?

Usa un piccolo baseline ripetibile:

  • p95 di latenza per endpoint chiave (non la media)
  • tasso di errori (5xx, timeout, cancellazioni)
  • tempo DB per richiesta (tempo passato in attesa di Postgres)

Scegli un obiettivo chiaro come “p95 sotto 200 ms con 50 utenti concorrenti, errori sotto 0.5%”. Cambia una cosa alla volta e ri-testa lo stesso mix di richieste.

Devo attivare il logging delle query lente di Postgres e quale soglia è pratica?

Attiva il logging delle query lente con una soglia bassa durante i test pre-lancio (ad esempio 100–200 ms) e registra l'intera istruzione così da poterla copiare in un client SQL.

Mantienilo temporaneo:

  • Diventa rapidamente rumoroso in produzione.
  • Può aggiungere overhead se registri troppo.

Dopo aver trovato i peggiori, passa al campionamento o aumenta la soglia.

Quali impostazioni iniziali del pool sono buone per una API Go su Postgres?

Un default pratico è un piccolo multiplo dei core CPU per istanza API, spesso 5–20 connessioni aperte massime, con un numero simile di connessioni inattive e riciclo delle connessioni ogni 30–60 minuti.

Due modalità di errore comuni:

  • Pool troppo piccolo: le richieste aspettano per ottenere una connessione anche se i tempi di query sono buoni.
  • Pool troppo grande: Postgres si sovraccarica con troppe sessioni attive e la latenza diventa irregolare.

Ricorda che i pool si moltiplicano tra le istanze (20 connessioni × 10 istanze = 200 connessioni).

Come posso confermare che il pool è il collo di bottiglia (non la SQL)?

Temporizza le chiamate DB in due parti:

  • Tempo in attesa della connessione (wait del pool)
  • Tempo di esecuzione della query (lavoro Postgres)

Se la maggior parte del tempo è wait del pool, ridimensiona pool, timeout e numero di istanze. Se la maggior parte è esecuzione query, concentrati su EXPLAIN e indici.

Conferma inoltre che chiudi sempre le rows prontamente così le connessioni tornano al pool.

Cosa dovrei cercare prima in EXPLAIN quando un endpoint è lento?

Esegui EXPLAIN (ANALYZE, BUFFERS) sulla SQL esatta che invia la tua API e cerca:

  • Seq Scan su una tabella grande
  • Grande divario tra righe stimate vs reali
  • che domina il tempo (spesso con )
Come scelgo l'indice giusto per un endpoint di lista con filtri e ordinamento?

Gli indici devono corrispondere a quello che l'endpoint effettivamente fa: filtri + ordine.

Approccio pratico:

  • Crea un index composto per il pattern WHERE + ORDER BY comune.
  • Tienilo piccolo (2 colonne spesso, 3 a volte).
Quando vale la pena usare un indice parziale in Postgres?

Usa un indice parziale quando la maggior parte del traffico colpisce una porzione prevedibile di righe.

Esempio:

  • Molte letture solo per active = true
  • Poche query per le righe inattive

Un indice parziale come ... WHERE active = true rimane più piccolo, ha più probabilità di stare in memoria e riduce l'overhead sulle scritture rispetto a indicizzare tutto.

Conferma con che Postgres lo usa per le tue query ad alto traffico.

Perché la paginazione LIMIT/OFFSET si rallenta nel tempo e cosa usare invece?

LIMIT/OFFSET peggiora sulle pagine profonde perché Postgres deve comunque scorrere (e spesso ordinare) le righe saltate. La pagina 1 può toccare poche decine di righe; la pagina 500 può costare molto di più.

Preferisci la paginazione keyset (a cursore):

Le mie query DB sono veloci, ma le risposte sono ancora lente—dovrei ridurre i payload JSON?

Di solito sì per gli endpoint di lista. La risposta più veloce è quella che non invii.

Guadagni pratici:

  • Seleziona solo le colonne necessarie (evita SELECT *).
  • Aggiungi include= o così i client optano per i campi pesanti.
Indice
Come si manifesta la "lentezza" per API Go su PostgresBaseline prima di tutto: i numeri che contanoPool di connessioni che mantiene Postgres stabilePianificazione delle query: leggere in fretta l'output di EXPLAINIndicizzazione per le query che realmente eseguiPattern di paginazione che non rallentano nel tempoModellazione JSON: risposte più veloci con payload più piccoliUna passata di tuning passo-passo prima dei primi utentiErrori comuni e trappole da evitareChecklist rapida e prossimi passi prima del lancioDomande frequenti
Condividi
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo
Sort
ORDER BY
  • “Rows Removed by Filter” molto alto
  • Molti blocchi letti condivisi in BUFFERS (letture pesanti)
  • Fissa prima il problema più evidente; non ottimizzare tutto insieme.

  • Metti prima il filtro più selettivo, poi la colonna di ordinamento.
  • Esempio: se filtri per user_id e ordini per i più recenti, un indice come (user_id, created_at DESC) spesso risolve i picchi di p95.

    EXPLAIN
  • Usa un ordinamento stabile più un tie-breaker unico (di solito id).
  • Mantieni l'ORDER BY identico tra le richieste.
  • Codifica (created_at, id) o simili nel cursore.
  • Così il costo di ogni pagina resta approssimativamente costante con la crescita della tabella.

    fields=
  • Limita gli array annidati (es. ultimi 10 elementi) e recupera il resto tramite endpoint dedicati.
  • Evita pattern N+1 (50 righe + 50 query extra). Usa join o query batch.
  • Spesso ridurrai CPU di Go, pressione della memoria e latenza di coda semplicemente riducendo i payload.