Sembra che tu abbia già provato molte delle cose che ti avrei suggerito all'inizio (aggiustare la configurazione dello swap, cambiare gli scheduler di I/O, ecc.).
A parte ciò che hai già provato a modificare, suggerirei di esaminare la modifica delle impostazioni predefinite in qualche modo cerebrali per il comportamento di riscrittura della VM. Questo è gestito dai seguenti sei valori sysctl:
vm.dirty_ratio
:controlla quante scritture devono essere in sospeso per il writeback prima che venga attivato. Gestisce il writeback in primo piano (per processo) ed è espresso come percentuale intera di RAM. L'impostazione predefinita è il 10% della RAMvm.dirty_background_ratio
:controlla quante scritture devono essere in sospeso per il writeback prima che venga attivato. Gestisce il writeback in background (a livello di sistema) ed è espresso come percentuale intera di RAM. L'impostazione predefinita è 20% di RAMvm.dirty_bytes
:Uguale avm.dirty_ratio
, eccetto espresso come numero totale di byte. O questo ovm.dirty_ratio
verrà utilizzato, qualunque sia stato scritto per ultimo.vm.dirty_background_bytes
:Uguale avm.dirty_background_ratio
, eccetto espresso come numero totale di byte. O questo ovm.dirty_background_ratio
verrà utilizzato, qualunque sia stato scritto per ultimo.vm.dirty_expire_centisecs
:Quanti centesimi di secondo devono trascorrere prima che inizi il writeback in attesa quando i quattro valori sysctl precedenti non lo attiverebbero già. Il valore predefinito è 100 (un secondo).vm.dirty_writeback_centisecs
:Quanto spesso (in centesimi di secondo) il kernel valuterà le pagine sporche per il writeback. Il valore predefinito è 10 (un decimo di secondo).
Quindi, con i valori predefiniti, ogni decimo di secondo, il kernel farà quanto segue:
- Scrivi tutte le pagine modificate nella memoria permanente se sono state modificate l'ultima volta più di un secondo fa.
- Scrivi tutte le pagine modificate per un processo se la quantità totale di memoria modificata che non è stata scritta supera il 10% della RAM.
- Scrivi tutte le pagine modificate nel sistema se la quantità totale di memoria modificata che non è stata scritta supera il 20% della RAM.
Quindi, dovrebbe essere abbastanza facile capire perché i valori predefiniti potrebbero causare problemi per te, perché il tuo sistema potrebbe provare a scrivere fino a 4 gigabytes di dati nell'archiviazione permanente ogni decimo di un secondo.
Il consenso generale in questi giorni è di modificare vm.dirty_ratio
essere l'1% della RAM e vm.dirty_background_ratio
essere del 2%, che per i sistemi con meno di circa 64 GB di RAM si traduce in un comportamento equivalente a quello originariamente previsto.
Alcune altre cose da esaminare:
- Prova ad aumentare
vm.vfs_cache_pressure
sysctl un po'. Questo controlla quanto aggressivamente il kernel recupera la memoria dalla cache del filesystem quando ha bisogno di RAM. Il valore predefinito è 100, non abbassarlo a valori inferiori a 50 (farai ottieni un comportamento davvero pessimo se scendi al di sotto di 50, comprese le condizioni OOM) e non aumentarlo a molto più di circa 200 (molto più alto, e il kernel perderà tempo cercando di recuperare memoria che in realtà non può). Ho scoperto che portarlo fino a 150 migliora visibilmente la reattività se disponi di spazio di archiviazione ragionevolmente veloce. - Prova a cambiare la modalità di overcommit della memoria. Questo può essere fatto modificando il valore di
vm.overcommit_memory
sysctl. Per impostazione predefinita, il kernel utilizzerà un approccio euristico per provare a prevedere quanta RAM può effettivamente permettersi di impegnare. L'impostazione di this su 1 disabilita l'euristica e dice al kernel di comportarsi come se avesse una memoria infinita. L'impostazione a 2 dice al kernel di non impegnare più memoria della quantità totale di spazio di swap sul sistema più una percentuale di RAM effettiva (controllata davm.overcommit_ratio
). - Prova a modificare il
vm.page-cluster
sysctl. Questo controlla quante pagine vengono scambiate dentro o fuori alla volta (è un valore logaritmico in base 2, quindi il valore predefinito di 3 si traduce in 8 pagine). Se stai effettivamente scambiando, questo può aiutare a migliorare le prestazioni dello scambio di pagine dentro e fuori.
Il problema è stato trovato!
Si scopre che si tratta di un problema di prestazioni nel recupero della memoria di Linux quando è presente un numero elevato di contenitori/cgroup di memoria. (Disclaimer:la mia spiegazione potrebbe essere errata, non sono uno sviluppatore del kernel.) Il problema è stato risolto in 4.19-rc1+ in questo set di patch:
Questo patchset risolve il problema con il lento shrink_slab() che si verifica sulle macchine che hanno molti shrinker e cgroup di memoria (cioè, con molti contenitori). Il problema è che la complessità di shrink_slab() è O(n^2) e cresce troppo velocemente con la crescita del numero di contenitori.
Facciamo 200 container, e ogni container ha 10 mount e 10cgroups. Tutte le attività del contenitore sono isolate e non toccano i montaggi dei contenitori esterni.
In caso di recupero globale, un'attività deve iterare su tutti i memcg e chiamare tutti gli strizzacervelli compatibili con memcg per tutti loro. Ciò significa che l'attività deve visitare 200 * 10 =2000 strizzacervelli per ogni memcg, e poiché ci sono 2000 memcg, le chiamate totali di do_shrink_slab() sono 2000 *2000 =4000000.
Il mio sistema è stato colpito particolarmente duramente, poiché eseguo un buon numero di container, che probabilmente era ciò che stava causando la comparsa del problema.
I miei passaggi per la risoluzione dei problemi, nel caso siano utili a chiunque debba affrontare problemi simili:
- Nota
kswapd0
usando un sacco di CPU quando il mio computer balbetta - Prova a fermare i contenitori Docker e a riempire di nuovo la memoria → il computer non balbetta!
- Esegui
ftrace
(seguendo il magnifico blog di spiegazioni di Julia Evan) per avere una traccia, vedere chekswapd0
tende a rimanere bloccato inshrink_slab
,super_cache_count
elist_lru_count_one
. - Google
shrink_slab lru slow
, trova il set di patch! - Passa a Linux 4.19-rc3 e verifica che il problema sia stato risolto.