Memória, zRAM e swap no Linux com agentes no terminal
Atualizado em 7 de outubro de 2026
Congelamentos no Linux com agentes no terminal: a zRAM de 32 GB que piorou os travamentos, /tmp na RAM com o pnpm e swap em dois níveis no Btrfs.
O meu notebook começou a travar com frequência depois que passei a rodar vários agentes de IA no terminal. As ventoinhas ficavam no máximo, a máquina esquentava e o sistema congelava no meio de chamadas do Google Meet. Ao mesmo tempo, o /tmp vivia com quase 6 GB ocupados sem motivo aparente.
A máquina é um notebook com processador Intel Core Ultra 5 125H, 16 GB de RAM e Arch Linux em Btrfs. Quem enfrenta falta de memória costuma aumentar o espaço de swap. Fiz isso e subi a zRAM para 32 GB. O resultado foi o oposto do esperado, pois os travamentos pioraram.
A investigação dos travamentos
O primeiro passo foi consultar os logs do systemd-oomd, o serviço que encerra processos antes que o kernel congele por falta de memória. Encontrei o registro do travamento:
systemd-oomd: Marked app-... for killing due to memory used (14.8G / 16.1G)
and swap used (30.9G / 34.3G) being more than 90.00%
A memória física estava em 14,8 GB e a swap em 30,9 GB. O computador tinha atingido o limite simultâneo de RAM e swap.
A minha swap estava toda em zRAM. A zRAM não fica no disco. Ela cria um bloco de memória virtual compactado dentro da própria RAM física.
No meu sistema, a zRAM usava o algoritmo zstd, com compressão média de 3 para 1. Quando os agentes ocuparam 30,9 GB de swap virtual, o kernel precisou de cerca de 10 GB de RAM física só para guardar as páginas compactadas.
O computador tem 15,5 GB de RAM útil, então sobraram 5 GB para o ambiente gráfico, os navegadores, as chamadas de vídeo e os próprios agentes. As 18 threads do processador passaram a disputar ciclos compactando e descompactando memória com zstd. A CPU atingiu o limite térmico, derrubou o clock e a interface congelou.
O acúmulo invisível no /tmp
O segundo problema estava no /tmp. No Arch Linux, o /tmp é montado em tmpfs, que reside na RAM e pode consumir até metade da memória física (7,6 GB).
Ao medir as pastas com o du, encontrei o que ocupava 5,8 GB da partição:
/tmp/.pnpm-storecom 1,3 GB- 332 pastas com nomes aleatórios do Vitest somando 1,1 GB
- worktrees do Git criadas por agentes, com 1,8 GB
/tmp/node-compile-cachecom 467 MB
O pnpm guarda os pacotes em uma store global e usa hard links para não duplicar arquivos entre projetos. Hard links não atravessam sistemas de arquivos. Quando um agente cria um clone ou worktree dentro do /tmp, o pnpm detecta que a pasta está no tmpfs e não no /home. Sem poder criar os links, ele baixa uma cópia completa de todas as dependências para dentro de /tmp/.pnpm-store, que passa a ocupar 1,3 GB da RAM.
Cada suíte de testes do Vitest em modo SSR também criava uma pasta temporária com identificador único, e os arquivos ficavam para trás quando a execução era interrompida.
As correções
Dividi a solução em quatro partes: limpeza do /tmp, worktrees fora da RAM, swap em dois níveis e limites de processo e concorrência.
Worktrees fora da RAM e limpeza automática
O primeiro ajuste foi proibir worktrees em /tmp. O caminho padrão passou a ser ~/.cache/worktrees/, no disco principal. Nesse diretório, o pnpm consegue usar os hard links da store global sem ocupar RAM.
Para as pastas abandonadas não se acumularem, criei ~/.config/user-tmpfiles.d/worktrees.conf:
e %h/.cache/worktrees - - - 3d
Ativei o timer do usuário no systemd:
systemctl --user enable --now systemd-tmpfiles-clean.timer
O sistema agora apaga qualquer pasta em ~/.cache/worktrees sem acesso por mais de 3 dias.
Swap em dois níveis com prioridades
Para os congelamentos pararem sem as chamadas do Meet caírem por falta de memória, montei um swap em dois níveis:
flowchart LR accTitle: Swap em dois níveis accDescr: O kernel grava primeiro na zram0, 16 GB comprimidos em RAM com prioridade 100. O arquivo de 32 GB no SSD, com prioridade 0, só recebe dados quando a zRAM enche. K[Kernel] -->|grava primeiro| Z["zram0<br/>16 GB em RAM<br/>lzo-rle · pri=100"] K -->|reserva| S["/swap/swapfile<br/>32 GB no SSD<br/>Btrfs · pri=0"]
No Linux, o número de prioridade define onde o kernel grava primeiro. Como 100 é maior que 0, o sistema direciona as gravações para a zRAM. O arquivo no SSD fica com 0 bytes e só recebe dados quando a zRAM enche. É a margem de segurança.
Troquei o algoritmo da zRAM de zstd para lzo-rle. O lzo-rle comprime um pouco menos, mas gasta muito menos CPU.
No Btrfs com snapshots do Snapper, o arquivo de swap não pode ficar no subvolume raiz, pois snapshots não suportam arquivo de swap ativo. Criei um subvolume dedicado em /swap:
sudo btrfs subvolume create /swap
sudo btrfs filesystem mkswapfile --size 32g /swap/swapfile
sudo swapon --priority 0 /swap/swapfile
No /etc/fstab, a entrada ficou:
/swap/swapfile none swap defaults,pri=0 0 0
Limites de memória e concorrência no Node e no Vitest
O teto de heap de cada processo Node.js vem do --max-old-space-size, e o default fica na casa dos 4 GB. Quando os agentes rodam vários scripts de lint e build juntos, esse consumo soma rápido.
Defini o teto de 2 GB em ~/.config/environment.d/50-dev-limits.conf e no ~/.bashrc:
export NODE_OPTIONS="--max-old-space-size=2048"
export VITEST_MAX_THREADS=4
A variável VITEST_MAX_THREADS=4 impede que o Vitest lance 18 workers simultâneos durante os testes e mantém a CPU dentro do limite térmico do notebook.
Regras de concorrência para os agentes
No arquivo de instruções que todos os meus agentes leem, o ~/.agents/CONTEXT.md, adicionei uma regra explícita:
- **Limite de concorrência em máquina local**: Nunca execute mais de 2 subagents simultaneamente quando a tarefa envolver compilação (tsc/build), execução de testes (vitest/jest/playwright) ou navegadores headless.
Resultados
Depois das mudanças, o /tmp caiu de 5,8 GB para 120 MB em uso estável.
Com a zRAM em lzo-rle e o arquivo de 32 GB no SSD como reserva na prioridade 0, o notebook parou de travar e de derrubar chamadas no Google Meet. A temperatura do processador ficou dentro da faixa normal mesmo com várias suítes de teste rodando juntas.