Configurare swap file per risolvere saturazioni RAM su VPS gestite senza supporto tecnico: guida pratica per Debian e Ubuntu

Configurare swap file per risolvere saturazioni RAM su VPS gestite senza supporto tecnico: guida pratica per Debian e Ubuntu

A febbraio 2025, un e-commerce Laravel su un Hetzner CPX21 - 3 vCPU, 4 GB di RAM, 80 GB NVMe - aveva un problema ricorrente: ogni notte, tra le 2:00 e le 3:00, il gestionale diventava irraggiungibile per 5-10 minuti. Il titolare - PMI piemontese nel settore forniture per ufficio - lo scopriva la mattina dai log di monitoring esterno (UptimeRobot) e dall'assenza degli ordini notturni dei clienti automatizzati. La diagnosi con dmesg ha mostrato il pattern chiaramente:

[Thu Feb 13 02:17:43 2025] Out of memory: Killed process 1847 (mysqld) total-vm:2847632kB
[Thu Feb 13 02:17:43 2025] oom_reaper: reaped process 1847 (mysqld), now anon-rss:0kB

Il kernel aveva attivato l'OOM (Out Of Memory) killer e aveva terminato MySQL - il processo che consumava più RAM - perché la memoria fisica era esaurita al 100% e non c'era swap configurato. Il VPS Hetzner, come la maggior parte dei VPS cloud, viene provisioned senza swap: zero byte di swap file o swap partition. Quando MySQL tentava di allocare memoria per l'import notturno dei listini (un job che caricava in memoria circa 800 MB di dati), la RAM superava il 100% e il kernel non aveva alternative all'uccidere il processo più affamato.

Lo swap non è un sostituto della RAM - è una rete di sicurezza. Configurarlo correttamente su un VPS è uno dei primi interventi che faccio su ogni macchina, e in questo articolo ti racconto come dimensionarlo, come tunarle lo swappiness, e perché nel 2025 ZRAM è spesso la scelta migliore rispetto allo swap tradizionale su disco.

Stai cercando un Consulente Informatico esperto per ottimizzare le performance del tuo VPS? Nel mio profilo professionale trovi l'esperienza concreta su tuning Linux, gestione memoria e ottimizzazione di stack LEMP per PMI. Contattami per una consulenza diretta.

Perché un VPS senza swap è una bomba a orologeria?

Quando un processo Linux richiede memoria e non ce n'è più di disponibile, il kernel ha due opzioni: se esiste uno swap, sposta le pagine meno usate dalla RAM al disco, liberando spazio; se non esiste swap, attiva l'OOM killer e termina un processo. L'OOM killer sceglie la vittima in base a un punteggio (oom_score) che considera quanta memoria usa il processo e da quanto tempo è in esecuzione - su un VPS LEMP, il bersaglio è quasi sempre MySQL o PHP-FPM, cioè i processi critici per l'applicazione.

L'OOM kill di MySQL è particolarmente distruttivo perché MySQL non gestisce bene la terminazione forzata: le transazioni InnoDB in corso vengono perse, il buffer pool non viene flushato su disco, e al riavvio MySQL deve eseguire un crash recovery che può richiedere minuti o, su database grandi, decine di minuti. In casi sfortunati il crash recovery stesso fallisce e richiede intervento manuale. Ho documentato le problematiche di performance MySQL - inclusa la configurazione corretta del buffer pool che riduce la pressione sulla RAM - nell'articolo sulla diagnosi di connessioni MySQL lente su VPS.

Creare e configurare uno swap file su Debian 12

La creazione di uno swap file su Debian 12 è diretta. Non uso mai dd per creare il file - fallocate è istantaneo perché alloca lo spazio senza scrivere zeri:

# 1. Creare il file swap (2GB per VPS con 4GB RAM)
fallocate -l 2G /swapfile

# 2. Proteggere il file (solo root può leggere/scrivere)
chmod 600 /swapfile

# 3. Formattare come swap
mkswap /swapfile

# 4. Attivare
swapon /swapfile

# 5. Verificare
swapon --show
free -h

Per rendere lo swap permanente dopo un riavvio, aggiungere la riga in /etc/fstab:

echo '/swapfile none swap sw 0 0' >> /etc/fstab

# Verificare che fstab sia sintatticamente corretto
# (un errore in fstab può impedire il boot!)
mount -a

Quanto swap configurare?

