Diagnosticando Swap Thrashing no Linux: Quando a Pressão de Memória Causa Lentidão

Diagnosticando Swap Thrashing no Linux: Quando a Pressão de Memória Causa Lentidão

O que é Swap Thrashing?

Conclusão: A memória física está esgotada, então o kernel constantemente expulsa páginas para o swap (disco) e as lê de volta. A CPU fica quase ociosa enquanto o I/O de disco satura, e todo o sistema fica extremamente lento. A causa raiz é "memória insuficiente", mas o sintoma parece "o disco está lento" ou "apenas o load average está alto".

Quando a memória fica escassa, o Linux move páginas raramente usadas para a área de swap (swap-out) para liberar RAM. Isso sozinho é normal. O problema começa quando uma página expulsa é necessária novamente quase imediatamente: lê-la de volta (swap-in), expulsar outra página para abrir espaço, precisar daquela novamente, e assim por diante. Esse ciclo é thrashing, e a CPU gasta seu tempo esperando transferências de páginas em vez de fazer trabalho real.

$ uptime
 14:32:10 up 8 days,  3:11,  2 users,  load average: 18.40, 16.92, 12.05

O load average dispara, mas o top mostra baixo uso de CPU (us/sy) e um alto wa (I/O wait). "A CPU está ociosa mas tudo está lento" é a assinatura clássica de thrashing.

Premissas (ambiente alvo)

  • SO: Ubuntu / Linux em geral
  • Sintoma: o servidor de repente fica lento, não responde, até o SSH trava
  • Swap está habilitado (a linha Swap no free não é 0)
  • Você pode ler vmstat / free / /proc (configurações permanentes requerem sudo)

Por que observar a taxa e não o uso do swap?

Conclusão: Swap em uso não é um problema por si só. Páginas de processos ociosos descansando no swap é saudável. O perigo é quando o fluxo de swap-in / swap-out é continuo e rápido -- somente isso se correlaciona com a lentidão perceptível. Julgue pela taxa (vmstat si/so), não pelo uso (free).

Mesmo se free -h mostrar o Swap cheio, isso pode ser apenas "expulso há muito tempo e deixado lá", o que é inofensivo. Por outro lado, uso moderado de swap com tráfego pesado por segundo fará o sistema parecer congelado.

Aspecto Saudável Thrashing
Uso do swap (free) Estável, mesmo se alto Oscila rápido para cima e baixo
si/so (vmstat) Próximo de 0 Continuamente grande
CPU Normal Alto wa (I/O wait)
Sensação Normal Cada ação está lenta

O indicador principal de thrashing não é "quanto swap está preenchido" mas "quanto está sendo movido para dentro e fora por segundo agora". A primeira coisa a verificar são as colunas si / so do vmstat.

Como observar primeiro? (vmstat / free)

Conclusão: Execute vmstat 1. Se si (swap-in KB/s) e so (swap-out KB/s) permanecerem grandes, o thrashing está confirmado. Cruze com a margem disponível com free -h e o alto wa com top.

Comece transmitindo o vmstat em intervalos de 1 segundo. A primeira linha é uma média desde o boot, mas cada linha subsequente cobre o último segundo, então você vê o "fluxo" de si/so.

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2 14 2087600  51200  10240 102400 4096 5120  4200 5300 3100 8900  3  4  8 85  0
 1 12 2091200  48300  10100 101800 3800 4900  3900 5100 2980 8500  2  3 10 85  0

si / so continuam fluindo em megabytes por segundo e wa (I/O wait) está acima de 80%. b (processos bloqueados) também está alto. Isso é evidência concreta de thrashing. Em seguida, verifique a margem disponível.

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            3.8Gi       3.5Gi       120Mi        12Mi       180Mi        90Mi
Swap:           2.0Gi       1.9Gi        80Mi

available é minúsculo e o Swap está quase cheio. A memória física está esgotada e não é mais possível descarregar para o swap. Confirme com top.

$ top -bn1 | head -5
top - 14:32:40 up 8 days,  load average: 18.40, 16.92, 12.05
Tasks: 210 total,   1 running, 209 sleeping
%Cpu(s):  3.0 us,  4.0 sy,  0.0 ni,  8.0 id, 85.0 wa,  0.0 hi,  0.0 si
MiB Mem :   3891.0 total,    120.0 free,   3591.0 used,    180.0 buff/cache
MiB Swap:   2048.0 total,     80.0 free,   1968.0 used

