هل تستغرق عمليات إعادة البناء وقتًا طويلاً مع ملايين عمليات الرفع؟ جرّب إضافة هذا القالب!

parallel-fs-ops.template.yml (2.8 KB)

لنتحدث عن مشكلة في التوسع تصبح مؤلمة الوضوح بمجرد أن تتراكم مكتبة رفع كبيرة على موقع Discourse.

ما كان يستغرق دقائق لتنفيذ أمر chown عبر دليل رفع ضخم أصبح يستغرق ثوانٍ الآن!

الخلفية

يمكن أن تنفذ عمليات إعادة بناء Discourse عمليات تكرارية مثل:

chown -R ...
chmod -R ...

بشكل أكثر تحديداً، هذه السطر في templates/web.template.yml:

- chown -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp

تتجول هذه الأوامر في نظام الملفات بشكل تسلسلي، واحد تلو الآخر.

هذا معقول تماماً لتثبيت صغير. ولكن عندما يحتوي shared/uploads على مئات الآلاف — أو الملايين — من الملفات، يمكن لعمليات الملكية والصلاحيات التكرارية أن تستحوذ على عملية النشر. قد تكون سعة المعالج المركزي والتخزين والشبكة متاحة، لكن عملية واحدة تتجول في الشجرة بأكملها عقدة تلو الأخرى (inode).

للمجتمعات التي تعتمد بشكل كبير على الرفع، يمكن أن يكون النتيجة هي:

  • عمليات إعادة بناء طويلة جداً
  • نوافذ صيانة أطول
  • تأخر في عمليات النشر وتحديثات الأمان
  • استغلال ضعيف للتخزين السريع أو الموزع
  • يبدو النشر عالقاً بينما يعالج شجرة ملفات هائلة
  • أداء مؤلم بشكل خاص على NFS و JuiceFS و CephFS وأنظمة الملفات عن بُعد الأخرى

الجزء المحبط هو أن العديد من هذه الملفات مستقلة. يمكن معالجة صلاحياتها بشكل متزامن.

الحل: قالب عمليات نظام الملفات المتوازية

أنشأت قالب pups يستبدل بشكل شفاف عمليات chmod و chown التكرارية بخطوط أنابيب find و xargs المتوازية.

تعلن الغلافات (wrappers) عن نفسها كلما اعترضت عملية تكرارية:

echo "[parallel-fs-ops] chmod -R override active: $*" >&2

و:

echo "[parallel-fs-ops] chown -R override active: $*" >&2

هذا البادئة بين القوسين يجعل التحسين سهلاً للتعرف عليه في سجل النشر.

كيف يبدو النشر

قرب بداية النشر، يؤكد القالب أي الثنائيات (binaries) التي ستتعامل مع عمليات نظام الملفات اللاحقة:

[parallel-fs-ops] chmod -> /usr/local/bin/chmod
[parallel-fs-ops] chown -> /usr/local/bin/chown

عندما يشغّل قالب صاعد (upstream) لاحقاً تغيير صلاحيات تكرارياً، يتضمن مخرجات النشر سطراً مشابهاً لـ:

[parallel-fs-ops] chmod -R override active: -R 0755 /var/www/discourse/public

ينتج عن تغيير الملكية التكراري:

[parallel-fs-ops] chown -R override active: -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp

لتثبيت يعتمد بشكل كبير على الرفع، قد ترى شيئاً يشبه:

[parallel-fs-ops] chown -R override active: -R discourse:www-data /shared/log/rails /shared/uploads /shared/backups /shared/tmp

تعتمد المسارات والحجج الدقيقة على القوالب المستخدمة، لكن الجزء المهم هو العلامة المرئية:

[parallel-fs-ops]

بدون القالب، قد يبدو النشر متوقفاً لفترة طويلة أثناء عملية نظام ملفات تكرارية. مع القالب، يخبرك السجل بأن:

  1. تم تثبيت الغلاف بشكل صحيح.
  2. تم اكتشاف عملية تكرارية.
  3. التنفيذ المتوازي نشط.
  4. الحجج الأصلية قيد المعالجة مرئية.

هذا قيم بشكل خاص أثناء استكشاف الأخطاء وإصلاحها لأنه يميز بين تجوال نظام الملفات المتوازي البطيء وبين عملية بناء متعذرة.

بعد انتهاء العملية، يستمر النشر بمخرجات pups العادية. لا يطبع الغلاف نفسه سطراً لكل ملف، لذا حتى شجرة تحتوي على ملايين عمليات الرفع لا تغمر سجل النشر.

القالب

run:
  - file:
      path: /usr/local/bin/chmod
      chmod: "+x"
      contents: |
        #!/bin/bash
        if [[ "$*" =~ (^|[[:space:]])-R([[:space:]]|$) ]]; then
          echo "[parallel-fs-ops] chmod -R override active: $*" >&2
          args=()
          for arg in "$@"; do
            [[ "$arg" != "-R" ]] && args+=("$arg")
          done
          mode="${args[0]}"
          targets=("${args[@]:1}")
          [[ ${#targets[@]} -eq 0 ]] && targets=(".")
          find "${targets[@]}" -print0 |
            xargs -0 -n 32 -P 128 /bin/chmod "$mode"
        else
          exec /bin/chmod "$@"
        fi

  - file:
      path: /usr/local/bin/chown
      chmod: "+x"
      contents: |
        #!/bin/bash
        if [[ "$*" =~ (^|[[:space:]])-R([[:space:]]|$) ]]; then
          echo "[parallel-fs-ops] chown -R override active: $*" >&2
          args=()
          for arg in "$@"; do
            [[ "$arg" != "-R" ]] && args+=("$arg")
          done
          owner="${args[0]}"
          targets=("${args[@]:1}")
          [[ ${#targets[@]} -eq 0 ]] && targets=(".")
          find "${targets[@]}" -print0 |
            xargs -0 -n 32 -P 128 /bin/chown "$owner"
        else
          exec /bin/chown "$@"
        fi

  - exec:
      cmd: |
        echo "[parallel-fs-ops] chmod -> $(command -v chmod)"
        echo "[parallel-fs-ops] chown -> $(command -v chown)"

يثبّت القالب غلافات في /usr/local/bin، والذي يظهر عادةً قبل /bin في PATH.

عندما تُطلب عملية عادية غير تكرارية، يفوّض الغلاف مباشرةً إلى الأداة القياسية:

exec /bin/chmod "$@"

عند وجود -R، يزيل علم التكرار، ويسرد الأهداف بأمان باستخدام فواصل فارغة (null delimiters)، ويعالج الدفعات بشكل متزامن:

find "${targets[@]}" -print0 |
  xargs -0 -n 32 -P 128 /bin/chmod "$mode"

يعمل هذا أيضاً عندما تستدعي pups الأوامر عبر /bin/sh. يتم احترام shebang الخاص بـ Bash للغلاف عند تشغيل الملف التنفيذي، حتى لو كان shell الداعي هو Dash.

لماذا هذا مهم أكثر عندما يكون لديك العديد من عمليات الرفع

المجتمعات التي تعتمد بشكل كبير على الرفع هي بالضبط المكان الذي يحتاج فيه سلوك النشر إلى التوسع بسلاسة.

قد يحتوي منتدى طويل الأمد على:

  • صور مضمنة عبر سنوات من المنشورات
  • صور رمزية وخلفيات الملف الشخصي
  • نسخ أصلية ومحسنة من الصور
  • مرفقات فيديو وصوت
  • مستندات وأرشيفات
  • عمليات رفع آمنة
  • وسائط تديرها الإضافات
  • أشجار رفع متعددة المواقع

قد يبقى حجم كود التطبيق مستقراً نسبياً بينما يستمر عدد كائنات نظام الملفات المرفوعة في النمو. يمكن أن يصبح تجوال نظام الملفات — وليس التجميع أو إنشاء الحاويات — التكلفة المهيمنة للنشر في النهاية.

هذه مشكلة توسع غير عادية: كلما أصبح المجتمع أكثر نجاحاً وغنىً بالمحتوى، أصبحت الأعمال التشغيلية الروتينية أكثر تكلفة.

لماذا نحتاج إلى قالب

تغيير .bashrc أو ضبط BASH_ENV لا يحل هذه المشكلة بشكل موثوق. ينفذ pups أوامر run عبر /bin/sh، و Dash لا يحمّل تكوين Bash ولا يفهم الدوال الخاصة بـ Bash.

يوفر القالب طريقة قابلة للتكرار لتثبيت الغلافات مبكراً بما يكفي بحيث تتحقق العمليات التكرارية اللاحقة — بما في ذلك تلك من القوالب الصاعدة — من خلال التنفيذ المتوازي:

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "containers/parallel-fs-ops.template.yml"

خيارات قابلة للتكوين

خيارات المعالجة المتوازية

يستخدم القالب:

find "${targets[@]}" -print0 |
  xargs -0 -n 32 -P 128 /bin/chmod "$mode"

معلمات xargs ذات الصلة هي:

الخيار الغرض
-0 يقرأ مسارات مفصولة بفواصل فارغة تنتجها find -print0. يتعامل هذا بأمان مع أسماء الملفات التي تحتوي على مسافات أو علامات اقتباس أو علامات تبويب أو أسطر جديدة.
-n 32 يمرر كحد أقصى 32 مساراً لكل استدعاء لـ chmod أو chown. هذا هو حجم الدفعة.
-P 128 يسمح بتشغيل ما يصل إلى 128 عملية chmod أو chown بشكل متزامن. هذا هو مستوى التوازي.

معاً، تعني -n 32 -P 128 أنه يمكن تشغيل ما يصل إلى 128 عملية في نفس الوقت، مع معالجة كل عملية لدفعة من حتى 32 مساراً. لذلك قد يتم توزيع حوالي 4,096 مساراً بشكل نشط عبر دفعات الأوامر في نفس الوقت.

اختيار -n

يتحكم -n في كمية العمل المخصصة لكل أمر:

  • القيم المنخفضة توفر توزيعاً أدق للعمل لكنها تبدأ المزيد من العمليات.
  • القيم العالية تقلل من نفقات بدء العملية لكنها تنشئ دفعات أكبر وأقل توزيعاً بالتساوي.
  • -n 1 يشغّل أمر chmod أو chown واحداً لكل مسار.
  • -n 32 نقطة بداية معقولة لموازنة الدفعات والتوازي.
  • قد تقلل القيم الكبيرة جداً من فعالية -P لأن عدد الدفعات الإجمالي المُنشأة يكون أقل.

اختيار -P

يتحكم -P في عدد الأوامر التي يمكن تشغيلها في نفس الوقت:

  • القيم المنخفضة تقلل الحمل على المعالج المركزي ونظام الملفات.
  • القيم العالية يمكن أن تحسن الأداء على التخزين السريع أو الموزع.
  • التوازي المفرط يمكن أن يرهق الأقراص، أو يشبع خادم البيانات الوصفية، أو يجعل الأداء أسوأ.
  • -P 1 هو تنفيذ تسلسلي فعلياً.
  • -P 8 أو -P 16 نقطة بداية حذرة.
  • -P 32 قد يناسب التخزين المدعوم بـ SSD السريع.
  • يجب استخدام -P 128 فقط عندما يمكن لنظام الملفات والمضيف تحمل هذا المستوى من التزامن.

تعتمد القيم الأفضل على زمن استجابة نظام الملفات، وأداء البيانات الوصفية، وسعة المعالج المركزي، وعدد الملفات. يجب أن تكون كلا القيمتين قابلة للتكوين ومعايرة (benchmarked) للتثبيت المحدد مثالياً.

يمكن أن يرهق التوازي المفرط نظام الملفات، أو يشبع خوادم البيانات الوصفية، أو يخفض أداء النشر. لذلك يجب أن يكون حجم الدفعة والتزامن قابلين للتكوين.

هذا القالب حل عملي مؤقت، لكن الاقتراح الأوسع نطاقاً هو:

هل يمكن لـ Discourse أن يدعم رسمياً توازياً قابلاً للتكوين للعمليات التكرارية الكبيرة على نظام الملفات أثناء عمليات النشر؟

يمكن للتنفيذ الصاعد (upstream) أن:

  • يوازى فقط أشجار المجلدات الكبيرة المعروفة
  • يتجنب تجوال أشجار الرفع غير المتغيرة بشكل غير ضروري
  • يجعل التزامن قابلاً للتكوين
  • يكتشف أنظمة الملفات المحلية مقابل تلك المدعومة بالشبكة
  • يحافظ على دلالات حجج chmod و chown الكاملة
  • يصدر تقدماً دورياً للأشجار الكبيرة جداً
  • يسجل الأزمنة حتى يتمكن المسؤولون من تحديد اختناقات النشر

تحذير مهم

الغلاف أعلاه يركز على أشكال الأوامر التكرارية المستخدمة في عملية البناء لدينا. إنه ليس إعادة تنفيذ كاملة لكل تركيبة خيارات chmod أو chown الممكنة.

يجب اختباره ضد الأوامر الدقيقة التي يولدها قوالب الموقع قبل الاستخدام في الإنتاج. يجب على المشغلين البدء بتوازي حذر وقياس التأثير على تخزينهم.

لكن المشكلة الأساسية حقيقية: العمليات التكرارية التسلسلية للبيانات الوصفية لا تتوسع بشكل جيد عندما يكون المجتمع قد تراكم شجرة رفع هائلة.

بالتوفيق، وأقدّر أي تعليقات أو اقتراحات (حتى لو كنت قد أكررت جهود شخص آخر، سأقدّر التوجيهات نحو ذلك أيضاً)!

أطيب التحيات!

إعجابَين (2)