I programmatori spesso mi pongono la stessa domanda: "La mia API è lenta. Da dove inizio?". Il riflesso è solitamente quello di potenziare il server o raddoppiare la RAM. Questo costa denaro e raramente risolve la causa principale. Nella maggior parte delle applicazioni Laravel, il collo di bottiglia risiede nello strato del database. La sintassi elegante del framework rende facile dimenticare che ogni chiamata Eloquent alla fine diventa SQL, e che l'SQL è spesso dove iniziano i problemi.
Prima di toccare una configurazione del server, analizza le tue query in modo metodico.
Inizia con la diagnostica corretta
Non ottimizzare al buio. Riscrivere le query a caso è pura congettura, e le congetture fanno perdere ore.
Devi trovare le istruzioni che consumano la maggior parte del tempo totale. Monitora la tua applicazione sotto carico reale. Laravel Telescope ti offre una visione chiara di ogni query eseguita durante una richiesta, con i relativi tempi di esecuzione. Laravel Debugbar le mostra nel browser durante lo sviluppo locale, così puoi individuare immediatamente le anomalie. Quando devi intercettare problemi in produzione, abilita il MySQL Slow Query Log. Esso registra le istruzioni che superano una soglia da te definita, il che lo rende ideale per trovare sorprese che non emergono in dataset di piccole dimensioni. Se gestisci qualcosa di più grande, uno strumento di Application Performance Monitoring può correlare gli endpoint HTTP lenti con specifiche chiamate al database.
Mentre esamini i dati, cerca due cose: il tempo di esecuzione assoluto e la frequenza delle chiamate. Una query che impiega quaranta millisecondi sembra innocua, finché non ti rendi conto che viene eseguita duemila volte al minuto. Un report da tre secondi che viene eseguito una volta all'ora potrebbe contare meno di una ricerca da mezzo secondo che viene eseguita su ogni pagina. Risolvi prima i problemi ad alto impatto.
Smetti di chiedere tutto
SELECT * è comodo. È anche costoso. Quando scrivi Model::all() o recuperi un set di risultati senza specificare le colonne, MySQL trascina con sé ogni campo per ogni riga corrispondente. Ciò include grandi campi di testo, blob JSON e qualsiasi altra cosa presente nella tabella. Il set di risultati cresce, l'uso della memoria aumenta e il tempo impiegato per serializzare la risposta si incrementa.
Sii esplicito. Se il tuo controller ha bisogno solo dei campi id, name ed email, richiedi esattamente quelli:
User::select('id', 'name', 'email')->get();
Nel query builder si applica lo stesso principio. Payload più piccoli si spostano più velocemente sulla rete e consumano meno RAM sul server dell'applicazione. Questa è una delle soluzioni più economiche a disposizione, eppure è facile trascurarla perché Laravel rende SELECT * il comportamento predefinito.
Lascia che EXPLAIN guidi le tue modifiche
Non rifattorizzare mai una query lenta senza aver prima eseguito EXPLAIN. In MySQL, la parola chiave EXPLAIN mostra il piano di esecuzione della query. Rivela esattamente come l'ottimizzatore intende trovare i tuoi dati.
Presta attenzione alla colonna type. Se vedi ALL, MySQL sta eseguendo una scansione completa della tabella (full table scan). Ciò significa che sta leggendo ogni riga per soddisfare la tua clausola WHERE. Guarda la colonna key per vedere se l'ottimizzatore sta usando un indice. Poi controlla la colonna Extra. Se noti Using temporary o Using filesort, MySQL sta creando tabelle intermedie o ordinando in memoria perché la tua struttura attuale non riesce a soddisfare la query in modo efficiente.
Esegui EXPLAIN nel tuo client MySQL, o usa uno strumento che ne formatti l'output per te. Una volta visto il piano, saprai se il problema è un indice mancante, una join errata o un predicato che il motore non riesce a ottimizzare. Le congetture diventano superflue.
Indicizza con intenzione
Gli indici sono lo strumento più potente per velocizzare le ricerche, ma funzionano solo quando corrispondono al modo in cui interroghi i dati. Senza l'indice giusto, MySQL scansiona riga per riga. Questo potrebbe sembrare accettabile in fase di sviluppo su una tabella con mille righe, per poi crollare in produzione su una tabella con dieci milioni di righe.
Inizia con indici su singola colonna per i campi che appaiono frequentemente nelle clausole WHERE. Se filtri costantemente per status, aggiungi un indice su status.
Quando una query filtra su più colonne contemporaneamente, passa agli indici composti. L'ordine delle colonne all'interno dell'indice è importante perché MySQL legge gli indici composti da sinistra a destra. Questa è chiamata la regola del prefisso più a sinistra (leftmost prefix rule). Se la tua query cerca per user_id e poi ordina per created_at, un indice composto su (user_id, created_at) aiuta significativamente. Inverti l'ordine e l'ottimizzatore potrebbe non utilizzare affatto l'indice per il filtro.
Non indicizzare ogni colonna. Ogni indice aggiunge un sovraccarico (overhead) alle operazioni di inserimento, aggiornamento e cancellazione, poiché MySQL deve mantenere la struttura. Aggiungili deliberatamente in base ai pattern riscontrati durante la misurazione.
Tieni le funzioni lontane dalle colonne
Questo errore disabilita silenziosamente gli indici. Quando si racchiude una colonna in una funzione all'interno di una clausola WHERE, MySQL non può