wa domina e id (idle) é minúsculo. A CPU está esperando a conclusão de I/O, não computando.

Se o sar (pacote sysstat) estiver disponível, você pode revisar o histórico com sar -W (pswpin/s, pswpout/s para swap-in/out) e sar -B (pgpgin/s, pgpgout/s para paginação) para ver "quando começou". Combinar o vmstat em tempo real com o histórico do sar facilita identificar o momento de início.

Qual processo está consumindo o swap?

Conclusão: smem -s swap -r é a forma mais legível de ver o uso de swap por processo. Se o smem não estiver disponível, some VmSwap de /proc/<pid>/status. O maior consumidor de swap geralmente é a causa principal do thrashing.

O smem pode mostrar o uso de swap por processo diretamente (sudo apt install smem).

$ sudo smem -s swap -r | head
  PID User     Command                         Swap      USS      PSS      RSS
 2314 www-data java -Xmx3g -jar app.jar      1245184   210432   215300   240128
 1190 mysql    /usr/sbin/mysqld               412300    98200   101400   130560
 1532 root     /usr/bin/dockerd               120400    40100    42300    61440

O processo com a coluna Swap desproporcional é o culpado. Se você não pode instalar o smem (adicionar um novo pacote é indesejável), some diretamente do /proc.

# Listar VmSwap de todos os processos, maior primeiro (KB)
$ for f in /proc/[0-9]*/status; do
    awk '/^Name:/{n=$2} /^VmSwap:/{print $2, n, FILENAME}' "$f"
  done 2>/dev/null | sort -rn | head
1245184 java /proc/2314/status
412300 mysqld /proc/1190/status
120400 dockerd /proc/1532/status

Uma reserva de memória mal configurada (um -Xmx da JVM maior que a RAM física, um buffer pool de banco de dados superdimensionado, etc.) é a causa típica. Reduzir o limite para um valor que cabe na memória física geralmente é a correção real.

Alto uso de swap não significa automaticamente "o culpado". Para encontrar "o processo movendo páginas para dentro e fora agora", capture o smem várias vezes durante o thrashing e observe quais processos têm um valor de Swap variável. Um valor estático são apenas páginas dormindo.

Como parar a lentidão agora? (Primeiros socorros)

Conclusão: Parar um processo descontrolado ou superdimensionado, ou reduzir seu limite de memória e reiniciá-lo, é a correção mais rápida. Para reiniciar o swap acumulado, use swapoff -a && swapon -a, mas executá-lo sem memória livre suficiente convida o OOM killer, então é perigoso. Os primeiros socorros devem apenas ir na direção de "liberar memória".

O passo mais seguro e eficaz é parar ou reconfigurar o processo superdimensionado encontrado com smem.

# Reiniciar o culpado pelo caminho adequado (apos revisar sua configuracao)
$ sudo systemctl restart myapp.service

Para "reiniciar" trazendo as páginas em swap de volta para a RAM, use swapoff -> swapon. Mas isso recarrega todo o conteúdo do swap na memória de uma vez, então se a memória física livre for menor que o uso do swap, o OOM killer é acionado.

# Perigoso: convida OOM se a memoria livre for menor que o uso do swap
$ free -h          # Confirme que available > Swap used primeiro
$ sudo swapoff -a && sudo swapon -a

"Apenas reiniciar" faz o sintoma desaparecer, mas ele voltará a menos que você corrija a configuração de memória ou o vazamento. Após os primeiros socorros, sempre prossiga para identificar a causa raiz (configuração superdimensionada / vazamento / RAM simplesmente insuficiente).

Deve-se ajustar o swappiness?

Conclusão: vm.swappiness é um valor de tendência (0-200, padrão 60; valores acima de 100 são para swap rápido como zram/zswap) para "quão agressivamente usar swap". Reduzi-lo torna o kernel menos ansioso para expulsar páginas de aplicação, o que pode melhorar a sensação de um servidor interativo. Mas não resolve a escassez de memória física em si. A correção real é mais RAM ou menos uso.

Verifique o valor atual.

$ cat /proc/sys/vm/swappiness
60

Reduza temporariamente e observe (reverte no reboot).

# Comece em torno de 10 em vez de 0
$ sudo sysctl -w vm.swappiness=10

Uma vez confirmado o efeito, torne permanente.

$ echo 'vm.swappiness = 10' | sudo tee /etc/sysctl.d/99-swappiness.conf
$ sudo sysctl --system
swappiness Tendência Indicado para
0-10 Evitar swap o máximo possível Servidores interativos, baixa latência
60 (padrão) Equilibrado Uso geral
100 Swap agressivamente Batch, orientado a throughput
Acima de 100 (até 200) Swap ainda mais agressivamente Swap rápido como zram/zswap

Definir swappiness como 0 não desabilita completamente o swap (ainda faz swap sob pressão de memória). Reduzi-lo demais também desequilibra o cache de arquivos e pode criar um tipo diferente de lentidão. Evite valores extremos e ajuste observando vmstat para si/so se estabilizarem.

Como limitar o swap de um serviço com cgroups?

Conclusão: Para um serviço gerenciado pelo systemd, MemoryMax / MemoryHigh impõem um limite de memória e contêm o thrashing para que um serviço não prejudique todo o host. Quando o limite é excedido, apenas aquele serviço é submetido a recuperação / OOM, e o restante fica protegido.

Defina um limite de memória em um serviço específico (ex: uma aplicação Java superdimensionada).

$ sudo systemctl edit myapp.service

Escreva os limites na seção [Service].

[Service]
MemoryHigh=2G
MemoryMax=2.5G

MemoryHigh é um "limite suave" que aciona recuperação agressiva quando excedido; MemoryMax é um "limite rigido" que aciona OOM kill quando excedido. Aplique após salvar.

$ sudo systemctl daemon-reload
$ sudo systemctl restart myapp.service
$ systemctl show -p MemoryMax myapp.service

Agora o uso de memória do serviço é limitado pelo cgroup e não espalha mais thrashing por todo o sistema.

Definir MemoryHigh um pouco abaixo de MemoryMax cria uma zona de amortecimento onde o serviço "é limitado e fica lento" antes de ser subitamente morto no limite rigido. Em produção, especificar ambos é mais seguro do que apenas MemoryMax.

E se a causa real for RAM insuficiente? (Adicionando Swap / Capacidade)

Conclusão: Se não há configuração errada e nenhum vazamento, e a memória é simplesmente sempre escassa, adicionar hardware (mais RAM) é o caminho correto. Adicionar swap é apenas um buffer "melhor do que morrer por OOM"; mesmo com mais swap, thrashing continua lento. Adicionar swap ganha tempo; adicionar RAM resolve.

Passos para adicionar um arquivo de swap para evitar OOM iminente (quando você não pode adicionar RAM imediatamente, ex: na nuvem).

# Criar um arquivo de swap de 2GB
$ sudo fallocate -l 2G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

Torne permanente adicionando ao /etc/fstab.

/swapfile  none  swap  sw  0  0

Adicionar swap não para a paginação enquanto a memória está escassa, então continua "lento". Adicionar swap é um paliativo para evitar OOM kills; não cura a lentidão do thrashing em si. Se você sofre thrashing crônico, mais RAM ou reduzir a carga de trabalho (reduzir limites de memória por processo, reduzir concorrência) é a abordagem correta.

Checklist quando ainda não melhora

Conclusão: Thrashing significa que "memória física insuficiente" está confirmada. Confirme si/so com vmstat -> identifique o culpado com smem -> corrija a configuração superdimensionada / vazamento -> adicione RAM se ainda insuficiente. A causa converge nessa camada. swappiness e cgroups são sintomáticos; a raiz é o balanco com a quantidade de memória.

  • [ ] Confirmou que si / so no vmstat 1 estão continuamente grandes (taxa, não uso)?
  • [ ] Confirmou que wa (I/O wait) está alto enquanto us/sy estão baixos no top?
  • [ ] Verificou a margem available e do Swap com free -h?
  • [ ] Identificou o culpado com smem -s swap -r ou VmSwap em /proc/*/status?
  • [ ] A configuração de memória daquele processo (JVM -Xmx, buffers de banco) cabe na RAM física?
  • [ ] Descartou vazamento de memória (não está aumentando monotonicamente ao longo do tempo)?
  • [ ] Confirmou available > Swap used antes dos primeiros socorros (swapoff)?
  • [ ] Considerou mais RAM / menos concorrência se crônico?

Próximas leituras