Introduzione
Quando si parla di performance in PHP, molti sviluppatori pensano subito a cache, OPcache o ottimizzazione delle query SQL. Sono temi fondamentali, ma c’è un altro aspetto spesso sottovalutato: il costo delle operazioni ripetute.
In molte applicazioni reali, il problema non è una singola funzione “pesante”, ma l’esecuzione di tante piccole operazioni una dopo l’altra: chiamate a servizi esterni, scritture su file, aggiornamenti di database, invio di email, log, notifiche, elaborazioni di record. Se queste attività vengono eseguite una alla volta, il tempo totale cresce rapidamente.
Una tecnica molto efficace per migliorare le prestazioni è il batching, cioè il raggruppamento di più operazioni in un’unica esecuzione. In pratica, invece di fare 100 chiamate separate, ne fai 1 o poche chiamate più grandi e meglio organizzate.
Questo tutorial ti mostra come applicare il batching in PHP in modo pratico, con un esempio reale: l’importazione di utenti in blocco. Vedrai come passare da un approccio ingenuo e lento a una soluzione più efficiente, robusta e facile da mantenere.
Codice completo
<?php
declare(strict_types=1);
/**
* Esempio: importazione utenti con batching.
* Obiettivo: ridurre il costo delle operazioni ripetute.
*/
class UserRepository
{
public function insertMany(array $users): void
{
// Simulazione di un´operazione batch verso il database.
// In un progetto reale qui useresti una INSERT multipla o una query preparata ottimizzata.
echo "Inseriti " . count($users) . " utenti in batchn";
}
}
class EmailService
{
public function sendWelcomeBatch(array $emails): void
{
// Simulazione invio email raggruppato.
echo "Inviate " . count($emails) . " email di benvenuto in batchn";
}
}
class UserImporter
{
private UserRepository $repository;
private EmailService $emailService;
private int $batchSize;
public function __construct(UserRepository $repository, EmailService $emailService, int $batchSize = 100)
{
$this->repository = $repository;
$this->emailService = $emailService;
$this->batchSize = $batchSize;
}
public function import(array $rows): void
{
$userBatch = [];
$emailBatch = [];
foreach ($rows as $row) {
$user = $this->mapRowToUser($row);
// Valida il record prima di accumularlo.
if (!$this->isValidUser($user)) {
continue;
}
$userBatch[] = $user;
$emailBatch[] = $user[´email´];
// Quando il batch raggiunge la dimensione impostata, eseguiamo le operazioni.
if (count($userBatch) >= $this->batchSize) {
$this->flush($userBatch, $emailBatch);
$userBatch = [];
$emailBatch = [];
}
}
// Gestione dell´ultimo batch incompleto.
if (!empty($userBatch)) {
$this->flush($userBatch, $emailBatch);
}
}
private function flush(array $users, array $emails): void
{
// Una sola operazione per il database invece di tante singole insert.
$this->repository->insertMany($users);
// Una sola operazione per il servizio email invece di invii isolati.
$this->emailService->sendWelcomeBatch($emails);
}
private function mapRowToUser(array $row): array
{
return [
´name´ => trim((string)($row[´name´] ?? ´´)),
´email´ => strtolower(trim((string)($row[´email´] ?? ´´))),
´created_at´ => date(´Y-m-d H:i:s´),
];
}
private function isValidUser(array $user): bool
{
return $user[´name´] !== ´´ && filter_var($user[´email´], FILTER_VALIDATE_EMAIL) !== false;
}
}
// --------------------
// ESEMPIO DI UTILIZZO
// --------------------
$rows = [
[´name´ => ´Mario Rossi´, ´email´ => ´[email protected]´],
[´name´ => ´ Lucia Bianchi ´, ´email´ => ´[email protected]´],
[´name´ => ´Nome non valido´, ´email´ => ´email-non-valida´],
// ... migliaia di righe
];
$importer = new UserImporter(
new UserRepository(),
new EmailService(),
2 // batch piccolo solo per mostrare il comportamento
);
$importer->import($rows); Spiegazione
Il principio alla base del batching è semplice: ridurre il numero di operazioni costose. In PHP, alcune azioni hanno un overhead significativo rispetto al semplice calcolo in memoria. Tra le più comuni troviamo:
- accessi al database;
- chiamate HTTP verso API esterne;
- invio di email;
- scritture su file o log;
- serializzazione e deserializzazione ripetuta;
- operazioni su filesystem in loop molto lunghi.
Nel codice di esempio, il metodo import() legge un array di righe, le trasforma in utenti e le accumula in due batch: uno per gli utenti e uno per le email. Quando il batch raggiunge la dimensione impostata, il metodo flush() esegue le operazioni in blocco.
Perché è più veloce?
Ogni chiamata verso un database o un servizio esterno ha un costo fisso: apertura della connessione, parsing della richiesta, latenza di rete, gestione della risposta. Se fai 100 operazioni singole, paghi quel costo 100 volte. Se le raggruppi, lo paghi una sola volta o comunque molte meno volte.
Nel caso di un database, per esempio, usare una INSERT multipla o una strategia di scrittura batch è spesso molto più efficiente di 100 INSERT separate. Lo stesso vale per l’invio di email: se il sistema lo consente, è meglio preparare i destinatari in gruppi e inviare in modo controllato.
Il ruolo della dimensione del batch
La variabile $batchSize è importante. Se il batch è troppo piccolo, non ottieni un vantaggio significativo. Se è troppo grande, rischi di consumare troppa memoria o di creare richieste troppo pesanti.
In pratica, il valore giusto dipende dal contesto:
- per operazioni su database, spesso funzionano bene batch da 100 a 1000 record;
- per API esterne, conviene rispettare i limiti del provider;
- per file e log, la dimensione dipende dalla frequenza di scrittura e dalla latenza accettabile.
Validazione prima del flush
Nel nostro esempio la validazione avviene prima di aggiungere il record al batch. Questo è utile perché evita di sprecare spazio e tempo su dati già scartabili. In un sistema reale, potresti anche raccogliere gli errori in una lista separata, così da mostrare un report finale all’utente.
Batching e manutenzione del codice
Un vantaggio spesso ignorato è che il batching rende il codice più ordinato. Invece di avere logica sparsa in un ciclo con tante chiamate ripetute, centralizzi l’esecuzione in un unico punto. Questo facilita test, debug e ottimizzazioni future.
Best practice
- Non ottimizzare alla cieca: misura prima il problema. Se le operazioni lente sono poche, il batching potrebbe non portare benefici reali.
- Scegli un batch size ragionevole: evita batch troppo grandi che aumentano memoria e latenza.
- Gestisci l’ultimo batch: spesso gli errori nascono proprio dai dati residui rimasti nel buffer.
- Valida i dati prima di accumularli: non riempire i batch con record inutilizzabili.
- Separare preparazione ed esecuzione: raccogli i dati in una fase e inviali in blocco in un’altra.
- Prevedi il recupero dagli errori: se un batch fallisce, devi sapere come riprovare o come segnalare il problema.
- Usa il batching anche per operazioni non database: file, API, notifiche e log possono trarne vantaggio.
Un’altra buona pratica è evitare di fare operazioni I/O dentro funzioni chiamate migliaia di volte. Se una funzione viene eseguita in un loop, chiediti sempre: posso accumulare prima e scrivere dopo?
Infine, se il sistema cresce molto, valuta la possibilità di spostare le operazioni pesanti in una coda asincrona. Il batching può essere il primo passo verso un’architettura più scalabile.
Riepilogo
Il batching è una tecnica semplice ma molto efficace per migliorare le performance in PHP. Funziona bene quando hai molte operazioni ripetute e costose, come inserimenti nel database, invio di email o chiamate a servizi esterni.
Il vantaggio principale è la riduzione dell’overhead: meno chiamate significa meno latenza, meno costi di connessione e spesso meno consumo di risorse. Nel codice visto sopra, abbiamo trasformato un flusso “record per record” in un flusso a blocchi, più efficiente e più pulito.
Se vuoi ottimizzare davvero una applicazione PHP, non limitarti ai micro-ritocchi. Osserva il comportamento reale del sistema e cerca i punti in cui puoi raggruppare il lavoro. Spesso è lì che si ottiene il miglior guadagno prestazionale.
Approfondisci con risorse ufficiali
- PHP Manual - Arrays: per gestire in modo efficiente i batch di dati
- PHP Manual - PDO: utile per eseguire inserimenti multipli e query preparate
- PHP Manual - filter_var(): per la validazione dei dati in ingresso
- PHP Manual - Date and Time: per la gestione dei timestamp nei processi di importazione
- Documentazione del database usato: MySQL, PostgreSQL o altri sistemi offrono spesso strategie specifiche per insert massivi e transazioni
Se vuoi fare un passo in più, prova ad applicare il batching a un caso reale del tuo progetto: importazione CSV, sincronizzazione con API, generazione di report o scrittura di log. È uno dei modi più concreti per vedere un miglioramento immediato nelle performance.
