Oltre le API: Ottenere alte prestazioni da Go su Cloud Run

Un mito architetturale molto comune è che Cloud Run sia solo per API stateless “leggere”, mentre il lavoro “reale” e ad alte prestazioni appartenga a GKE.

Questo malinteso spesso deriva da una configurazione errata piuttosto che da limitazioni della piattaforma. Quando si abbina l’efficiente runtime di Go alla scalabilità serverless di Cloud Run, si può raggiungere un throughput incredibile, a patto di comprendere la “simpatia meccanica” richiesta tra i due.

1. Concorrenza vs. Parallelismo nel Serverless

La maggior parte delle piattaforme serverless (come AWS Lambda) segue un modello di 1 richiesta per istanza. Cloud Run è diverso; permette a un’unica istanza contenitore di gestire più richieste simultanee.

Per uno sviluppatore Go, impostare concurrency = 1 è un enorme spreco di risorse. Lo scheduler di Go è progettato per moltiplicare migliaia di goroutine su pochi thread del sistema operativo. Aumentando la concorrenza (ad esempio a 80 o 100), permetti al tuo binario Go di utilizzare il tempo in cui la CPU è inattiva durante le attese I/O, abbattendo significativamente i costi e migliorando la latenza.

2. La trappola dell’allocazione della CPU (L’effetto “Stutter”)

Mentre Go 1.25+ ora identifica correttamente i limiti di CPU del contenitore per impostare automaticamente GOMAXPROCS, la disponibilità di quella CPU è ancora una scelta di configurazione.

Il problema: Su Cloud Run, se non selezioni “La CPU è sempre allocata”, la CPU del tuo contenitore viene limitata (throttling) a quasi zero quando non sta elaborando attivamente una richiesta.

Anche con uno scheduler perfettamente ottimizzato, le attività in background di Go (come il GC mark-and-sweep o il monitoraggio interno) non possono finire mentre la CPU è limitata. Quando arriva una nuova richiesta, il servizio “balbetta” (stutter) mentre la runtime cerca di rimettersi in pari con le sue attività di manutenzione prima di servire il traffico.

La Soluzione: Se hai un traffico ad alta frequenza o requisiti di latenza P99 severi, alloca sempre la CPU. Questo assicura che il runtime Go possa mantenere la sua salute in background, garantendo una gestione delle richieste molto più fluida.

3. Binari leggeri e latenza di avvio

Se il binario Go è appesantito da dipendenze non utilizzate, l’immagine del contenitore sarà grande. In un ambiente serverless, ogni megabyte si aggiunge alla tua latenza di “Cold Start” (avvio a freddo).

Il Refactoring: Usa build Docker multi-stage e ldflags per rimuovere i simboli di debug. Un binario da 15MB viene scaricato ed eseguito molto più velocemente di uno da 200MB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Dockerfile Pattern (Go 1.26)
FROM golang:1.26-bookworm AS builder
WORKDIR /app
# Pre-copiare go.mod per una migliore memorizzazione nella cache dei layer
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o server .

FROM debian:bookworm-slim
# Aggiunta dei certificati CA per chiamate in uscita sicure
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/server /server
CMD ["/server"]

4. La Strategia del Sidecar

Nel 2026, non consideriamo le nostre istanze Cloud Run come silos isolati. Con i Sidecar, possiamo eseguire proxy locali o cache (come un Redis locale o un proxy Envoy) direttamente nello stesso pod.

Se il tuo servizio Go necessita di recuperare gli stessi dati di configurazione 1.000 volte al secondo, non effettuare 1.000 chiamate di rete a Secret Manager o a un database. Usa un sidecar per la cache locale. Questo riduce i costi di uscita e mantiene il tuo codice Go focalizzato sulla logica di business.

Alte prestazioni su Cloud Run non riguardano solo il codice che scrivi; riguardano come quel codice “respira” all’interno dei limiti del contenitore. Con Go 1.25+ che gestisce le basi dello scheduler per noi, il nostro lavoro da senior si sposta verso un’orchestrazione di livello più alto: scegliere l’allocazione corretta della CPU, sfruttare i sidecar e mantenere i nostri artefatti di distribuzione leggeri.