Por que meu sistema Linux gagueja a menos que eu elimine caches continuamente?

2

Nos últimos meses, tive um problema extremamente irritante com meu sistema Linux: ele gagueja na reprodução de áudio do Firefox, no movimento do mouse, etc., com um minúsculo sub-segundo (mas ainda perceptível) gaguejando a cada poucos segundos . O problema piora à medida que o cache de memória é preenchido ou quando tenho programas altamente intensivos em disco / memória em execução (por exemplo, software de backup restic ). No entanto, quando o cache não está cheio (por exemplo, sob carga muito leve), tudo corre muito bem.

Examinando perf top output, vejo que list_lru_count_one tem alta sobrecarga (~ 20%) durante esses períodos de defasagem. htop também mostra kswapd0 usando 50-90% da CPU (embora pareça que o impacto é muito maior do que isso). Durante períodos de atraso extremo, o htop do medidor da CPU é geralmente dominado pelo uso da CPU do kernel.

A única solução que encontrei é forçar o kernel a manter a memória livre ( sysctl -w vm.min_free_kbytes=1024000 ) ou a descartar continuamente os caches de memória através de echo 3 > /proc/sys/vm/drop_caches . Nem é ideal, é claro, e nem resolve completamente a gagueira; só o torna menos frequente.

Alguém tem alguma idéia sobre por que isso pode estar ocorrendo?

Informações do sistema

  • i7-4820k com 20 GB de RAM DDR3 (incompatível)
  • Reproduzido no Linux 4.14-4.18 no NixOS instável
  • Executa contêineres Docker e Kubernetes em segundo plano (o que eu sinto que não deveria criar microstattering?)

O que eu já tentei

  • Alterando os planejadores de E / S (bfq), usando planejadores de E / S multicamadas
  • Usando o patchset -ck de Con Kolivas (não ajudou)
  • Desabilitando o swap, alterando o swappiness, usando o zram

EDITAR : Para maior clareza, aqui está uma imagem de htop e perf durante esse pico de latência. Observe a alta carga da CPU list_lru_count_one e o uso da CPU do kernel kswapd0 + alto.

    
por Pneumaticat 10.09.2018 / 19:59

2 respostas

0

O problema foi encontrado!

Acontece que é um problema de desempenho na recuperação de memória do Linux quando há um grande número de contêineres / cgroups de memória. (Aviso: minha explicação pode ser falha, não sou um desenvolvedor do kernel.) O problema foi corrigido em 4.19-rc1 + em este conjunto de patches :

This patcheset solves the problem with slow shrink_slab() occuring on the machines having many shrinkers and memory cgroups (i.e., with many containers). The problem is complexity of shrink_slab() is O(n^2) and it grows too fast with the growth of containers numbers.

Let us have 200 containers, and every container has 10 mounts and 10 cgroups. All container tasks are isolated, and they don't touch foreign containers mounts.

In case of global reclaim, a task has to iterate all over the memcgs and to call all the memcg-aware shrinkers for all of them. This means, the task has to visit 200 * 10 = 2000 shrinkers for every memcg, and since there are 2000 memcgs, the total calls of do_shrink_slab() are 2000 * 2000 = 4000000.

Meu sistema foi particularmente afetado, pois eu gero um bom número de contêineres, o que provavelmente causou o problema.

Minhas etapas de solução de problemas, caso sejam úteis para qualquer pessoa que esteja enfrentando problemas semelhantes:

  1. Observe kswapd0 usando uma tonelada de CPU quando meu computador gagueja
  2. Tente parar os contêineres do Docker e preencher a memória novamente → o computador não gagueja!
  3. Executar ftrace (seguindo Blog de explicação magnífica de Julia Evan ) para obter um rastreamento, veja que kswapd0 tende a ficar preso em shrink_slab , super_cache_count e list_lru_count_one .
  4. Google shrink_slab lru slow , encontre o conjunto de patches!
  5. Mude para o Linux 4.19-rc3 e verifique se o problema foi corrigido.
por 16.09.2018 / 02:30
2

