Consumo excessivo de memória devido à pré-compilação de assets

Olá pessoal,

Nós temos executado nossas próprias instâncias do Discourse no OpenShift nos últimos anos e, nos últimos meses (a partir de janeiro de 2026, aproximadamente, e mais ou menos alinhado com a nova abordagem explicada em Introducing pre-compiled JS assets for self-hosters e Introducing a new build system for plugins), observamos o seguinte cenário:

Ao pré-compilar ativos no momento da construção (bundle exec rake assets:precompile:build), essa operação agora falha e consome mais de 20 GB:

...
gem install prometheus_exporter -v 2.2.0 -i /var/www/discourse/plugins/discourse-prometheus/gems/3.4.7 --no-document --ignore-dependencies --no-user-install
Instalação bem-sucedida do prometheus_exporter-2.2.0
1 gem instalada
O nome do plugin é 'msgraph-polling', mas o diretório do plugin se chama 'msgraph-poll-discourse-plugin'
[assemble_ember_build] Nenhum arquivo de informações de construção existente encontrado.
Baixando e extraindo https://get.discourse.org/discourse-assets/2026.5.0-latest-03484cbd/production.tar.gz...
  % Total    % Received % Xferd  Average Speed   Time     Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 20.1M  100 20.1M    0     0  22.5M      0 --:--:-- --:--:-- --:--:-- 22.5M
Ativos pré-construídos baixados e extraídos com sucesso.
[assemble_ember_build] Reutilizando a construção central do Ember existente. Tudo pronto.
O nome do plugin é 'msgraph-polling', mas o diretório do plugin se chama 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compilando 49 plugins...

# trava aqui por muito tempo

Analisando o consumo de memória, vemos:

Every 1.0s: free -h                             webapp-test-discourse-689b5fcb4d-fd2dp-debug-b7nn2: Mon May  4 14:15:57 2026

               total        used        free      shared  buff/cache   available
Mem:            28Gi        26Gi       596Mi       524Mi       2.1Gi       1.7Gi
Swap:             0B          0B          0B

Enquanto antes era bastante rápido, com consumo moderado de memória.

Tentamos definir variáveis de ambiente como CI=1 e NODE_OPTIONS="--max-old-space-size=X", mas nada parece ajudar a conter esse consumo de memória.

Alguém está enfrentando o mesmo problema e, se sim, como vocês resolveram?

Muito obrigado!

Ismael

Você consegue compartilhar uma lista dos plugins que você instalou?

Sua memória total é de 4 GB? Se sim, você tem uma configuração de swap?

Você pode compartilhar as especificações do servidor?

Olá David,

A lista de plugins extras é:

#   - Botões de negociação (usados no Marketplace)
          - git clone --depth=1 https://github.com/jannolii/discourse-topic-trade-buttons.git
          #   - Buscas salvas
          - git clone --depth=1 https://github.com/discourse/discourse-saved-searches.git
          #   - Discourse Akismet
          - git clone --depth=1 https://github.com/discourse/discourse-akismet.git
          #   - Prometheus
          - git clone --depth=1 https://github.com/discourse/discourse-prometheus.git
          #   - Documentação do Discourse
          - git clone --depth=1 https://github.com/discourse/discourse-docs.git
          #   - Poll do MSGraph
          - git clone --depth=1 https://github.com/CERN/msgraph-poll-discourse-plugin.git

Abraços,

Ismael

Olá Heliosurge,

Os nós possuem 8 vCPUs e 30 GiB de RAM. Em condições normais, um fórum costumava consumir 1 vCPU e no máximo 2-3 GB de RAM (incluindo a pré-compilação).

Não há swap configurado. Entendi que o swap é utilizado aqui quando há restrições de memória, o que não deveria ser o caso aqui. No entanto, estou mais “preocupado” com a quantidade de memória consumida, já que isso nunca foi um problema antes.

Abraços,

Ismael

Bem, a especificação do seu servidor não deve precisar de swap. O membro da equipe David aqui provavelmente poderá ajudar melhor.

O uso de memória diminui assim que a tarefa assets:build é concluída?

Olá @david,

O uso de memória diminui assim que a tarefa assets:build é concluída?

Não. Investiguei mais a fundo e há algo estranho.

Antes de executar o precompiling:build, esta é a lista de plugins:

/var/www/discourse$ ls plugins/
automation           discourse-akismet           discourse-data-explorer  discourse-hcaptcha           discourse-microsoft-auth  discourse-post-voting  discourse-saved-searches       discourse-user-notes           styleguide
chat                 discourse-apple-auth        discourse-details        discourse-lazy-videos        discourse-narrative-bot   discourse-presence     discourse-solved               discourse-zendesk-plugin
checklist            discourse-assign            discourse-docs           discourse-local-dates        discourse-oauth2-basic    discourse-prometheus   discourse-subscriptions        footnote
discourse-adplugin   discourse-cakeday           discourse-gamification   discourse-login-with-amazon  discourse-openid-connect  discourse-reactions    discourse-templates            msgraph-poll-discourse-plugin
discourse-affiliate  discourse-calendar          discourse-github         discourse-lti                discourse-patreon         discourse-rewind       discourse-topic-trade-buttons  poll
discourse-ai         discourse-chat-integration  discourse-graphviz       discourse-math               discourse-policy          discourse-rss-polling  discourse-topic-voting         spoiler-alert

Percebi o seguinte comportamento após depurar o código.

/var/www/discourse$ script/rails runner "AssetProcessor.ember_version"
O nome do plugin é 'msgraph-polling', mas o diretório do plugin se chama 'msgraph-poll-discourse-plugin'

# trava aqui para sempre

O AssetProcessor.ember_version corresponde à linha discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub

Então, fiz algumas modificações neste arquivo (em anexo), basicamente para imprimir onde ele trava durante o processamento e para remover o AssetProcessor.ember_version em discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub, colocando apenas 5 para continuar a geração do hash hexadecimal.

Em seguida, reduzi o paralelismo para 1 para facilitar as coisas em discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub (parallel_count = [Etc.nprocessors, 1].min).

Após isso, executei bundle exec rake assets:precompile:build, resultando no seguinte:

/var/www/discourse$ bundle exec rake assets:precompile:build
O nome do plugin é 'msgraph-polling', mas o diretório do plugin se chama 'msgraph-poll-discourse-plugin'
[assemble_ember_build] Reutilizando a build principal do Ember existente. Tudo pronto.
O nome do plugin é 'msgraph-polling', mas o diretório do plugin se chama 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compilando 49 plugins...
Compilando automation...
final de files.sort
final de files.sort
        hex_digest 103dc9ebebb80a7065cb8dd41fb3356b30f151f7
########### recursivo

# trava aqui para sempre, consumindo toda a memória

Suspeito que isso possa estar relacionado ao valor de ulimit (que configuramos como unlimited em vez de algo como ulimit -n 1048576;), dado que você está abrindo um grande número de arquivos e armazenando seu conteúdo na memória (por meio de chamadas recursivas).

Avise-me se isso lhe soa familiar ou se você tem alguma outra pista sobre qual pode ser o problema.

Abraços,

Ismael

js_manager.rb.txt (7.7 KB)

Sempre é aconselhável ter swap. É uma ótima ideia permitir que o kernel faça overcommit. Isso pode reduzir substancialmente suas necessidades de pico de memória.

Resolva essas duas coisas e tente novamente. Para overcommit, consulte

Em termos de diagnóstico, pode ser útil verificar o dmesg para eventos de OOM (Out of Memory), o que você pode fazer depois, e também executar o vmstat no momento da travagem.

vmstat 5 5

Aqui está meu conselho geral de diagnóstico:

Olá @Ed_S,

Obrigado pela sua mensagem.

Fiz a diagnose e este é o resultado que obtive:

vmstat 5 200
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  0      0 19595924    104 3919416    0    0  4173    32  439 1040  5  1 93  0  0
 1  0      0 19595924    104 3919416    0    0     0   154 4249 6449  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0     0    39 4399 6778  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0    12    75 5414 8640  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0    51    69 4248 6637  1  1 99  0  0
 1  0      0 19595924    104 3919416    0    0     0    83 4441 6784  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0     9    53 6111 9254  2  1 97  0  0
 1  0      0 19595924    104 3919416    0    0     0   887 4854 7373  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0     0    40 4705 7319  1  1 98  0  0
 1  0      0 19595924    104 3919416    0    0     0    37 4701 7305  1  1 98  0  0
# iniciamos a pré-compilação...
 3  0      0 19595924    104 3919416    0    0   124   902 8292 10254 19  5 75  0  0
 2  0      0 19595924    104 3919416    0    0 43073  6829 13702 16200 11  4 82  4  0
 2  0      0 19595924    104 3919416    0    0 19624   815 12340 15581 10  4 83  3  0
 2  0      0 19595924    104 3919416    0    0  1818  3953 7554 9248 13  3 84  0  0
 2  0      0 19595924    104 3919416    0    0     0    99 7475 8661 16  2 82  0  0
 2  0      0 19595924    104 3919416    0    0     0    52 7634 9084 13  2 84  0  0
 2  0      0 19595924    104 3919416    0    0   115   585 6843 8121 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0 13139 7254 8444 13  2 84  0  0
 2  0      0 19595924    104 3919416    0    0     3  1305 8740 11091 14  2 83  0  0
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 5  0      0 19595924    104 3919416    0    0   465  9798 8403 9279 13  2 85  0  0
 3  0      0 19595924    104 3919416    0    0     6    99 7264 8993 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0    96 7190 8627 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0    66 6869 8299 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0   109 7075 8521 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     3    78 8763 11295 14  2 83  0  0
 2  0      0 19595924    104 3919416    0    0     0  3075 7337 8358 13  2 85  0  0
 4  0      0 19595924    104 3919416    0    0     6   133 7016 8697 13  2 85  0  0
 3  0      0 19595924    104 3919416    0    0     0    45 7005 8370 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0   134 7330 9011 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0    26    86 7239 8747 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0     0   127 8809 11618 15  3 83  0  0
 2  0      0 19595924    104 3919416    0    0     6  1473 7142 8352 13  2 85  0  0
 2  0      0 19595924    104 3919416    0    0  2021   136 8041 10138 13  3 84  0  0
 2  0      0 19595924    104 3919416    0    0  4457   664 6913 7927 12  3 84  0  0

O consumo de memória disparou:

               total        used        free      shared  buff/cache   available
Mem:            28Gi        26Gi       460Mi       518Mi       2.3Gi       1.8Gi
Swap:             0B          0B          0B

E travou durante a compilação dos plugins:

/var/www/discourse$ bundle exec rake assets:precompile:build

gem install prometheus_exporter -v 2.2.0 -i /var/www/discourse/plugins/discourse-prometheus/gems/3.4.7 --no-document --ignore-dependencies --no-user-install
Instalação bem-sucedida do prometheus_exporter-2.2.0
1 gem instalado

Uma nova versão do RubyGems está disponível: 3.6.9 → 4.0.11!
Execute `gem update --system 4.0.11` para atualizar sua instalação.

O nome do plugin é 'msgraph-polling', mas o diretório do plugin está nomeado como 'msgraph-poll-discourse-plugin'
[assemble_ember_build] Nenhum arquivo de informações de build existente encontrado.
Buscando e extraindo https://get.discourse.org/discourse-assets/2026.5.0-latest-6b98fe35/production.tar.gz...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--  0     0    0     0    0     0      0      0 --:--:-- --:--  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 20.1M  100 20.1M    0     0  20.8M      0 --:--:-- --:--:-- --:--:-- 20.8M
Ativos pré-compilados baixados e extraídos com sucesso.
[assemble_ember_build] Reutilizando build central do Ember existente. Tudo pronto.
O nome do plugin é 'msgraph-polling', mas o diretório do plugin está nomeado como 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compilando 49 plugins...

# trava aqui até gerar um OOMKilled

Você consegue modificar o ulimit e verificar se o comportamento acima é reproduzido?

Abraços,

Ismael

Ah, você viu um OOM, ótimo. Isso é definitivo. O ulimit não tem nada a ver com isso.

Adicione swap. Não há motivo para não fazer isso, exceto a falta de espaço em disco. Adicione 8 GB ou 16 GB e tente novamente. Você quer chegar a um estado funcional. Depois, se quiser, pode tentar medir qual processo está causando o problema.

Configure o overcommit. É uma boa prática, reduz problemas de pico de memória. Você não precisa entendê-lo ou justificá-lo, apenas faça. Faz parte de uma boa configuração do Linux. Verifique primeiro. É tão simples:

# uname -a
Linux ubuntu-4gb-hel1-1 6.8.0-110-generic #110-Ubuntu SMP PREEMPT_DYNAMIC
 Thu Mar 19 17:16:23 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux
# cat /proc/sys/vm/overcommit_memory
1

Olá pessoal,

Muito obrigado por todos os dicas que vocês nos forneceram, são muito apreciadas. Acreditamos que identificamos a causa raiz dos recentes problemas de memória.

Anteriormente, executar bundle exec rake assets:precompile:build no momento da construção (como root) não exigia ter o Redis nem conexão com o banco de dados. Esse comportamento mudou (ref: Introducing pre-compiled JS assets for self-hosters e Introducing a new build system for plugins).

Para acomodar isso, movemos a etapa bundle exec rake assets:precompile:build para um contêiner de inicialização em tempo de execução (antes de executar db:migrate, etc). Isso permite que ele seja executado como o usuário discourse, com o acesso necessário aos serviços tanto ao Redis quanto ao banco de dados.

No entanto, durante a execução, o processo entra em um loop em lib/plugin/js_manager.rb. Ao observar ps -fe, vemos o pnpm tentando repetidamente adicionar a si mesmo, o que leva à saturação de memória:

...
discour+     704     688  5 11:00 pts/0    00:00:00 node /usr/bin/pnpm -C=frontend/asset-processor node build.js
discour+     718     704  5 11:00 pts/0    00:00:00 node /usr/bin/pnpm add pnpm@10.28.0 --loglevel=error --allow-build=@pnpm
discour+     729     718  6 11:00 pts/0    00:00:00 node /usr/bin/pnpm add pnpm@10.28.0 --loglevel=error --allow-build=@pnpm
discour+     740     729  6 11:00 pts/0    00:00:00 node /usr/bin/pnpm add pnpm@10.28.0 --loglevel=error --allow-build=@pnpm
discour+     754     740  7 11:00 pts/0    00:00:00 node /usr/bin/pnpm add pnpm@10.28.0 --loglevel=error --allow-build=@pnpm
...
# e a lista começa a crescer e continua indefinidamente, provocando a saturação de memória

Em nossos testes, descobrimos que executar o contêiner de inicialização como root, em seguida, executar npm uninstall -g pnpm seguido de npm install -g pnpm@10.28.0, resolve o loop e permite que a compilação do plugin seja concluída com sucesso:

...
[Plugin::JsManager] Compilando 49 plugins...
[Plugin::JsManager] Compilação inicial dos plugins concluída em 5,82s

Portanto, antes de superengenhariar nossa infraestrutura e provavelmente alterar nosso design, acho que essa pergunta é mais para @david: há planos de restaurar o comportamento anterior para assets:precompile:build para que ele possa ser executado sem Redis ou conexão com o banco de dados (semelhante ao que você está fazendo com o fluxo DISCOURSE_DOWNLOAD_PRE_BUILT_ASSETS: 0)?

Como nota lateral e por curiosidade: por que executar o processo node como um usuário não root desencadeia esse loop recursivo de instalação do pnpm, enquanto executá-lo como root parece evitá-lo?

Abraços,
Ismael