Aggiungere RAM a un database lento è la mossa più cara e la meno informativa. Prima le query. Poi gli indici. La RAM, se serve, viene dopo.
Un database lento si indaga dalle query, non dalla scheda della RAM. Prima si guarda cosa fa il motore: scansioni complete, indici mancanti, lock, N+1. La memoria nasconde il sintomo. Non spiega la causa. Aggiungerla senza una misura è una spesa, non una diagnosi.
In breve: si parte dal slow query log, si legge EXPLAIN, si cercano full table scan e indici assenti, si controllano lock e query ripetute riga per riga. Solo se, a query sistemate, restano attesa di I/O e saturazione del disco, allora il collo è la macchina. La RAM viene dopo. Non prima.
Cosa trovi in questo articolo
Perché la RAM è la mossa sbagliata per prima
Aggiungere memoria a un database è visibile, si fa in un pannello, dà l'impressione di "aver fatto la performance". Il limite è che un buffer più grande fa stare in cache le stesse query sbagliate. Se una SELECT scorre tutta una tabella a ogni hit del catalogo, più RAM rende quella scansione un po' meno dolorosa. Non la toglie. Il giorno in cui i dati crescono, il dolore torna.
La performance del sito, nel cluster Performance, si misura sul campo: quanto impiega la pagina, dove aspetta. Se aspetta il database, il passo successivo non è il listino del VPS. È chiedere al motore quali statement durano, con quale piano di esecuzione, su quali tabelle. Senza quel dato si sta ottimizzando a occhi chiusi, lo stesso errore descritto nella guida sulla performance come prodotto: si insegue un intervento visibile invece della causa.
Slow query log: da dove si comincia
MySQL e MariaDB hanno un log delle query lente. Non è un extra da consulenza. È documentazione ufficiale. Su MySQL si attiva con slow_query_log e si soglia con long_query_time; si può anche registrare le query che non usano indici (log_queries_not_using_indexes). La pagina di riferimento è il Slow Query Log del manuale MySQL. MariaDB ha la propria panoramica, lo Slow Query Log Overview.
Il log risponde a tre domande: quali statement superano la soglia, quanto durano, quante volte. Una query da 2 secondi lanciata due volte al giorno è un problema diverso dalla stessa query lanciata a ogni scheda prodotto. Si ordina per tempo totale, non per il record più lento. Il record fa notizia. Il totale fa coda.
In parallelo si guarda il log applicativo e l'access log: quale URL produce quella query. Il database non sa se sta servendo una home, un cron o un export. Lo sa l'applicazione. I due pezzi si tengono insieme, come nel discorso sui log del sito: una traccia serve se si può ricostruire un incidente, non se sta su disco a prendere spazio.
Indici mancanti e full table scan
Dopo il log, EXPLAIN. MySQL documenta Using EXPLAIN e l'ottimizzazione tramite indici. Si cerca, in particolare:
type ALL: scansione completa della tabella. Il motore legge tutte le righe per filtrare in memoria. Su una tabella piccola passa inosservato. Su un catalogo o una tabella di ordini diventa il collo.
key NULL: nessun indice usato. A volte l'indice c'è e la query non può usarlo (funzione sulla colonna, tipo diverso, LIKE con jolly iniziale). A volte l'indice non c'è.
Extra: Using filesort / Using temporary: ordinamenti e raggruppamenti che non stanno sull'indice. Non sono automaticamente un bug. Lo diventano se arrivano su ogni richiesta web.
rows stimato alto: il piano prevede di toccare troppe righe. Si confronta con la selettività reale.
Un indice non è gratis. Occupà disco, rallenta INSERT e UPDATE, va mantenuto. Si indicizza ciò che si filtra e si join-a nelle query calde, non "tutte le colonne per sicurezza". L'indice sbagliato è rumore: il pianificatore può ignorarlo, o usarlo peggio di una scansione. Si aggiunge dopo EXPLAIN, non prima, e si verifica che il piano sia cambiato.
Le colonne in JOIN e in WHERE delle query del log sono il primo candidato. Le foreign key senza indice sul lato n, i filtri su stato e data senza indice composito, le ricerche su slug e email senza chiave unica: sono il pane quotidiano. Non servono ipotesi sulla RAM per vederli.
N+1, lock, cache applicativa
L'N+1 non è un parametro del motore. È un pattern dell'applicazione, tipico degli ORM: una query per la lista, poi una query per ogni riga (dettaglio, traduzione, prezzo, scorta). In sviluppo, su dieci righe, non si vede. In produzione, su duecento prodotti, diventano duecentouno round-trip. Il slow query log può mostrare duecento statement identici, ciascuno "veloce". Il totale non lo è. Si sistema in codice: JOIN, eager load, una query sola. Non in memoria.
I lock sono un altro capitolo. InnoDB documenta i tipi di lock. Una transazione lunga che tiene un row lock, un UPDATE su troppe righe, un ALTER in orario di traffico, una migrazione che riscrive una tabella calda: le altre sessioni aspettano. L'utente vede timeout. Il grafico della CPU può restare calmo. Si guarda SHOW ENGINE INNODB STATUS, le attese, i deadlock. Si accorciano le transazioni. Si evita di fare lavoro batch sulla stessa riga che sta servendo il checkout.
La cache applicativa, se c'è, va trattata come un acceleratore su dati già corretti, non come toppa su una query sbagliata. Cachare il risultato di un full scan sposta il problema: la prima richiesta resta lenta, l'invalidazione diventa un secondo mestiere. Prima il piano di esecuzione. Poi, se il dato è letto spesso e cambia poco, la cache.
Quando, davvero, è la macchina
Dopo indici, query e lock, restano casi in cui il collo è l'host. Segnali onesti: attesa I/O alta mentre le query già usano indici, disco saturo, swap, buffer pool troppo piccolo rispetto al working set che EXPLAIN mostra come necessario, noisy neighbor su un VPS condiviso. A quel punto ha senso parlare di macchina, di I/O dedicato, di un piano tra i servizi hosting di Servereasy o di un VPS/dedicato con disco misurato, non di "un po' di RAM in più" a caso. Il confronto tra forme di host sta in VPS o server dedicato: isolamento e I/O, non il numero di core sul listino.
Anche qui si misura. IOPS, latenza del volume, percentuale di wait, hit del buffer pool. Senza quei numeri, un upgrade è un atto di fede. Con quei numeri, è una decisione.
Ha senso quando. Non serve quando
Ha senso aggiungere RAM quando il working set delle query già buone non sta in buffer, quando il hit ratio del buffer pool è basso e EXPLAIN non mostra scansioni, quando si è già tolta la coda N+1. Allora la memoria riduce I/O. È il suo mestiere.
Non serve quando la prima riga del slow log è una SELECT senza indice, quando un cron blocca le tabelle diurne, quando l'ORM fa una query per riga, quando "lento" è in realtà un lock. In quei casi la RAM copre, per un po', un errore di progettazione. Poi i dati crescono e il coprire non basta più.
Errori da evitare
Raddoppiare la RAM e dichiarare vittoria. Senza before/after sulle stesse query, non si è imparato nulla. Si è solo spostata la soglia.
Aggiungere indici a occhio su tutte le colonne. Si paga in scrittura e si confonde il pianificatore.
Guardare solo la query più lenta. Vince il tempo totale: frequenza per durata.
Lanciare ANALYZE o ottimizzazioni distruttive in orario di punta. Una manutenzione può essere il lock che si stava cercando altrove.
Cachare il risultato di una query sbagliata. Si ottiene una prima visita lenta e una cache da invalidare. Il piano resta sbagliato.
In sintesi
Un database lento è, nella maggior parte dei siti, una query che legge troppo o un'applicazione che la ripete troppe volte. Il slow query log e EXPLAIN bastano per vederlo. Indici sulle query calde, niente N+1, transazioni corte. La RAM e il disco arrivano dopo, quando il piano di esecuzione è già onesto e l'I/O resta il collo. Prima misura, poi macchina. Il contrario è rumore a pagamento.
Fonti
MySQL, The Slow Query Log (8.4).
MariaDB, Slow Query Log Overview.
MySQL, Optimizing Queries with EXPLAIN.
MySQL, Optimization and Indexes.
MySQL, InnoDB Locking.
Questo contenuto è stato realizzato con il supporto di sistemi di intelligenza artificiale e successivamente verificato secondo le linee editoriali della redazione.