استهلاك مفرط للذاكرة بسبب تجميع الأصول مسبقًا

مرحبًا بالجميع،

لقد قمنا بتشغيل نسخ خاصة من Discourse على OpenShift خلال السنوات الماضية، وخلال الأشهر الأخيرة (اعتبارًا من يناير 2026 تقريبًا، وبالتزامن مع النهج الجديد الموضح في Introducing pre-compiled JS assets for self-hosters و Introducing a new build system for plugins)، لاحظنا السيناريو التالي:

عند تجميع الأصول مسبقًا أثناء عملية البناء (bundle exec rake assets:precompile:build)، تفشل هذه العملية حاليًا وتستهلك أكثر من 20 جيجابايت:

...
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
Successfully installed prometheus_exporter-2.2.0
1 gem installed
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[assemble_ember_build] No existing build info file found.
Fetching and extracting 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
Prebuilt assets downloaded and extracted successfully.
[assemble_ember_build] Reusing existing core ember build. All done.
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compiling 49 plugins...

# stucks here for long

عند مراقبة استهلاك الذاكرة، نلاحظ ما يلي:

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

بينما كانت العملية سابقًا سريعة نسبيًا مع استهلاك معتدل للذاكرة.

لقد جربنا تعيين متغيرات بيئة مثل CI=1 و NODE_OPTIONS="--max-old-space-size=X"، لكن لا يبدو أن أيًا منها يساعد في الحد من هذا الاستهلاك للذاكرة.

هل يواجه أي شخص هذه المشكلة؟ وإذا كان الأمر كذلك، كيف تم حلها؟

شكرًا جزيلًا!

إسماعيل

هل يمكنك مشاركة قائمة الإضافات التي قمت بتثبيتها؟

هل إجمالي الذاكرة لديك 4 جيجابايت؟ إذا كان الأمر كذلك، هل قمت بإعداد مساحة تبديل (swap)؟

هل يمكنك مشاركة مواصفات الخادم؟

مرحبًا ديفيد،

قائمة الإضافات الإضافية هي:

#   - أزرار التداول (تُستخدم في السوق)
          - git clone --depth=1 https://github.com/jannolii/discourse-topic-trade-buttons.git
          #   - عمليات البحث المحفوظة
          - git clone --depth=1 https://github.com/discourse/discourse-saved-searches.git
          #   - ديسكورد أكسميت
          - git clone --depth=1 https://github.com/discourse/discourse-akismet.git
          #   - بروميثيوس
          - git clone --depth=1 https://github.com/discourse/discourse-prometheus.git
          #   - وثائق ديسكورد
          - git clone --depth=1 https://github.com/discourse/discourse-docs.git
          #   - استطلاع MSGraph
          - git clone --depth=1 https://github.com/CERN/msgraph-poll-discourse-plugin.git

مع أطيب التحيات،

إسماعيل

مرحبًا Heliosurge،

الوحدات (Nodes) تحتوي على 8 معالجات و30 جيجابايت من ذاكرة الوصول العشوائي. في الظروف العادية، كان المنتدى يستهلك معالجًا واحدًا و2-3 جيجابايت كحد أقصى من الذاكرة العشوائية (بما في ذلك التجميع المسبق).

لم يتم إعداد ذاكرة التبادل (Swap). لقد فهمت أن ذاكرة التبادل تُستخدم هنا عند وجود قيود على الذاكرة، وهو ما لا ينبغي أن يكون هو الحال هنا. ومع ذلك، فأنا أكثر “قلقًا” بشأن كمية الذاكرة المستهلكة، حيث لم يكن هذا هو الحال من قبل.

تحياتي،

إسماعيل

حسناً، مواصفات الخادم الخاصة بك لا يجب أن تحتاج إلى ذاكرة تبادلية. من المرجح أن يكون زميل الفريق ديفيد هنا هو الأقدر على المساعدة.

هل ينخفض استخدام الذاكرة بمجرد انتهاء مهمة assets:build؟

مرحبًا @david ،

هل ينخفض استخدام الذاكرة فورًا بعد انتهاء مهمة assets:build؟

لا. لقد بحثت أكثر ووجدت شيئًا غريبًا.

قبل تشغيل precompiling:build، كانت قائمة الإضافات كالتالي:

/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

لاحظت السلوك التالي بعد تصحيح الكود (debugging):

/var/www/discourse$ script/rails runner "AssetProcessor.ember_version"
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'

# يتوقف هنا إلى الأبد

يتوافق AssetProcessor.ember_version مع السطر discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub

لذلك قمت ببعض التعديلات على هذا الملف (مرفق)، بهدف طباعة المكان الذي يتعطل فيه أثناء المعالجة، وإزالة استدعاء AssetProcessor.ember_version في discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub واستبداله بالرقم 5 لمواصلة توليد الـ hex.

ثم قمت بتقليل التوازي إلى 1 لتبسيط الأمور في discourse/lib/plugin/js_manager.rb at latest · discourse/discourse · GitHub (parallel_count = [Etc.nprocessors, 1].min).

بعد ذلك، نفذت الأمر bundle exec rake assets:precompile:build، وكانت النتيجة كالتالي:

/var/www/discourse$ bundle exec rake assets:precompile:build
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[assemble_ember_build] Reusing existing core ember build. All done.
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compiling 49 plugins...
Compiling automation...
end of files.sort
end of files.sort
        hex_digest 103dc9ebebb80a7065cb8dd41fb3356b30f151f7
########### recursive

# يتوقف هنا إلى الأبد، مستهلكًا الذاكرة بالكامل

أشتبه في أن المشكلة قد تكون مرتبطة بقيمة ulimit (التي قمنا بضبطها على unlimited بدلاً من قيمة مثل ulimit -n 1048576;)، نظرًا لأنك تفتح عددًا كبيرًا من الملفات وتخزن محتواها في الذاكرة (عبر الاستدعاءات العودية).

أخبرني إذا كان هذا يذكرك بشيء، أو إذا كانت لديك أي تلميح آخر حول ما قد تكون المشكلة.

تحياتي،

إسماعيل

js_manager.rb.txt (7.7 KB)

من المستحسن دائمًا وجود مساحة تبديل (swap). ومن فكرة جيدة جدًا تمكين النواة من تجاوز الالتزام (overcommit)، حيث يمكن أن يقلل ذلك بشكل كبير من احتياجاتك القصوى للذاكرة.

أتمنى أن تحل هاتين المشكلتين ثم تحاول مرة أخرى. لمعرفة المزيد حول تجاوز الالتزام، راجع:

أما فيما يتعلق بالتشخيص، فقد يكون من المفيد التحقق من سجل dmesg بحثًا عن أحداث نفاد الذاكرة (OOM)، ويمكنك القيام بذلك لاحقًا، وكذلك تشغيل أمر vmstat في وقت التوقف:

vmstat 5 5

إليك نصيحتي العامة للتشخيص:

مرحبًا @Ed_S،

شكرًا لك على رسالتك.

لقد قمت بالتشخيص وهذه هي النتائج التي حصلت عليها:

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
# we start precompiling...
 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

ارتفعت استهلاك الذاكرة بشكل كبير:

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

وتعطلت أثناء تجميع الإضافات:

/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
Successfully installed prometheus_exporter-2.2.0
1 gem installed

A new release of RubyGems is available: 3.6.9 → 4.0.11!
Run `gem update --system 4.0.11` to update your installation.

Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[assemble_ember_build] No existing build info file found.
Fetching and extracting 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
Prebuilt assets downloaded and extracted successfully.
[assemble_ember_build] Reusing existing core ember build. All done.
Plugin name is 'msgraph-polling', but plugin directory is named 'msgraph-poll-discourse-plugin'
[Plugin::JsManager] Compiling 49 plugins...

# hangs here till giving an OOMKilled

هل يمكنك تعديل ulimit ورؤية ما إذا كان السلوك أعلاه يتكرر؟

تحياتي،

إسماعيل

آه، لقد واجهت خطأ نفاد الذاكرة (OOM)، رائع. هذا تأكيد قاطع. لا علاقة لـ ulimit بهذا الأمر.

أضف مساحة تبديل (swap). لا يوجد سبب لعدم فعل ذلك سوى نقص مساحة القرص. أضف 8 جيجابايت أو 16 جيجابايت، ثم جرب مرة أخرى. هدفك هو الوصول إلى حالة عمل مستقرة. بعد ذلك، يمكنك محاولة قياس العملية التي تستهلك الذاكرة بشكل مفرط، إذا رغبت في ذلك.

قم بإعداد خاصية تجاوز الالتزام (overcommit). إنها ممارسة جيدة تقلل من مشاكل الذروة في الذاكرة. لا تحتاج إلى فهمها أو تبريرها، فقط قم بتنفيذها. إنها جزء من إعداد جيد لنظام لينكس. تحقق أولاً. الأمر بهذه السهولة:

# 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

مرحباً يا رفاق،

شكراً جزيلاً لكم على جميع النصائح التي قدمتموها لنا، نقدر ذلك كثيراً. نعتقد أننا حددنا السبب الجذري لمشاكل الذاكرة الأخيرة.

في السابق، لم يتطلب تشغيل أمر bundle exec rake assets:precompile:build أثناء البناء (بصلاحيات الجذر) وجود Redis أو اتصال بقاعدة البيانات. وقد تغير هذا السلوك (راجع: Introducing pre-compiled JS assets for self-hosters و Introducing a new build system for plugins).

للتكيف مع هذا التغيير، قمنا بنقل خطوة bundle exec rake assets:precompile:build إلى حاوية تهيئة (init container) تعمل وقت التشغيل (قبل تنفيذ db:migrate وما إلى ذلك). وهذا يسمح بتشغيلها باسم مستخدم discourse مع الوصول الضروري إلى كل من Redis وقاعدة البيانات.

ومع ذلك، أثناء التنفيذ، يدخل العملية في حلقة في lib/plugin/js_manager.rb. عند النظر إلى ps -fe، نرى أن pnpm يحاول إضافة نفسه بشكل متكرر، مما يؤدي إلى استنفاد الذاكرة:

...
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
...
# وتبدأ القائمة بالنمو وتستمر دون توقف، مما يتسبب في استنفاد الذاكرة

في اختباراتنا، وجدنا أن تشغيل حاوية التهيئة بصلاحيات الجذر بدلاً من ذلك، ثم تنفيذ npm uninstall -g pnpm يليه npm install -g pnpm@10.28.0، يحل المشكلة ويؤدي إلى إكمال تجميع الإضافة بنجاح:

...
[Plugin::JsManager] Compiling 49 plugins...
[Plugin::JsManager] Finished initial compilation of plugins in 5.82s

لذلك، قبل المبالغة في هندسة بنيتنا التحتية وربما تغيير تصميمنا، أعتقد أن هذا السؤال موجه أكثر إلى @david: هل توجد خطط لاستعادة السلوك السابق لـ assets:precompile:build بحيث يمكن تشغيله دون اتصال بـ Redis أو قاعدة بيانات (مشابه لما تفعله في تدفق DISCOURSE_DOWNLOAD_PRE_BUILT_ASSETS: 0

وفي جانب جانبي، ودافعاً عن الفضول: لماذا يؤدي تشغيل عملية node كمستخدم غير جذري إلى إحداث هذه الحلقة التكرارية لتثبيت pnpm، بينما يبدو أن التشغيل بصلاحيات الجذر يتجنبها؟

تحياتي،
إسماعيل