La vecchia regola "swap = 2× RAM" è obsoleta da un decennio. Su un VPS cloud la regola che uso è:

  • VPS con 1-2 GB RAM: swap 2 GB (rete di sicurezza ampia, la RAM è molto limitata)
  • VPS con 4-8 GB RAM: swap 2 GB (sufficiente come buffer per picchi temporanei)
  • VPS con 16+ GB RAM: swap 1-2 GB (solo come safety net estremo, la RAM dovrebbe bastare)

Lo swap non è performance - è sopravvivenza. Se il tuo VPS usa regolarmente lo swap, il vero problema è che hai bisogno di più RAM, non di più swap. Lo swap su disco, anche su NVMe, è ordini di grandezza più lento della RAM (200 microsecondi vs 100 nanosecondi), e un VPS che "swappa" attivamente diventa lentissimo.

Swappiness: quanto aggressivamente il kernel usa lo swap

Il parametro vm.swappiness controlla quanto il kernel è propenso a spostare pagine dalla RAM allo swap. Il default è 60 - troppo aggressivo per un server. Con swappiness 60, il kernel inizia a swappare quando la RAM è al 60% di utilizzo, il che su un VPS con applicazioni PHP/MySQL significa swapping costante anche in condizioni normali.

# Verificare il valore attuale
cat /proc/sys/vm/swappiness

# Impostare a 10 (swap solo quando strettamente necessario)
sysctl vm.swappiness=10

# Rendere permanente
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-swap.conf

# Opzionale: ridurre anche la pressione sulla cache dei metadati
echo 'vm.vfs_cache_pressure=50' >> /etc/sysctl.d/99-swap.conf
sysctl -p /etc/sysctl.d/99-swap.conf

Con vm.swappiness=10 il kernel usa lo swap solo quando la RAM è quasi esaurita - esattamente il comportamento che vuoi su un server: la RAM per le applicazioni, lo swap come paracadute.

ZRAM: l'alternativa compressa che nel 2025 è la scelta migliore

ZRAM è un modulo del kernel Linux che crea un dispositivo a blocchi compresso in RAM. Invece di scrivere le pagine swappate su disco, ZRAM le comprime e le tiene in memoria. Con l'algoritmo LZ4 (molto veloce) o ZSTD (migliore compressione), un blocco ZRAM da 1 GB di RAM fisica può contenere 3-4 GB di dati compressi - è come avere 3× la RAM senza toccare il disco.

Su un VPS con NVMe, la differenza di performance tra swap su disco e ZRAM è misurabile ma non drammatica (NVMe è già veloce). Ma su VPS con storage SATA o network storage - come i piani entry-level di OVH o Contabo - ZRAM è ordini di grandezza più veloce dello swap su disco perché evita completamente l'I/O.

La configurazione su Debian 12 è semplice grazie al pacchetto zram-tools:

# Installare zram-tools
apt install -y zram-tools

# Configurare: 50% della RAM come ZRAM, algoritmo LZ4
cat > /etc/default/zramswap << 'EOF'
ALGO=lz4
PERCENT=50
PRIORITY=100
EOF

# Riavviare il servizio
systemctl restart zramswap

# Verificare
swapon --show
# Output:
# NAME       TYPE      SIZE USED PRIO
# /dev/zram0 partition   2G   0B  100

Il PRIORITY=100 è importante: se hai sia ZRAM che uno swap file su disco, il kernel usa prima quello con priorità più alta. Con ZRAM a priorità 100 e swap file a priorità -2 (default), il kernel comprime in RAM finché possibile e spilla su disco solo quando ZRAM è pieno.

La mia configurazione standard su ogni VPS è: ZRAM al 50% della RAM come swap primario, più uno swap file da 1 GB su disco come fallback estremo. Questo copre sia i picchi temporanei di memoria (gestiti da ZRAM senza I/O) sia gli scenari catastrofici in cui anche ZRAM è saturo.

Proteggere MySQL e PHP-FPM dall'OOM killer

Anche con lo swap configurato, in situazioni estreme il kernel potrebbe comunque attivare l'OOM killer. La difesa aggiuntiva che configuro è l'oom_score_adj - un parametro che dice al kernel quali processi sono prioritari e non devono essere uccisi per primi:

# Proteggere MySQL dall'OOM killer (valore -900 = quasi immune)
echo -900 > /proc/$(pgrep -x mysqld)/oom_score_adj

# Proteggere il master process di PHP-FPM
echo -500 > /proc/$(pgrep -x php-fpm8.2 | head -1)/oom_score_adj

# Rendere permanente con un override systemd
mkdir -p /etc/systemd/system/mysql.service.d
cat > /etc/systemd/system/mysql.service.d/oom.conf << 'EOF'
[Service]
OOMScoreAdjust=-900
EOF
systemctl daemon-reload

Il valore va da -1000 (mai uccidere) a +1000 (uccidere per primo). Impostando MySQL a -900, il kernel sceglierà di uccidere qualsiasi altro processo prima di toccare MySQL - il che è quasi sempre il comportamento desiderato su un server applicativo, perché un processo PHP terminato viene rigenerato da FPM in millisecondi, mentre un MySQL terminato richiede crash recovery.

Monitoring della pressione sulla memoria: sapere quando lo swap non basta

Il segnale più affidabile per capire se il server è sotto pressione di memoria non è free -h - è PSI (Pressure Stall Information), disponibile su kernel 4.20+ (Debian 12 usa kernel 6.1, quindi lo supporta):

# Pressione sulla memoria in tempo reale
cat /proc/pressure/memory
# Output:
# some avg10=0.00 avg60=0.00 avg300=0.00 total=0
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0

# Se "some" è > 0 per periodi prolungati, il sistema sta rallentando per mancanza di memoria
# Se "full" è > 0, processi stanno completamente bloccati in attesa di memoria

Lo script di monitoring che installo controlla sia lo swap attivo che la PSI:

#!/usr/bin/env bash
# /root/scripts/memory-pressure-check.sh - cron ogni 5 minuti
SWAP_USED=$(free -m | awk '/Swap:/{print $3}')
SWAP_TOTAL=$(free -m | awk '/Swap:/{print $2}')
PSI_SOME=$(awk -F= '/some/{print $2}' /proc/pressure/memory | cut -d' ' -f1)

# Alert se swap usato > 50% o PSI > 5%
if [ "$SWAP_TOTAL" -gt 0 ] && [ "$((SWAP_USED * 100 / SWAP_TOTAL))" -gt 50 ]; then
    echo "SWAP al $((SWAP_USED * 100 / SWAP_TOTAL))% su $(hostname)" | \
        mail -s "MEMORY ALERT" [email protected]
fi

Un VPS che usa più del 50% dello swap in modo costante ha bisogno di più RAM - lo swap sta tamponando un deficit strutturale. La corretta sequenza di intervento è: prima configura lo swap come safety net (questo articolo), poi ottimizza l'applicazione per ridurre il consumo di memoria (buffer pool MySQL, worker PHP-FPM, code block), e solo se il consumo resta alto dopo l'ottimizzazione, fai l'upgrade della RAM del VPS.

Ho descritto l'intero workflow di ottimizzazione performance su VPS Hetzner e OVH - inclusi il dimensionamento del buffer pool InnoDB e il calcolo dei worker PHP-FPM - in un articolo dedicato.

Il risultato sul VPS piemontese

Dopo la configurazione di 2 GB di swap file con swappiness a 10, l'OOM killer non si è più attivato. L'import notturno dei listini ora "spilla" circa 300 MB nello swap durante il picco - visibile con vmstat 1 durante il job - ma completa correttamente senza uccidere MySQL. Il response time del sito durante l'import è leggermente degradato (da 200ms a 350ms) perché parte della memoria è in swap, ma il servizio resta online e funzionante.

La soluzione definitiva, che ho implementato la settimana successiva, è stata ridurre il consumo di memoria dell'import riscrivendo la query da Product::all() (che caricava 340.000 prodotti in un array PHP) a Product::cursor() (che processa un record alla volta con un generator). Il consumo di RAM dell'import è passato da 800 MB a 45 MB, e lo swap non viene più toccato nemmeno durante il picco. Ma lo swap resta configurato - perché il prossimo picco potrebbe arrivare da un posto diverso, e la rete di sicurezza deve essere sempre lì.

Per le tecniche di ottimizzazione performance PHP e MySQL sul tuo VPS che riducono il consumo di memoria alla radice - anziché tamponarlo con lo swap - ho scritto un articolo dedicato.

Se il tuo VPS non ha swap configurato e i processi critici vengono terminati senza preavviso durante i picchi di carico, la soluzione è a cinque minuti di distanza - cinque comandi e un reboot per rendere permanente. Se non sai da dove partire o vuoi una configurazione ottimizzata per il tuo specifico carico di lavoro, contattami.

Ultima modifica: