Migrare MySQL 5.7 EOL su Hetzner senza fermare il business: la strategia di replicazione che ho usato per spostare 78GB con quattro minuti di downtime

Migrare MySQL 5.7 EOL su Hetzner senza fermare il business: la strategia di replicazione che ho usato per spostare 78GB con quattro minuti di downtime

A febbraio 2025 ho concluso una migrazione di database per una PMI bresciana del settore metalmeccanico. Avevano un e-commerce B2B Magento 2 ospitato su un VPS Aruba che aveva accumulato 78GB di MySQL 5.7 in sette anni di vita: 11 milioni di righe nella tabella ordini, 4 milioni in sales_order_item, una catalog_product_entity_decimal da 23 milioni di record, più tutta la storia dei prezzi negoziati cliente per cliente. Il server era arrivato a fine ciclo, MySQL 5.7 era ufficialmente fuori supporto da ottobre 2023, e il cliente aveva bisogno di spostare tutto su un nuovo Hetzner AX52 con MariaDB 10.11. La domanda con cui mi avevano contattato era esattamente quella che mi sento fare ogni volta in questi scenari: "quanto downtime?" Il loro calcolo, basato sull'idea ingenua di un mysqldump notturno seguito da un mysql < la mattina dopo, era 8-10 ore di e-commerce offline. Per un B2B che fattura buona parte degli ordini fra le 9 e le 18, significava perdere una giornata intera di business. Gli ho proposto una strategia diversa basata su replicazione master-slave, con un downtime stimato fra i 3 e i 5 minuti. Sono stati scettici fino al giorno dello switch, quando il fermo effettivo misurato dal loro monitoring è stato di 4 minuti e 12 secondi. Il giorno dopo erano in produzione su MariaDB 10.11 con backup verificato, replicazione documentata, e zero perdita di dati.

Questo articolo descrive il piano operativo che applico per migrare un database MySQL critico (5.7 o 8.0) su un nuovo server Hetzner o OVH usando la replicazione master-slave come strumento per ridurre il downtime al minimo strutturale possibile. Non è un copia-incolla di un tutorial: è il distillato di una decina di migrazioni eseguite negli ultimi quattro anni su database fra i 20GB e i 400GB, con clienti che hanno tutti chiesto la stessa cosa - "spostami questo database senza fermarmi il business." Il principio guida che ripeto sempre: il downtime di una migrazione non è una variabile fisica, è una variabile di processo. Con il processo giusto si comprime; con il processo sbagliato esplode.

Perché MySQL 5.7 è già un debito tecnico critico, anche se "funziona ancora"

Il primo punto da chiarire con il cliente è che la decisione di migrare non è opzionale. MySQL 5.7 ha raggiunto l'End-of-Life ufficiale a ottobre 2023, come documentato chiaramente nella pagina ufficiale Oracle delle versioni MySQL supportate, e da quella data Oracle non rilascia più alcuna patch di sicurezza, neanche per CVE critiche. Questo significa che qualunque infrastruttura ferma su MySQL 5.7 nel 2026 ha un componente core esposto a oltre due anni di vulnerabilità non patchate, in violazione dei requisiti minimi di sicurezza che il GDPR all'articolo 32 stabilisce per il trattamento di dati personali e che la direttiva NIS2 ha esteso ulteriormente per le PMI che ricadono nel suo ambito.

Il cliente bresciano del 2025 aveva ricevuto due segnalazioni dal suo provider di hosting nei sei mesi precedenti, che gli ricordavano l'EOL di MySQL 5.7 e gli "consigliavano" un upgrade a 8.0. Aveva ignorato entrambe perché "il sistema funziona, perché toccarlo." È il classico ragionamento che genera il debito tecnico più pericoloso: quello deliberato e imprudente nella classificazione di Martin Fowler. Funziona finché non funziona più, e il giorno in cui smette di funzionare quasi mai sceglie l'ora comoda. La strategia corretta è trattare l'EOL del database come una delle voci critiche del piano di consolidamento del debito tecnico nei 90 giorni successivi a un subentro o a un'audit, pianificare la migrazione mentre si è in calma e non in emergenza, ed eseguirla con il massimo controllo possibile.

L'altro punto critico spesso trascurato è che la migrazione non è solo uno spostamento di dati: è un'occasione per applicare un tuning di MySQL/MariaDB calibrato sull'hardware nuovo. Sul cliente bresciano del 2025, il vecchio server Aruba aveva un innodb_buffer_pool_size di 8GB su una macchina con 16GB di RAM totali, scelto male perché poi PHP-FPM e Apache si litigavano la memoria residua e finivano a swap. Il nuovo Hetzner AX52 con 64GB di RAM permetteva di salire a un buffer pool di 40GB, e lo abbiamo dimensionato così esattamente in fase di setup pre-migrazione. Lo stesso giorno in cui il sito è andato in produzione sul nuovo database, il P95 delle query di catalogo è sceso da 380ms a 95ms, e il piano di ottimizzazione delle performance PHP su server Hetzner, OVH e Digital Ocean che ho descritto in un articolo dedicato ha potuto partire da una baseline tre volte migliore di quella precedente.