Parece que você já tentou muitas das coisas que eu sugeriria inicialmente (ajustes na configuração de troca, alteração de agendadores de E / S, etc.).

Além do que você já tentou alterar, sugiro pesquisar a alteração dos padrões de morte cerebral para o comportamento de write-back da VM. Isso é gerenciado pelos seguintes seis valores de sysctl:

  • vm.dirty_ratio : controla quantas gravações devem estar pendentes para write-back antes de serem acionadas. Manipula o write-back em primeiro plano (por processo) e é expresso como uma porcentagem inteira de RAM. Padrões para 10% de RAM
  • vm.dirty_background_ratio : controla quantas gravações devem estar pendentes para write-back antes de serem acionadas. Manipula write-back em segundo plano (em todo o sistema) e é expresso como uma porcentagem inteira de RAM. Padrões para 20% de RAM
  • vm.dirty_bytes : O mesmo que vm.dirty_ratio , exceto expresso como um número total de bytes. Ou ou vm.dirty_ratio será usado, o que foi escrito para durar.
  • vm.dirty_background_bytes : O mesmo que vm.dirty_background_ratio , exceto expresso como um número total de bytes. Ou ou vm.dirty_background_ratio será usado, o que foi escrito para durar.
  • vm.dirty_expire_centisecs : Quantos centésimos de segundo devem ser passados antes que o writeback pendente seja iniciado quando os quatro valores de sysctl acima já não forem acionados. O padrão é 100 (um segundo).
  • vm.dirty_writeback_centisecs : Com que frequência (em centésimos de segundo) o kernel avaliará páginas sujas para write-back. O padrão é 10 (um décimo de segundo).

Assim, com os valores padrão, a cada décimo de segundo, o kernel fará o seguinte:

  • Anote todas as páginas modificadas para armazenamento persistente se elas foram modificadas pela última vez há mais de um segundo.
  • Escreva todas as páginas modificadas para um processo se a quantidade total de memória modificada que não foi gravada exceder 10% da RAM.
  • Anote todas as páginas modificadas no sistema se a quantidade total de memória modificada que não foi gravada exceder 20% da RAM.

Portanto, deve ser muito fácil ver por que os valores padrão podem estar causando problemas para você, porque seu sistema pode estar tentando gravar até 4 gigabytes de dados para armazenamento persistente a cada < em> décimo de um segundo.

O consenso geral atualmente é ajustar vm.dirty_ratio para 1% da RAM e vm.dirty_background_ratio para 2%, o que para sistemas com menos de 64GB de RAM resulta em um comportamento equivalente ao que foi originalmente pretendido.

Algumas outras coisas para investigar:

  • Tente aumentar um pouco o vm.vfs_cache_pressure sysctl. Isso controla a agressividade com que o kernel recupera memória do cache do sistema de arquivos quando precisa de RAM. O padrão é 100, não o reduza para nada abaixo de 50 (você irá obter um comportamento realmente ruim se você for abaixo de 50, incluindo condições OOM), e não o levará a muito mais do que 200 (muito maior, e o kernel perderá tempo tentando recuperar a memória que realmente não pode). Descobri que aumentar até 150 na verdade melhora visivelmente a capacidade de resposta, caso você tenha um armazenamento razoavelmente rápido.
  • Tente alterar o modo de supercomprometimento de memória. Isso pode ser feito alterando o valor do vm.overcommit_memory sysctl. Por padrão, o kernel usará uma abordagem heurística para tentar prever a quantidade de memória RAM que pode realmente se comprometer. Configurar isto para 1 desativa a heurística e diz ao kernel para agir como se tivesse memória infinita. Configurar isso como 2 diz ao kernel para não se comprometer com mais memória do que a quantidade total de espaço de troca no sistema mais uma porcentagem da RAM real (controlada por vm.overcommit_ratio ).
  • Tente ajustar o vm.page-cluster sysctl. Isso controla quantas páginas são trocadas de uma vez por vez (é um valor logarítmico de base 2, então o padrão de 3 é traduzido para 8 páginas). Se você estiver realmente trocando, isso pode ajudar a melhorar o desempenho de troca e saída de páginas.
por 10.09.2018 / 21:37