Un approccio sistematico all'ottimizzazione dei costi cloud
L'ottimizzazione dei costi cloud segue il principio di Pareto (80/20): una piccola percentuale delle risorse genera in genere la maggior parte dei costi. Cominci usando strumenti come DoiT Cloud Intelligence™ per individuare le aree di spesa più consistenti.
Fase 2: incassi i risultati amministrativi rapidi
Prima di addentrarsi nelle ottimizzazioni del codice, intervenga sugli aggiustamenti amministrativi più semplici:
- Right-sizing delle istanze sottoutilizzate
- Eliminazione delle risorse orfane
- Adozione dei tier di storage adeguati
- Taratura dei parametri di autoscaling
Sono interventi meno onerosi rispetto al mettere mano al codice.
Fase 3: cerchi gli indicatori di inefficienza a livello di codice
Una volta concluse le ottimizzazioni amministrative, esamini le risorse a costo elevato per individuare possibili inefficienze legate al codice.
A causa dell'"illusione di efficienza", questi indicatori possono essere poco evidenti. Gli scenari reali presentati più avanti in questo articolo illustrano i segnali rivelatori più frequenti, spesso individuabili con i normali strumenti di monitoring cloud nelle Console di GCP, AWS e Azure.
Fase 4: indaghi con profiling e analisi
Dal livello cloud passi al livello del codice tramite strumenti di profiling dell'esecuzione utilizzabili direttamente nel cloud.
- Per i database: query analyzer e performance insights della Cloud Console
- Per le applicazioni: profiler specifici per linguaggio e analizzatori di memoria
- Per le data pipeline: grafi di esecuzione e metriche di distribuzione

Flame Graph di Google Cloud Profiler
L'implementazione può essere semplice — come per SQL, dove gli strumenti sono integrati nel cloud — oppure complessa, come nel caso del profiling della memoria di Python in applicazioni distribuite eseguite in ambienti gestiti.
Fase 5: implementi, misuri, validi
Risolva i problemi individuati, esegua il redeploy e misuri i miglioramenti tecnici tramite le Cloud Console di AWS, GCP e Azure; consulti poi i report dei costi di Cloud Intelligence™ per validare il ritorno economico.
Scenari reali e soluzioni
Scenario 1: microservizio Java con memory leak
Cosa sembrava efficiente: un microservizio Java su Lambda con un utilizzo della memoria del 70-100%, apparentemente sfruttato al massimo nell'allocazione delle risorse.
La realtà: l'applicazione soffriva di memory leak: un oggetto globale tratteneva catene di riferimenti che mantenevano in vita oggetti tra un'invocazione e l'altra. I crash sporadici e le sostituzioni di istanze erano talmente rari da sfuggire all'attenzione del team SRE.
L'indizio: il monitoring evidenziava un andamento della memoria a dente di sega nel tempo. Approfondendo i cali di quel dente di sega, i log di CloudWatch hanno rivelato crash periodici.
L'indagine: è stato attivato CodeGuru Profiler, che ha mostrato un consumo di memoria in costante crescita. Successive analisi offline con un profiler JVM hanno rivelato una ritenzione inattesa di oggetti.
La soluzione: il codice è stato modificato per rilasciare i riferimenti agli oggetti al termine di ogni richiesta web.
Il risultato: utilizzo stabile della memoria, meno sostituzioni di istanze e costi delle risorse più bassi.
Scenario 2: pipeline Java di elaborazione dati con strutture dati inefficienti
Cosa sembrava efficiente: una pipeline Dataflow con container Java personalizzati elaborava milioni di record al giorno mantenendo un utilizzo della CPU costantemente elevato.
La realtà: il codice impiegava strutture dati inefficienti, con mappe e concatenazioni di stringhe superflue per ogni oggetto all'interno di loop serrati, generando un sovraccarico eccessivo di garbage collection.
L'indizio: l'elevato consumo di risorse suggeriva la necessità di un'analisi più approfondita.
L'indagine: è stato integrato GCP Cloud Profiler nel container, che ha mostrato uno scaling super-lineare di tempo e memoria al crescere dei dataset.
La soluzione:
- Sostituzione delle mappe con oggetti personalizzati contenenti solo le informazioni strettamente necessarie.
- Adozione di un corretto string joining al posto delle concatenazioni ripetute.
Il risultato: -50% di utilizzo della memoria e -70% sui tempi di elaborazione, con la possibilità di adottare worker più piccoli e ridurre il numero di istanze.
Scenario 3: strutture dati in memoria che impongono VM sovradimensionate
Cosa sembrava efficiente: VM con grande memoria sembravano economicamente vantaggiose rispetto a soluzioni con scaling orizzontale, e le strutture dati Python in memoria offrivano vantaggi di velocità algoritmica rispetto alle query su database.
La realtà: questo approccio generava molteplici inefficienze:
- I cloud provider impongono rapporti minimi tra CPU e memoria, costringendo a pagare CPU costose e inutilizzate.
- Le allocazioni di memoria avvengono per incrementi predefiniti, obbligando a pagare buffer di capacità non sfruttati.
- I lunghi tempi di inizializzazione imponevano di tenere attive simultaneamente più istanze costose per garantire la robustezza.
L'indizio: DoiT Cloud Intelligence™ mostrava che una quota rilevante della spesa totale derivava da VM ultra-large — di solito un sintomo di gestione problematica dello stato nelle architetture cloud.
L'indagine: un'analisi approfondita degli algoritmi ha rivelato margini di refactoring per spostare la memorizzazione dei dati al di fuori della memoria applicativa.
La soluzione:
- Refactoring degli algoritmi per operare su dataset parziali interrogati dal database all'occorrenza.
- Adozione di un database NoSQL con Redis come cache in memoria.
- Dove era necessario il preload completo dei dati di riferimento principali, le strutture dati ottimizzate in Redis hanno permesso un footprint di memoria inferiore rispetto agli oggetti nella memoria applicativa.
Il risultato: questo cambio architetturale ha ridotto in modo significativo le dimensioni delle VM, abilitando lo scaling orizzontale e abbattendo radicalmente i costi, pur richiedendo un notevole sforzo di engineering.
Riprendiamo i casi precedenti
Torniamo a due ulteriori esempi tratti dall'articolo che ho citato prima, "Stop Chasing Idle Servers", e vediamo come si inseriscono in questo framework.
Scenario 4: database all'85% di IOPS
Cosa sembrava efficiente: l'istanza RDS appariva pienamente sfruttata, lasciando supporre un'allocazione ottimale delle risorse.
La realtà: ogni query eseguiva full-table scan per la mancanza di due indici critici, facendo lievitare il fabbisogno di risorse.
L'indizio: poiché la maggior parte delle query SQL non dovrebbe richiedere un consumo elevato di risorse (salvo nei processi batch finemente ottimizzati), un pattern di utilizzo elevato segnalava opportunità di ottimizzazione.
L'indagine: sono state identificate le query problematiche e gli indici mancanti tramite AWS RDS Performance Optimizer, attivo out-of-the-box nella AWS Console. (GCP offre l'analogo Cloud SQL Query Insights.)
La soluzione: aggiunta degli indici mancanti.
Il risultato: latenza delle query ridotta di 10 volte e possibilità di ridimensionare il database di due tier.
Scenario 5: job Spark al 70% di CPU per quattro ore ogni notte
Cosa sembrava efficiente: il cluster manteneva un utilizzo elevato della CPU, lasciando supporre un'adeguata allocazione delle risorse.
La realtà: l'80% dei dati era concentrato su un'unica chiave sbilanciata, generando task straggler che allungavano sensibilmente i tempi di elaborazione.
L'indizio: il problema è comparso in un momento preciso senza altre cause evidenti. (In seguito si è scoperto che coincideva con l'arrivo di nuovi dati associati alla "hot key".)
L'indagine: il codice Spark viene eseguito in un ambiente altamente distribuito, il che rende difficile utilizzare un profiler tradizionale come quello adottato per le applicazioni. Questo è un buon motivo per mantenere la logica concentrata su trasformazioni semplici anziché su logica di business complessa. Tuttavia, analizzando con la Spark UI la distribuzione dei task tra gli stage sono stati identificati gli straggler. Il monitoring di BigTable ha rivelato la presenza di "hot key" nei dati elaborati.
La soluzione: ripartizionamento e salting della chiave problematica per distribuire i workloads in modo più uniforme.
Il risultato: il tempo di completamento del job è sceso da 4 ore a 45 minuti e la dimensione del cluster necessaria è stata ridotta di due terzi.
Raggiungere una vera efficienza nel cloud richiede a volte di andare oltre le configurazioni cloud e di intervenire anche sulle inefficienze a livello di codice. Quando la causa originaria dei costi cloud eccessivi è il codice, le sole modifiche all'infrastruttura non bastano a risolvere il problema.
Affiancando il suo team di sviluppo a FinOps e SRE può individuare e risolvere queste inefficienze nascoste con un approccio sistematico:
- Parta dalle aree di spesa più rilevanti evidenziate dalle analisi dei costi.
- Affronti per primi i quick win a livello di configurazione cloud.
- Nelle Cloud Console, cerchi gli indizi rivelatori che giustificano un'analisi più approfondita.
- Utilizzi gli strumenti di profiling adeguati — preferibilmente nel cloud, oppure offline se necessario — per individuare le inefficienze.
- Corregga il codice, esegua il redeploy e validi i miglioramenti sui costi.
Questo approccio collaborativo non solo riduce i costi, ma spesso migliora anche prestazioni e affidabilità delle applicazioni: un vantaggio sia per il suo budget sia per i suoi utenti.
Nel team DoiT Customer Reliability Engineering accompagno le organizzazioni lungo l'intero percorso di ottimizzazione. Grazie a DoiT Cloud Intelligence™ e a decenni di esperienza, aiutiamo a individuare i potenziali risparmi, a definire le correzioni a livello cloud, a smascherare le illusioni di efficienza e a validare l'impatto dei miglioramenti a livello di codice. Ci contatti su doit.com/services