Migliorare l'utilizzo delle risorse del server monitorando le perdite di memoria


La prima ipotesi che abbiamo esplorato era che il modello di utilizzo della memoria dei giochi stesse cambiando. Incoraggiamo gli sviluppatori a spingersi oltre i limiti della piattaforma, a utilizzare le risorse a loro disposizione per creare giochi straordinari e innovativi. Era facile verificarlo: potevamo aggregare l'utilizzo della memoria di tutti i giochi e vedere se fosse aumentato. Ma non è stato così: la memoria media e gli aggregati percentili per gioco sono rimasti relativamente stabili, mentre la nostra capacità era in costante calo.
Ogni mese archiviamo terabyte di dati sulle prestazioni e sull'utilizzo delle risorse che possono essere aggregati e filtrati per aiutare a individuare la causa principale di problemi come questo. Abbiamo cercato di isolare il problema a una particolare area geografica, tipo di hardware o versione software, ma sfortunatamente il problema era presente ovunque. Abbiamo quindi deciso di effettuare controlli a campione su alcuni server di gioco e di condurre un'indagine approfondita. Inizialmente sono stato fuorviato dal concetto di memoria "libera" sui sistemi Linux. Si tratta di un problema talmente comune che ha spinto qualcuno a registrare un dominio e a creare un sito web per spiegare le categorie di memoria: https://www.linuxatemyram.com.

In breve: avere la maggior parte della memoria utilizzata è una cosa positiva, la memoria libera è memoria sprecata. Dobbiamo preoccuparci solo quando la memoria disponibile è vicina allo 0.
Una volta stabilito che stavamo monitorando la memoria correttamente, abbiamo avviato una serie di esperimenti per capire dove venisse utilizzata la memoria. Il nostro approccio è stato quello di monitorare specifiche sottocategorie di memoria in modo da poter avere una soluzione mirata.

La memoria disponibile è calcolata approssimativamente come la somma di MemFree + Active(file) + Inactive(file) + SReclaimable.
MemFree tiene traccia della memoria inutilizzata.
Active(file) e Inactive(file) tengono traccia della memoria della cache di pagina. La cache di pagina memorizza i dati a cui si è avuto accesso in memoria per ridurre la quantità di I/O su disco.
SReclaimable tiene traccia della memoria slab recuperabile. La memoria slab viene utilizzata per conservare le cache degli oggetti inizializzati comunemente utilizzati dal kernel.
Il primo esperimento è stato quello di modificare l'ottimizzazione della cache, in particolare: vm.vfs_cache_pressure e vm.dirty_background_ratio.
Un aumento di vfs_cache_pressure renderà più probabile il recupero di oggetti dalla cache recuperabile. Ciò ha un impatto sulle prestazioni (sia in termini di cache miss che di tempo di ricerca per trovare oggetti liberabili).
dirty_background_ratio è la percentuale (della memoria della cache di pagina che è sporca) quando iniziamo a scrivere su disco in modo non bloccante.
Abbiamo apportato le seguenti modifiche e osservato gli effetti su memoria, slabinfo e cgroups.
vm.vfs_cache_pressure= 100 ==> 10000<br>vm.dirty_background_ratio= 10 ==> 5Un modo non invasivo per monitorare il risultato è stato quello di eseguire periodicamente un cron job che acquisisse un'istantanea dello stato della memoria. Qualcosa del tipo:
#!/bin/bash<br>now=`date +%Y-%m-%d.%H:%M`
# create test dir<br>mkdir -p ~/memtest # log meminfo<br>sudo cat /proc/meminfo
~/memtest/meminfo_$now# log slabinfo<br>sudo cat /proc/slabinfo
~/memtest/slabinfo_$now # log cgroups<br>sudo cat /proc/cgroups
~/memtest/cgroups_$now Dopo aver applicato le modifiche alla pressione della cache e aver osservato per alcune ore, abbiamo iniziato l'analisi. Abbiamo concluso di aver guadagnato circa 8 GB di memoria libera, ma quella memoria proveniva direttamente dalle cache di pagina e su disco, dalle categorie Active(file) e Inactive(file). Questo è stato un risultato deludente: non c'era un aumento netto significativo della memoria disponibile, inoltre non stavamo più utilizzando questa memoria in modo proficuo. Dovevamo recuperare memoria da qualche altra parte.
Dopo aver fallito nel tentativo di aumentare direttamente la memoria disponibile, abbiamo provato a ridurre le categorie in competizione. Abbiamo notato che la categoria di memoria SUnreclaim era ampia, in alcuni casi gonfiandosi fino a 60 GB nell'arco di pochi mesi. La categoria SUnreclaim tiene traccia della memoria utilizzata per i pool di oggetti dal sistema operativo che non può essere recuperata in condizioni di pressione della memoria. Il primo segnale di un problema era un numero di cgroup in costante crescita. Ci aspettavamo al massimo un paio di centinaia di cgroup dall'esecuzione dei nostri processi dockerizzati, ma ne vedevamo CENTINAIA DI MIGLIAIA. Fortunatamente per noi, sembra che un altro ingegnere, Roman Gushchin, di Facebook, avesse recentemente individuato e risolto proprio questo problema a livello di kernel https://patchwork.kernel.org/cover/10943797/. Egli afferma:
Il problema di fondo è piuttosto semplice: qualsiasi pagina addebitata a un cgroup contiene un riferimento ad esso, quindi il cgroup non può essere recuperato a meno che tutte le pagine addebitate non siano state eliminate. Se un oggetto slab è attivamente utilizzato da altri cgroup, non verrà recuperato e impedirà il recupero del cgroup di origine.
Questo sembrava essere esattamente il nostro problema, quindi abbiamo atteso con impazienza il kernel 5.3 per verificare la correzione.
Abbiamo riutilizzato lo script di monitoraggio della memoria dall'esperimento sulla pressione della cache, ma per l'esperimento sul kernel volevamo stabilire gruppi di controllo e sperimentali. Abbiamo scaricato il traffico di produzione da 2 rack di server, quindi abbiamo aggiornato il kernel alla versione 5.3 su un rack e mantenuto il kernel 5.0 sull'altro. Successivamente abbiamo riavviato entrambi i rack e li abbiamo riaperti al traffico di produzione. Dopo circa una settimana abbiamo monitorato come i cgroup e la memoria slab non recuperabile sono cambiati nel tempo. Ecco i risultati:


Il kernel 5.0.0 presenta una crescita ininterrotta dei cgroup e nell'arco di una settimana guadagna 4 GB di memoria slab non recuperabile, per un totale di 6 GB. D'altra parte, il kernel 5.3.7 presenta riduzioni giornaliere significative nei cgroup e la crescita della memoria slab non recuperabile è molto lenta. Dopo una settimana, la memoria slab non recuperabile è di circa 2 GB. Con il nuovo kernel, la memoria slab non recuperabile si stabilizza a circa 4 GB, anche dopo diversi mesi di attività.
Il problema principale che volevamo risolvere era che i nostri server di gioco stavano perdendo capacità nel tempo. Ciò era dovuto alla minore memoria disponibile, a sua volta causata dalla costante crescita della memoria slab non recuperabile. Quindi, una volta che abbiamo ottenuto un relativo controllo grazie alla correzione del kernel, qual è stato l'effetto sulla capacità del server?

A sinistra, potete vedere il nostro problema: un calo costante del numero di giochi per server che ha messo a dura prova la nostra infrastruttura. Abbiamo implementato la versione 5.3 del kernel su tutta la nostra flotta globale intorno a marzo 2020 e finora siamo riusciti a mantenere un'elevata capacità dei server. Un grande ringraziamento a Roman Gushchin per la correzione del kernel e ad Andre Tran per aver aiutato a indagare sul problema e a implementare la correzione.
Né Roblox Corporation né questo blog promuovono o sostengono alcuna azienda o servizio. Inoltre, non vengono fornite garanzie o promesse riguardo all'accuratezza, all'affidabilità o alla completezza delle informazioni contenute in questo blog.
Questo post è stato originariamente pubblicato sul blog tecnico di Roblox.