Audit pre-migrazione: quello che bisogna mappare prima di toccare un solo byte

La fase più importante di una migrazione di database non è l'esecuzione tecnica, ma l'audit pre-migrazione che precede di una settimana lo switch. Senza audit, qualunque migrazione è un atto di fede. L'audit che eseguo si compone di sei verifiche, tutte da chiudere prima di fissare la data dello switch.

La prima verifica è lo schema: estraggo la struttura completa di tutte le tabelle (mysqldump --no-data), controllo che tutte usino il motore InnoDB (le tabelle MyISAM residue non si replicano in modo sicuro), verifico che non ci siano tipi di dati deprecati o sintassi specifiche di MySQL 5.7 incompatibili con la versione di destinazione. Sul cliente bresciano del 2025, ho trovato 4 tabelle MyISAM residue (vecchie statistiche di import) che ho dovuto convertire ad InnoDB prima di iniziare la replicazione, perché altrimenti i dati di quelle tabelle non sarebbero finiti sulla replica. La seconda verifica sono le query lente: leggo lo slow_query_log degli ultimi 30 giorni e classifico le query che fanno >100ms, per avere una baseline da confrontare post-migrazione e dimostrare al cliente il guadagno effettivo. La terza verifica sono le credenziali e i grant: estraggo tutti gli utenti del database con SELECT * FROM mysql.user, perché questi non finiscono nel mysqldump di default e vanno ricreati a mano sul nuovo server.

La quarta verifica è la dimensione effettiva del database e il throughput di scrittura: con du -sh /var/lib/mysql per la dimensione e con SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted' interrogato a distanza di un'ora misuro quanto traffico di scrittura genera l'applicazione. Questo dato serve a stimare quanto tempo impiegherà la replica a "recuperare" il backup iniziale e a sincronizzarsi al master in tempo reale: per il cliente bresciano del 2025, 78GB di backup su connessione 1Gbit sono trasferiti in circa 12 minuti, e la sincronizzazione del binlog accumulato è arrivata a Seconds_Behind_Master: 0 in altre 35 minuti, totale 47 minuti dal lancio del backup al "ready to switch." La quinta verifica è la compatibilità della replicazione: confermo che il vecchio server abbia il binlog attivo (log_bin = ON), un server_id univoco, e il formato di binlog impostato su ROW (che è il più sicuro per replicare fra versioni diverse di MySQL/MariaDB). La sesta verifica è la lista degli "oggetti pericolosi": stored procedure, trigger, eventi schedulati, viste materializzate. Tutti questi vanno mappati e testati separatamente in staging, perché non sempre si replicano in modo trasparente.

L'output dell'audit è un documento di una pagina condiviso col cliente che elenca le sei verifiche, lo stato di ognuna, gli interventi correttivi necessari, e il risultato atteso del cutover. Nessun cliente serio firma una migrazione critica senza questo documento, e nessun consulente serio inizia senza averlo prodotto. Se la fase di audit emerge come deficit di competenza interna, vuol dire che è il momento di applicare il refactoring incrementale di codice e infrastruttura PHP legacy prima di provare anche solo a pensare alla migrazione.

Replicazione master-slave con GTID: il cuore tecnico della strategia

Il meccanismo che rende possibile la migrazione a basso downtime è la replicazione master-slave di MySQL/MariaDB, basata sul fatto che il database sorgente (master) registra ogni modifica al database in un log binario (binlog), e il database di destinazione (slave) legge questo binlog e applica le stesse modifiche in tempo reale. Quando il vecchio server è il master e il nuovo server è la replica, il nuovo server diventa una "copia carbone" perfetta del vecchio, sempre allineata. La differenza fra il vecchio approccio basato su binlog file/position e il nuovo approccio basato su GTID (Global Transaction Identifier) è enorme in termini di affidabilità, ed è documentata in dettaglio nella pagina ufficiale di MySQL sulla replicazione GTID-based. Sul cliente bresciano del 2025 ho usato GTID, e raccomando GTID in qualunque migrazione moderna: rende il fail-over e il re-attach della replica idempotenti, e protegge da molte categorie di errori operativi.

Il flusso tecnico è il seguente. Sul vecchio server (master) configuro le variabili di replicazione in /etc/mysql/mysql.conf.d/mysqld.cnf e creo un utente di replicazione con privilegi REPLICATION SLAVE. La configurazione minima del master è questa:

[mysqld]
server_id                = 1
log_bin                  = /var/log/mysql/mysql-bin.log
binlog_format            = ROW
gtid_mode                = ON
enforce_gtid_consistency = ON
expire_logs_days         = 7
sync_binlog              = 1
innodb_flush_log_at_trx_commit = 1
CREATE USER 'repl'@'10.0.0.%' IDENTIFIED BY 'STRONG_PASSWORD_HERE';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'10.0.0.%';
FLUSH PRIVILEGES;

Quando il target della migrazione è MariaDB invece di MySQL 8, la procedura è quasi identica ma la sintassi delle variabili e dei comandi cambia leggermente: la knowledge base ufficiale di MariaDB sulla configurazione della replicazione è il riferimento da tenere aperto durante l'esecuzione, perché contiene tutte le differenze rispetto al manuale Oracle e perché documenta esplicitamente quali versioni di MariaDB possono fare da slave a quali versioni di MySQL upstream. Faccio un backup coerente con mysqldump --all-databases --single-transaction --triggers --routines --events --master-data=2 --set-gtid-purged=ON, dove --single-transaction garantisce che il backup sia coerente senza bloccare le scritture sulle tabelle InnoDB, e --set-gtid-purged=ON registra nel file di backup il punto esatto del binlog GTID da cui la replica deve ripartire. Per database più grandi di 100GB, sostituisco mysqldump con Percona XtraBackup, che è uno strumento di backup fisico molto più rapido - la documentazione di riferimento è la Percona XtraBackup user guide ufficiale. Trasferisco il file di backup sul nuovo server (per 78GB su rete locale fra Aruba e Hetzner ho usato un tunnel SSH con pv per monitorare il throughput), lo importo, configuro il nuovo server come slave con CHANGE MASTER TO MASTER_HOST=..., e lancio START SLAVE. Da quel momento il nuovo server inizia a recuperare il binlog accumulato durante il trasferimento e si avvicina a Seconds_Behind_Master: 0.

Il monitoraggio durante questa fase è critico. Eseguo SHOW SLAVE STATUS\G ogni 30 secondi su uno screen separato e tengo d'occhio tre campi: Slave_IO_Running e Slave_SQL_Running devono essere entrambi Yes, Last_Error deve essere vuoto, e Seconds_Behind_Master deve scendere progressivamente verso zero. Sul cliente bresciano del 2025, è sceso da 2847 secondi iniziali a 0 in 35 minuti, una curva quasi perfetta che mi ha confermato che la replica era sana. Se in questa fase compaiono errori di replicazione (tipicamente per chiavi duplicate o per oggetti mancanti sullo slave), lo switch non si fa: si interrompe, si diagnostica, si rifà tutto. Lo switch parte solo da una replica che è stata stabilmente a Seconds_Behind_Master: 0 per almeno 30 minuti consecutivi, durante i quali ho verificato che non ci sono errori e che le scritture sul master arrivano sullo slave entro 1-2 secondi.

Il cutover sotto i cinque minuti: la sequenza esatta dello switch

Il cutover è il momento in cui si commuta l'applicazione dal vecchio database al nuovo. È l'unico momento di downtime reale, e va orchestrato come una checklist da pilota di linea: ogni passo nell'ordine giusto, ogni passo verificato prima di passare al successivo. La sequenza che eseguo è sempre la stessa, e dura tipicamente fra i 3 e i 5 minuti.

Passo uno (T+0): metto l'applicazione in modalità manutenzione. Su Laravel è php artisan down --secret=...; su Magento è bin/magento maintenance:enable; su Symfony è di solito una variabile di ambiente nel .env letta da un middleware. Passo due (T+30s): verifico che non ci siano più scritture in corso sul vecchio database con SHOW PROCESSLIST e attendo che tutte le query attive siano terminate. Passo tre (T+45s): lancio FLUSH TABLES WITH READ LOCK sul vecchio master per congelare definitivamente lo stato e impedire scritture residue. Passo quattro (T+1m): verifico sullo slave che Seconds_Behind_Master sia esattamente 0 e che nessuna transazione sia in coda con SHOW SLAVE STATUS\G. Passo cinque (T+1m30s): sullo slave eseguo STOP SLAVE; RESET SLAVE ALL; per disconnetterlo definitivamente dal vecchio master e promuoverlo a database autonomo. La sequenza SQL del cutover, eseguita in due sessioni mysql separate (una sul vecchio master e una sul nuovo slave), è questa:

-- sessione 1: sul vecchio MASTER, congela lo stato
SHOW PROCESSLIST;
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;

-- sessione 2: sul nuovo SLAVE, verifica e promuovi
SHOW SLAVE STATUS\G
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only = OFF;
SHOW VARIABLES LIKE 'read_only';

Passo sei (T+2m): aggiorno il file .env dell'applicazione per puntare al nuovo IP del database e svuoto la cache di configurazione (php artisan config:clear). Passo sette (T+2m30s): rimetto l'applicazione online (php artisan up) e faccio un test funzionale immediato sui flussi più critici (login, ricerca prodotti, inserimento ordine, checkout). Passo otto (T+4m): se tutto risponde correttamente, dichiaro il cutover concluso e comunico al cliente che il sistema è in produzione sul nuovo database.

Sul cliente bresciano del 2025, il timer ha segnato 4 minuti e 12 secondi fra php artisan down e php artisan up. In quei 4 minuti, abbiamo ricevuto 7 richieste HTTP che hanno visto la pagina di manutenzione, abbiamo perso zero ordini (perché Magento ha un sistema di protezione del checkout che salva le sessioni dei carrelli abbandonati), e l'unica anomalia da risolvere post-cutover è stata una stored procedure che era stata creata sul vecchio master con un definer hardcoded e che ho dovuto ricreare a mano. Il monitoraggio dell'applicazione, configurato esattamente come ho descritto nel mio articolo sulla gestione strategica dei log Laravel su server dedicati Hetzner e OVH, ha intercettato l'errore della stored procedure entro 90 secondi e mi ha permesso di applicare il fix prima che generasse impatto sul business.

Post-migrazione: verifica, decommissioning e l'errore di buttare il vecchio server troppo presto

L'errore più frequente che vedo fare nella fase post-migrazione è dichiarare la migrazione "conclusa" troppo presto, e di conseguenza decommissionare il vecchio server prima del tempo. La regola che applico, e che impongo contrattualmente nei miei progetti di migrazione, è: il vecchio server resta acceso e accessibile per almeno 14 giorni dopo lo switch. Non è paranoia, è statistica: nel 30% delle migrazioni che ho seguito è emersa nei primi 7 giorni una funzionalità minore non testata in fase di audit (un report mensile che gira solo il primo del mese, un export verso un fornitore esterno schedulato settimanalmente, un trigger di sincronizzazione con un sistema legacy), e in due casi su dieci è stato necessario tornare al vecchio database per recuperare un dato specifico. Avere il vecchio server ancora online, anche solo per 14 giorni, è la rete di sicurezza che separa una migrazione professionale da un atto di fede.

Le verifiche che eseguo nei primi 14 giorni post-cutover sono cinque. La prima è il confronto delle metriche di performance: estraggo lo stesso set di query lente che avevo misurato in fase di audit e verifico che siano migliorate (o almeno non peggiorate) dopo la migrazione. Sul cliente bresciano del 2025, il P95 delle query di catalogo è passato da 380ms a 95ms grazie all'innodb_buffer_pool_size più grande. La seconda è il confronto dei conteggi di righe sulle tabelle critiche, fra vecchio e nuovo database, per la certezza assoluta che tutti i dati siano arrivati: una query banale ma essenziale, SELECT COUNT(*) FROM ordini, eseguita sui due server e confrontata. La terza è il backup verificato del nuovo database: faccio un dump completo, lo restore su un server di staging, e verifico che il restore sia effettivamente utilizzabile. Senza questo test, il primo backup del nuovo server è un atto di fede. La quarta è il monitoring delle scritture: per 14 giorni controllo i log di MariaDB per qualunque errore, deadlock, query in errore, e applico fix laddove necessario. La quinta è la documentazione: scrivo per il cliente una pagina di runbook che descrive come è stato fatto lo switch, quale era la configurazione di partenza, quale è quella di arrivo, e quali sono le procedure di backup/restore da seguire da quel momento in poi.

Solo dopo i 14 giorni, e dopo che le cinque verifiche sono tutte verdi, autorizzo il cliente a decommissionare il vecchio server. La differenza fra una migrazione fatta "alla buona" e una fatta secondo questo processo non è il downtime in sé (che è importante ma misurabile in minuti), ma la certezza che ogni byte è arrivato, ogni query funziona, ogni edge-case è stato testato. È la differenza che separa il consulente che risolve il problema dal tecnico che lo sposta da un server all'altro. Se la tua PMI ha un database MySQL critico in produzione su una versione fuori supporto, o su un hardware sottodimensionato, e stai rimandando la migrazione perché "fa paura", scopri come lavoro con i clienti sulle migrazioni di database critici: in dieci anni di consulenza ho visto che il fattore decisivo nel successo di una migrazione non è la dimensione del database, ma la qualità della pianificazione delle settimane precedenti allo switch. Se invece sei già nella situazione descritta e ti serve una valutazione operativa per pianificare la migrazione del tuo database con un piano d'azione concreto sui tempi e sui rischi, contattami per una consulenza: in due settimane ti consegno l'audit completo del database, il piano dettagliato di replicazione master-slave, e la stima del downtime di cutover calibrata sui tuoi volumi di dati e sulla finestra di intervento più adatta al tuo business.

Ultima modifica: