Criar swap e evitar falhas por falta de memória

Publicado a 17 min de leitura

Serviço em baixo, sem relatório de falha e com o log cortado a meio da frase: assim demonstra um OOM-Kill, cria corretamente um ficheiro de swap e reconhece quando o swap apenas adia o problema.

O serviço desapareceu. Não há relatório de falha, não há stacktrace, o ficheiro de log termina a meio de uma linha. O MariaDB deixou de responder, o nginx devolve 502, o servidor de Minecraft está offline e na própria aplicação não se vê nada de invulgar. O padrão é quase sempre o mesmo: o kernel ficou sem memória livre e terminou um processo para manter o sistema vivo. Este artigo mostra como demonstrar isso sem margem para dúvidas, como criar um ficheiro de swap corretamente e registá-lo de forma permanente, e em que casos o swap apenas adia o problema uns minutos.

Reunir a prova: dmesg e journalctl

Antes de configurar seja o que for, precisa da prova. A cada OOM-Kill (Out of Memory), o kernel escreve um bloco detalhado no buffer circular:

dmesg -T | grep -iE 'out of memory|oom-kill'

Se não vier nenhuma linha e o comando terminar com o código de saída 1, não houve nenhum OOM do kernel desde o último arranque do sistema. Esse código de saída não é um erro, é apenas a resposta habitual do grep quando não encontra nada. Se vier alguma coisa, o aspeto é este nos quatro sistemas aqui tratados:

mariadbd invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/mariadb.service,task=mariadbd,pid=1043,uid=107
Out of memory: Killed process 1043 (mariadbd) total-vm:2894760kB, anon-rss:1583204kB, file-rss:0kB, shmem-rss:0kB, UID:107 pgtables:3820kB oom_score_adj:0

Há três pormenores importantes. Primeiro: invoked oom-killer nomeia o processo que fez o pedido de memória, não necessariamente o culpado. Segundo: o processo terminado aparece na linha Killed process. Terceiro: o que conta é o anon-rss, ou seja, a memória anónima realmente ocupada. O total-vm é espaço de endereçamento reservado e, com Java ou Go, costuma ser um múltiplo daquele valor, pelo que não diz nada.

Se executar o dmesg sem ser root, o Debian 12, o Debian 13, o Ubuntu 22.04 e o Ubuntu 24.04 respondem com dmesg: read kernel buffer failed: Operation not permitted, porque kernel.dmesg_restrict está a 1 em todos eles. Trabalhe portanto com sudo ou como root. Se a mensagem se mantiver mesmo como root, está dentro de um container LXC ou OpenVZ que não tem acesso ao buffer circular do sistema anfitrião. Nesse caso só o segundo caminho, através do journalctl -k, o leva mais longe.

Depois de um reinício, o buffer circular está vazio. Daí o segundo caminho, através do journal, e é aqui que está a armadilha que a maioria dos guias ignora:

journalctl -k --grep "Out of memory"

O -k implica -b e mostra, por isso, exclusivamente o arranque atual. Se a máquina foi reiniciada depois do incidente, este comando não devolve nada, apesar de o acontecimento ter sido registado. Para o arranque anterior ou para um determinado período:

journalctl -k -b -1 --grep "Out of memory"
journalctl --since "7 days ago" --grep "Out of memory|oom-kill"

Se o primeiro dos dois comandos responder No journal boot entry found for the specified boot (-1), é porque no journal simplesmente não está guardado nenhum arranque anterior. Também isso não é um erro, é o caso normal num sistema cujo journal só regista desde o arranque atual.

Isto só funciona se o journal for de facto persistente. Verifique:

ls -d /var/log/journal

No Debian e no Ubuntu este diretório existe de origem, pelo que o teste dá quase sempre resultado e o mkdir seguinte fica sem efeito, sem estragar nada. Se, excecionalmente, o diretório não existir, o journal fica apenas em /run e perde-se a cada reinício. Isso recupera-se com dois comandos:

mkdir -p /var/log/journal
systemctl restart systemd-journald

OOM do kernel ou limite do systemd? Duas causas diferentes

Esta distinção decide se o swap chega sequer a servir para alguma coisa. Repare no campo constraint da mensagem do kernel.

CONSTRAINT_NONE significa: o sistema inteiro ficou sem memória. Aqui o swap ajuda.

CONSTRAINT_MEMCG significa: apenas um control group ultrapassou o seu limite, o resto do sistema tinha memória de sobra. Aqui o swap não ajuda, aqui é preciso aumentar o limite ou tornar a aplicação mais poupada. Estas mortes também se reconhecem no próprio serviço:

systemctl status mariadb

Se ali aparecer Main process exited, code=killed, status=9/KILL e, mais abaixo, Failed with result 'oom-kill', foi um OOM-Kill. Se houve ou não um limite de cgroup em jogo, revela-o um ficheiro de contadores. Ele pressupõe o cgroup v2, que é o padrão nas quatro distribuições; com o antigo cgroup v1 o caminho não existe:

cat /sys/fs/cgroup/system.slice/mariadb.service/memory.events
systemctl show mariadb -p MemoryMax -p MemoryHigh

Um valor maior do que zero em oom_kill, em conjunto com um MemoryMax definido, é a prova de um limite local.

Há ainda um terceiro candidato, muitas vezes esquecido porque não escreve absolutamente nada no dmesg: o systemd-oomd. Este serviço trabalha em espaço de utilizador, avalia o indicador de pressão PSI e termina control groups inteiros antes de o kernel sequer intervir. A sua mensagem no journal diz, no essencial, Killed /system.slice/... due to memory pressure for /system.slice being 60.00% > 50.00% for > 20s with reclaim activity. Verifique se está a correr:

systemctl is-active systemd-oomd

No Ubuntu, o systemd-oomd vem instalado e ativo de origem desde a 22.04; no Debian não faz parte da instalação padrão. Um inactive num servidor Debian é portanto expectável e não indica qualquer erro.

Importante para mais à frente: o systemd-oomd também pode disparar porque o swap está a encher. Com o oomd ativo, mais swap pode portanto provocar as interrupções mais cedo em vez de mais tarde.

Antes do swap: quanta memória falta realmente

Dois minutos de medição poupam uma decisão errada.

free -h

Só interessa a coluna available, não a free. A cache conta como disponível e é libertada quando for preciso. Quem lê a coluna free julga que qualquer sistema saudável está sobrecarregado.

ps -eo pid,comm,rss,%mem --sort=-rss | head -n 11
systemd-cgtop --order=memory -b -n 1

O primeiro comando mostra os maiores processos individuais, o segundo agrupa por serviços. Na prática aparecem ali quase sempre os mesmos três suspeitos: o MariaDB com um innodb_buffer_pool_size demasiado alto, o PHP-FPM com um pm.max_children exagerado e uma JVM com um -Xmx demasiado generoso.

Como ponto de partida para o tamanho do ficheiro de swap, esta tabela chega. Num servidor, maior não é melhor, porque um swap que chega mesmo a ser usado por completo torna a máquina impossível de operar.

Memória RAMFicheiro de swap razoável
1 GB1 a 2 GB
2 GB2 GB
4 a 8 GB2 a 4 GB
16 GB ou mais4 GB, raramente mais

Criar o ficheiro de swap

Primeiro convém ver se já existe swap. Os servidores Ubuntu instalados a partir da ISO trazem muitas vezes já um /swap.img, o Debian do instalador traz normalmente uma partição de swap a sério. As imagens cloud de ambas as distribuições costumam não trazer rigorosamente nada.

swapon --show

Se a saída ficar vazia, não há swap nenhum. A seguir verifique o sistema de ficheiros, porque é dele que depende o procedimento:

findmnt -no FSTYPE -T /

Com ext4 ou xfs pode seguir logo em frente. Crie o ficheiro com dd, não com fallocate. O fallocate é mais rápido, mas, consoante o sistema de ficheiros e o kernel, produz um ficheiro com zonas não escritas, e o swapon rejeita-o. O dd escreve zeros a sério e funciona em todo o lado:

dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
chmod 600 /swapfile

Antes do passo seguinte compensa uma verificação que muita gente salta. Se debaixo de /swapfile já estiver um swap ligado, por exemplo de uma tentativa anterior ou da configuração de origem da imagem, o mkswap recusa o trabalho com mkswap: error: /swapfile is mounted; will not make swapspace. O que conta aqui é apenas se o caminho consta como ativo em /proc/swaps. Portanto, veja primeiro e, se for o caso, desligue:

swapon --show
swapoff /swapfile

Se em swapon --show não aparecer nenhuma linha com /swapfile, pode saltar o swapoff. Depois disso, escreva a área de swap:

mkswap /swapfile

O mkswap responde com Setting up swapspace version 1, size = 2 GiB (2147479552 bytes) e um UUID novo. Só a seguir é que se liga:

swapon /swapfile

A ordem não é negociável. chmod antes de mkswap, mkswap antes de swapon, e um eventual swapoff antes de tudo o resto.

Como reconhecer que resultou mesmo

O facto de o swapon terminar sem mensagem de erro ainda não prova nada. A prova são estas duas saídas:

swapon --show
free -h

O swapon --show tem de produzir uma linha com /swapfile, file, o tamanho e uma prioridade. No free -h, a linha Swap: tem de ter saltado de 0B para o novo tamanho. Se uma das duas saídas ficar na mesma, o swap não está ativo, independentemente do que o comando tenha dito antes.

Tornar permanente sem pôr o arranque em risco

Um swapon não sobrevive a um reinício. A entrada pertence ao /etc/fstab, e é precisamente ali que se destroem servidores. Primeiro, uma cópia de segurança:

cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Repare nos dois sinais de maior. Um único > substitui o ficheiro inteiro, e a partir daí a máquina deixa de arrancar como deve ser. A seguir verifique a sintaxe antes de reiniciar:

findmnt --verify

Um aviso é aqui perfeitamente normal e não é motivo para voltar a retirar a linha: [W] non-bind mount source /swapfile is a directory or regular file. Com um ficheiro de swap isso é inevitável, e mesmo assim o comando termina com valor de retorno 0. Se, além disso, faltar ao ficheiro um cabeçalho de swap válido, junta-se-lhe [W] cannot detect on-disk filesystem type, e aí esqueceu-se mesmo de um mkswap.

Se já tiver ligado o swap manualmente mais acima com swapon /swapfile, ele já está ativo. Caso contrário, o swapon -a ativa todas as entradas do fstab sem ser preciso reiniciar. O verdadeiro teste, porém, é outro. O systemd gera uma unit própria a partir de cada linha do fstab; para /swapfile chama-se swapfile.swap. Se essa unit aparecer e estiver ativa, o swap é garantidamente montado no arranque seguinte:

systemctl daemon-reload
systemctl list-units --type swap

O que se espera é uma linha swapfile.swap loaded active active Swap. Se ela faltar, a linha do fstab não está correta e um reinício acabaria sem swap. Só quando isto estiver certo é que compensa reiniciar, com a verificação subsequente através de swapon --show.

Definir a swappiness corretamente

O parâmetro de kernel vm.swappiness controla com que facilidade as páginas anónimas vão para o swap em vez de se descartar cache de ficheiros. O valor por omissão é o mesmo em Debian 12, Debian 13, Ubuntu 22.04 e Ubuntu 24.04, ou seja, 60:

cat /proc/sys/vm/swappiness

Desde o kernel 5.8 o intervalo de valores vai de 0 a 200, e as quatro distribuições estão acima dessa versão. Dois equívocos frequentes: vm.swappiness=0 não desliga o swap, apenas evita a paginação preventiva e deixa o kernel recorrer ao swap na mesma antes de matar seja o que for. E um valor baixo não torna um sistema mais rápido quando o que falta é mesmo memória.

Valores razoáveis: 10 a 20 em servidores de base de dados, 60 em servidores web mistos, 100 ou mais se usar zram. De forma permanente, isso pertence a um ficheiro próprio em /etc/sysctl.d/, não ao /etc/sysctl.conf, que fica no caminho das atualizações de pacotes:

echo 'vm.swappiness = 10' > /etc/sysctl.d/99-swappiness.conf
sysctl --system
cat /proc/sys/vm/swappiness

O terceiro comando é o controlo do resultado. O sysctl --system lê todos os diretórios por uma ordem fixa, e um ficheiro já existente com um número mais alto pode sobrepor-se ao seu valor.

Quando corre mal: as mensagens de erro na íntegra

swapon: /swapfile: insecure permissions 0644, 0600 suggested. É apenas um aviso, o swap funciona à mesma. Ainda assim, corrija, caso contrário qualquer utilizador pode ler o conteúdo dos processos que foram para o swap: chmod 600 /swapfile.

swapon: /swapfile: swapon failed: Invalid argument O erro mais frequente. Ou se esqueceu o mkswap, ou o ficheiro tem buracos. No log do kernel aparece então, adicionalmente, swapon: swapfile has holes. Solução: apagar o ficheiro e voltar a criá-lo com dd em vez de fallocate.

Em btrfs aplica-se o mesmo erro, acrescido de BTRFS warning: swapfile must not be copy-on-write. Aqui a ordem é outra: o ficheiro tem de ser criado vazio e marcado como não copy-on-write antes de ser preenchido. Uma advertência logo à partida, que pode decidir o destino de um servidor em produção: o truncate -s 0 e o rm também são executados quando debaixo desse caminho ainda está montado um swap ativo. O kernel passa depois a apontar para blocos que já não existem. Por isso, se o swapon --show apresentar o caminho como ativo, é obrigatório um swapoff /swapfile antes.

truncate -s 0 /swapfile
chattr +C /swapfile

Depois disso, preencher com dd como de costume, chmod 600, mkswap, swapon. Em subvolumes comprimidos e em snapshots o swap continua a não funcionar.

swapon: /swapfile: swapon failed: Operation not permitted Está dentro de um container. LXC, OpenVZ e Docker partilham o kernel do anfitrião e não podem ligar um swap próprio. Verifique com:

systemd-detect-virt

Se o comando indicar kvm, qemu ou none, está a correr um kernel próprio e o swap é possível. Se indicar lxc, openvz ou docker, só ajuda mais memória ou um produto com virtualização completa. Os servidores root KVM e os servidores dedicados da KernelHost trazem um kernel próprio, e ali o swap pode ser configurado sem restrições.

dd: error writing '/swapfile': No space left on device O disco está demasiado cheio. Primeiro df -h, depois remova o ficheiro escrito pela metade com rm /swapfile, caso contrário continua a ocupar espaço. Também aqui vale a mesma regra: se o swapon --show mostrar o caminho como ativo, um swapoff /swapfile tem de vir antes da eliminação.

O sistema deixa de arrancar e aparece a consola de emergência. É quase sempre uma gralha no /etc/fstab. Inicie sessão através da consola na área de cliente, depois mount -o remount,rw /, retire a linha errada ou volte a copiar o /etc/fstab.bak, e reinicie. Foi exatamente para isto que a cópia de segurança foi criada antes.

Diferenças entre Debian 13, Debian 12, Ubuntu 24.04 e 22.04

Os comandos são idênticos nos quatro sistemas, o estado inicial não.

  • Swap já existente: o Ubuntu Server instalado a partir da ISO cria muitas vezes um /swap.img, o Debian do instalador cria uma partição de swap. As imagens cloud de ambas as distribuições vêm sem swap. Comece sempre por swapon --show.
  • systemd-oomd: no Ubuntu vem instalado e ativo de origem desde a 22.04, nas duas versões do Debian não faz parte da instalação padrão. Isso explica porque é que software idêntico pode morrer de maneiras diferentes em dois sistemas aparentemente iguais.
  • Base de dados: o Debian 12 e o Debian 13 não fornecem mysql-server, ali corre sempre MariaDB (10.11 no Debian 12, 11.8 no Debian 13). O Ubuntu 22.04 e o 24.04 têm ambos. Os valores por omissão de innodb_buffer_pool_size diferem em conformidade, e é precisamente esse valor a causa de OOM mais frequente em servidores pequenos.
  • Java: o Debian 12 só conhece o OpenJDK 17, o Debian 13 só o OpenJDK 21, o Ubuntu 22.04 e o 24.04 cobrem do 8 ao 21. Uma JVM sem -Xmx definido fica por omissão com um quarto da memória, e com várias instâncias o OOM-Kill está garantido à partida.
  • Mensagens do kernel: o formato e o texto da mensagem de OOM são iguais nos quatro sistemas, os exemplos acima servem em todo o lado.
  • swappiness: 60 como valor por omissão em todos.

Quando o swap ajuda e quando apenas adia o problema

O swap ajuda de forma fiável em picos curtos, por exemplo durante um backup, uma atualização de pacotes ou uma importação noturna. Ajuda em serviços que ocupam muita memória e depois passam dias sem fazer nada, porque essas páginas podem perfeitamente ficar no disco. E, em caso de emergência, dá-lhe minutos durante os quais a sessão SSH ainda responde e é possível intervir, em vez de ficar diante de um servidor morto.

O swap não ajuda quando a necessidade permanente está simplesmente acima da memória instalada. Aí o sistema entra em thrashing: move páginas para o swap e de volta sem parar, a load sobe para valores de dois dígitos, a utilização do CPU mantém-se baixa e tudo espera por I/O. Na prática, isso é pior do que um OOM-Kill limpo, porque nem sequer o início de sessão consegue passar. Dois comandos mostram se está nesse estado:

vmstat 1 5
test -e /proc/pressure/memory && cat /proc/pressure/memory

No vmstat contam as colunas si e so. Valores de três dígitos de forma continuada significam movimento ativo de páginas para dentro e para fora do swap. Em /proc/pressure/memory o que decide é o full avg10: valores acima de 10 significam que o sistema inteiro passa dez por cento do tempo à espera de memória. Acima de 40, a máquina está praticamente morta. O ficheiro, porém, só existe se a Pressure Stall Information estiver ativa no kernel. Os kernels padrão do Debian e do Ubuntu trazem-na, outros kernels não necessariamente. Daí a salvaguarda com test -e: se o ficheiro faltar, a saída fica vazia, em vez de tomar um No such file or directory por uma avaria. A PSI pode ser acrescentada através do parâmetro de arranque do kernel psi=1.

Que processos estão realmente no swap, responde-o esta linha:

grep VmSwap /proc/*/status | sort -k2 -rn | head

O swap também não serve de nada contra um limite de cgroup (CONSTRAINT_MEMCG), contra memória fixada com mlock e contra uma JVM cujo heap esteja configurado acima da RAM existente. O garbage collector percorre regularmente todo o heap e traz de imediato de volta cada página que tinha ido para o swap.

As três ferramentas para quando o swap não chega

O zram cria uma área de swap comprimida dentro da própria RAM. É várias ordens de grandeza mais rápido do que um ficheiro no disco e, consoante os dados, proporciona 20 a 40 por cento de memória efetiva adicional:

apt update
apt install -y zram-tools

A configuração faz-se em /etc/default/zramswap através de ALGO=zstd e PERCENT=50, seguida de systemctl restart zramswap. A verificação faz-se com zramctl e novamente com swapon --show, onde passa a aparecer /dev/zram0. Com zram, o vm.swappiness deve subir para 100 a 180, porque aqui enviar páginas para o swap é barato.

O earlyoom intervém antes de o kernel congelar o sistema e termina processos de forma dirigida, em vez de por heurística:

apt install -y earlyoom

Em /etc/default/earlyoom define, através de EARLYOOM_ARGS, os limiares e protege os processos importantes, por exemplo com -m 5 -s 5 --avoid '(^|/)(sshd|systemd)$'. Assim o acesso por SSH continua disponível enquanto o processo que devora a memória é abatido.

Limites fixos por serviço são a solução mais limpa quando um determinado serviço se descontrola com regularidade. Através de systemctl edit mariadb introduz MemoryHigh=1200M e MemoryMax=1500M. O serviço passa então a ser travado e, se for preciso, terminado sozinho, em vez de arrastar o servidor inteiro consigo. Verificação através de systemctl show mariadb -p MemoryMax.

A correção verdadeira continua a ser, na maioria dos casos, a configuração da aplicação: innodb_buffer_pool_size num valor realista, pm.max_children em função da memória realmente necessária por worker, um -Xmx definido para cada JVM. O swap é a rede de segurança, não a solução.

Reverter, caso não sirva

O caminho de volta é curto e deve seguir esta ordem:

swapoff /swapfile

Com o swap muito ocupado, o comando pode demorar vários minutos, porque todas as páginas têm de voltar para a RAM. Se der em swapoff: /swapfile: swapoff failed: Cannot allocate memory, é porque não há espaço na RAM para o transporte de volta e há que parar serviços primeiro. Só depois de um swapoff bem-sucedido é que se retira a linha do fstab e se apaga o ficheiro:

rm /swapfile
swapon --show

Apagar o ficheiro enquanto ele ainda está montado leva a um sistema cuja gestão de memória aponta para um inode que já não existe. Isso acaba mal. No fim, mais um free -h e um reinício como contraprova.

Perguntas frequentes

De quanto swap preciso num servidor?
Em servidores já não vale a velha regra do dobro da memória. O razoável são 1 a 2 GB com 1 GB de RAM, 2 GB com 2 GB de RAM e 2 a 4 GB com 4 a 8 GB de RAM. A partir de 16 GB chegam 4 GB. Um swap que chega mesmo a ser usado por completo torna o sistema impossível de operar, por causa do movimento constante de páginas, ou seja, mais espaço não é reserva nenhuma, é apenas uma agonia mais longa.
Porque é que o journalctl -k não mostra nenhum OOM-Kill, apesar de ter havido um?
Porque a opção -k implica -b e mostra por isso apenas o arranque atual. Se o servidor foi reiniciado depois do incidente, não vê nada. Use journalctl -k -b -1 para o arranque anterior ou journalctl --since "7 days ago" --grep "Out of memory|oom-kill" para um período de tempo. É preciso um journal persistente, verificável com ls -d /var/log/journal, que no Debian e no Ubuntu existe de origem. Se o journalctl -k -b -1 responder em vez disso "No journal boot entry found for the specified boot (-1)", é porque simplesmente não há nenhum arranque anterior guardado.
Como reconheço se foi o kernel ou um limite do systemd a terminar o processo?
Pelo campo constraint da mensagem do kernel. CONSTRAINT_NONE significa que o sistema inteiro ficou sem memória, e aí o swap ajuda. CONSTRAINT_MEMCG significa que um control group concreto ultrapassou o seu limite, e aí o swap não ajuda. Verifique adicionalmente systemctl show SERVICO -p MemoryMax, bem como o contador oom_kill no ficheiro memory.events do respetivo control group.
Porque é que o swapon falha com "Invalid argument"?
Ou se esqueceu o mkswap, ou o ficheiro foi criado com fallocate e contém zonas não escritas. No log do kernel aparece então swapon: swapfile has holes. Crie antes o ficheiro com dd if=/dev/zero. Em btrfs, o ficheiro tem ainda de ser marcado previamente como não copy-on-write com truncate -s 0 e chattr +C, e isso só depois de um swapoff /swapfile, porque esvaziar um ficheiro de swap montado faz o kernel apontar para blocos que já não existem. Se for o próprio mkswap a recusar com "/swapfile is mounted; will not make swapspace", é porque debaixo desse caminho continua montado um swap ativo, visível com swapon --show.
Posso configurar swap dentro de um container?
Não. LXC, OpenVZ e Docker usam o kernel do sistema anfitrião e não podem ativar um swap próprio, o swapon responde a isso com "Operation not permitted". Verifique com systemd-detect-virt: com kvm, qemu ou none há um kernel próprio e o swap é possível. Dentro de um container só ajuda mais memória ou a mudança para uma virtualização completa.
vm.swappiness=0 significa que deixa de haver qualquer paginação para o swap?
Não. Desde o kernel 3.5 esse valor apenas evita a paginação preventiva. Se a memória escassear, o kernel recorre ao swap na mesma antes de terminar um processo. Quem quiser desligar mesmo o swap tem de usar o swapoff. O intervalo de valores vai de 0 a 200 em todos os kernels atuais do Debian e do Ubuntu, e o valor por omissão é 60 em todos.

Linux Swap Resolução de problemas Debian Ubuntu Administração de servidores