Skip to main content

Overleaf-Benchmark.pdf

الملخص

عادةً ما تُحدَّد أحجام عمليات نشر Overleaf المستضافة ذاتيًا وفق قاعدة تقريبية واحدة: نواة معالج واحدة وغيغابايت واحد من الذاكرة لكل خمسة إلى عشرة مستخدمين متزامنين. نُبيّن أن هذه القاعدة ليست غير دقيقة فحسب، بل خاطئة من الناحية البنيوية، لأنها تفترض أن بُعدًا واحدًا من الموارد يحكم السعة، في حين أن هناك في الواقع جدارين مستقلين، ولأن معاملين برمجيين — لا علاقة لأي منهما بالعتاد — يهيمنان على النتيجة بعوامل تصل إلى أربعة أضعاف. نقيس نشرًا قياسيًا لـ Ayakaleaf Pro v6.2.2 مع الترجمة المعزولة (TeX Live 2025) عبر 21 تكوينًا للمعالج/الذاكرة داخل أنظمة ضيفة تعمل على QEMU/KVM، مع تثبيت تردد أنوية المضيف عند 3.0 GHz. عبء العمل هو أطروحة حقيقية من 63 صفحة مكتوبة بـ XeLaTeX، يترجمها في آنٍ واحد ما يصل إلى عدة مئات من حسابات المستخدمين المختلفة. نجد أنه تحت 32 GiB من ذاكرة النظام الضيف، يكاد عدد الأنوية يكون عديم الأهمية — فعند 16 GiB تختلف السعة المقيسة للأنظمة الضيفة ذات 4 و8 و16 vCPU بأقل من 8% — وأن السعة تحكمها بدلًا من ذلك جدار ذاكرة فوق خطي ناشئ عن ذاكرة التخزين المؤقت للصفحات (page cache) المشتركة لشجرة TeX Live. ولمعرفة ما إذا كانت هذه القوانين تصمد أمام تغيّر في المقياس بمرتبة عشرية كاملة، نكرّر المسح على خادم واحد ذي 64 نواة و995 GiB. يتحمّل هذا الخادم 1024 عملية ترجمة باردة متزامنة بنسبة نجاح 100% — أي ثمانية أضعاف عدد خيوطه — ولم نصل قط إلى سقفه. والرقم المفيد ليس ذلك السقف بل نقطة الانعطاف (الركبة) التي تسبقه: يزداد زمن الاستجابة الطرفي (tail latency) بنسبة 20–40% مع كل مضاعفة حتى N=256N=256، ثم بنسبة 190% عند N=512N=512. وبالتالي فإن السعة المُبلَّغ عنها بوصفها “أكبر قدر من التزامن لا يفشل” ستبالغ في تقدير نقطة التشغيل القابلة للاستخدام بعامل أربعة. على تلك الآلة، لا تكون الذاكرة أبدًا هي المورد المقيِّد؛ بل الحد هو المعالج مقرونًا بالمعدل الذي يستطيع به خادم الحاويات (daemon) قبول بيئات معزولة جديدة، والذي يتشبّع قرب 200 بغض النظر عن عدد عمليات الترجمة المطلوبة. كما نحدد تأثيرين على مستوى التنفيذ لا يظهران في تخطيط السعة. أولًا، يفرض CLSI سقفًا مُرمَّزًا بشكل ثابت قدره 65 عملية ترجمة متزامنة، لا يُكشف عبر أي متغير بيئة؛ وبعده يتلقى المستخدمون HTTP 503 فورًا بدلًا من وضعهم في قائمة انتظار. ثانيًا، كان حد الذاكرة لكل حاوية في مشغّل Docker غير فعّال منذ إدخاله في 2018، من حيث القيمة ومن حيث الموضع معًا، لذا فإن حدث نفاد الذاكرة يُسقط المضيف بأكمله بدلًا من عملية ترجمة واحدة. إن رفع سقف التزامن وزيادة المهلة الافتراضية للترجمة من 180 s إلى 300 s يرفعان السعة المقيسة لنظام ضيف بـ 8 vCPU / 48 GiB من 64 إلى 268 عملية ترجمة متزامنة — أي بعامل 4.2 دون أي تكلفة عتادية. وأخيرًا، نُبيّن أن التزامن في هذا النظام لا يشتري سوى المشاركة الزمنية (time-sharing)، وأن عبء العمل محدود بتردد الساعة وحده. يعطي قانون التدهور الملائَم T(N)=T1max⁡(1,N/C)bT(N)=T_1\max(1,N/C)^{b} القيمة b=0.914b=0.914، وهي قريبة من التباطؤ التناسبي التام، كما أن مسحًا لتردد الساعة عبر النطاق الكامل للآلة 1.0–5.5 GHz يجمع ثلاثين قياسًا على T=(k/f)max⁡(1,N/C)T=(k/f)\max(1,N/C) مع k=27.9GHz⋅sk=27.9 GHz·s وتشتت متبقٍ قدره 5.1%. فزيادة تردد الساعة 5.5 مرات تحقق تسريعًا بمقدار 5.5 مرات دون تناقص في العائد، وهذا هو المعنى الذي يشتري به التردد والأنوية أشياء مختلفة: التردد يجعل ترجمة كل مستخدم أسرع، أما الأنوية فلا تفعل سوى استيعاب مزيد من المستخدمين.

1. المقدمة

Overleaf هو محرر LaTeX التعاوني المهيمن، وتوزيعته للاستضافة المحلية منتشرة على نطاق واسع لدى الجامعات والمجموعات البحثية التي لا تستطيع إرسال مخطوطات غير منشورة إلى سحابة تابعة لطرف ثالث. ويُعدّ تحديد حجم مثل هذا النشر سؤالًا عمليًا متكررًا: بالنظر إلى ميزانية عتاد ثابتة، كم شخصًا يمكنه فعليًا الضغط على “Recompile” في الوقت نفسه؟ الإرشادات الرسمية قاعدة خطية — تقريبًا نواة واحدة وغيغابايت واحد لكل خمسة إلى عشرة مستخدمين متزامنين — تفترض أن السعة تتدرج بسلاسة وبشكل مشترك في كلا الموردين. وتتناقض قياساتنا مع ذلك من ثلاث نواحٍ.

1.1 تحكم السعة جداران مستقلان، لا جدار واحد

يفشل التكوين إما بسبب نفاد الذاكرة، وفي هذه الحالة تنهار حزمة Overleaf نفسها وتُرجع HTTP 502، أو لأن عمليات الترجمة تتجاوز المهلة من جهة الخادم، وفي هذه الحالة يُبلغ CLSI عن timedout بينما تبقى غيغابايتات من الذاكرة غير مستخدمة. لهذين النظامين سلوك تدرّج مختلف تمامًا وعلاجات مختلفة. إن إضافة أنوية إلى تكوين مقيَّد بالذاكرة ليست غير فعالة فحسب، بل قد تكون أحيانًا ذات نتائج عكسية: فقد قسنا تكوينات تؤدي فيها زيادة عدد الأنوية إلى تقليل السعة، لأن مزيدًا من الأنوية يجعل عمليات الترجمة المتزامنة تتقدم بخطى متطابقة، فتتزامن ذروات طلبها على الذاكرة بدلًا من أن تتداخل بالتناوب.

1.2 المعاملات البرمجية تطغى على العتاد

مهلة الترجمة حقل لكل مستخدم في MongoDB، وقيمتها الافتراضية 180 s تحدّ بصمت من سعة التكوينات المقيَّدة بالمعالج. ورفعها إلى 300 s يضاعف السعة المقيسة بما يصل إلى 4.2 مرة على العتاد نفسه دون تغيير. وبشكل مستقل، يرفض CLSI أكثر من 65 عملية ترجمة متزامنة بسبب ثابت مُرمَّز في الشيفرة. وأي دراسة للسعة — وأي نشر — لا تأخذ كليهما في الحسبان إنما تقيس البرمجيات، لا الآلة.

1.3 التزامن مشاركة زمنية، لا توازٍ

لأن ترجمة LaTeX أحادية الخيط، فإن خدمة NN مستخدمًا متزامنًا على CC نواة لا تجعل النظام ينتهي أسرع؛ بل تجعل كل مستخدم ينتظر مدة أطول بشكل تناسبي. لذلك فإن سؤال “كم مستخدمًا متزامنًا يُدعَم” سؤال غير محدد الصياغة إلى أن يُحدَّد المدة التي يكون المستخدم مستعدًا لانتظارها. نجعل هذه التبعية صريحة ونقيسها كميًا.

1.4 المساهمات

  • مصفوفة سعة عبر 21 تكوينًا للمعالج/الذاكرة، مقيسة في ظروف مثبتة التردد ومُتحقَّق منها بالتكرار، مع تحديد القيد الحاكم لكل تكوين من بصمة فشله.
  • نموذجان ملائَمان: نموذج سعة يفصل جدار ذاكرة فوق خطي عن سقف المعالج، ونموذج زمن استجابة يُثبت سلوك المشاركة الزمنية البحتة.
  • تحديد مشكلتين في تنفيذ النظام المنشور وتأكيدهما تجريبيًا، بما في ذلك حد ذاكرة للحاويات معطّل منذ 2018.
  • قياس كمي للمفاضلة بين مهلة الترجمة والسعة، ونرى أنه يجب ذكرها إلى جانب أي رقم للتزامن.

2. الخلفية

2.1 مسار الترجمة

ينتقل طلب الترجمة في Overleaf عبر web →\rightarrow clsi →\rightarrow حاوية ترجمة. في نشر يستخدم الترجمة المعزولة (SIBLING_CONTAINERS_ENABLED=true)، لا يشغّل CLSI الأداة latexmk داخل العملية نفسها؛ بل يطلب من خادم Docker على المضيف، الذي يمكن الوصول إليه عبر مقبس مربوط (bind-mounted)، بدء حاوية جديدة من صورة TeX Live مع ربط مجلد المشروع عند /compile. وبالتالي فإن عملية ترجمة واحدة هي حاوية قصيرة العمر تشغّل عملية latexmk واحدة. تترتب على ذلك ثلاث نتائج، وجميعها تشكّل القياسات في هذه الورقة. أولًا، وحدة العمل عملية أحادية الخيط: XeLaTeX لا يعمل بالتوازي. ثانيًا، عزل الموارد لكل عملية ترجمة هو ما يطلبه مشغّل Docker أيًا كان — ونبيّن في §6.2 أنه فعليًا لا يطلب شيئًا. ثالثًا، لا تهيمن الوثيقة على مجموعة العمل بل شجرة TeX Live، وهي مجموعة للقراءة فقط بحجم 32 GiB تقريبًا تقرأ منها كل عملية ترجمة متزامنة، وبالتالي تتشاركها عبر ذاكرة التخزين المؤقت للصفحات في المضيف. هذا التشارك هو أصل التدرج فوق الخطي للذاكرة الذي نلاحظه.

2.2 تفعيل الترجمة المعزولة

يشغّل Overleaf بإصدار المجتمع latexmk داخل حاوية التطبيق نفسها. أما Ayakaleaf Pro، شأنه شأن Overleaf Server Pro، فيمكنه بدلًا من ذلك تشغيل كل عملية ترجمة في حاوية شقيقة — وهي حاوية يبدؤها التطبيق على خادم Docker الخاص بـ المضيف بدلًا من أن تكون متداخلة داخل حاوية التطبيق. يفعّل ذلك إعدادان في Toolkit:
يربط Toolkit مقبس Docker الخاص بالمضيف داخل حاوية التطبيق ويحوّل هذه الإعدادات إلى البيئة التي يقرؤها CLSI: SANDBOXED_COMPILES=true وSANDBOXED_COMPILES_SIBLING_CONTAINERS=true وSANDBOXED_COMPILES_HOST_DIR، وآخرها هو مسار مجلد الترجمة على المضيف. لهذا المسار أهمية: لأن الخادم الذي يبدأ حاوية الترجمة هو خادم المضيف، يجب أن يكون الربط الممنوح له قابلًا للحل في فضاء أسماء المضيف، لا في فضاء أسماء حاوية التطبيق. كما يفرض الملف config/env.sh في Server Pro القيمة TEXLIVE_IMAGE_USER=www-data في هذا الوضع، بحيث تكون ملكية الملفات التي تكتبها حاوية الترجمة متسقة. التحقق مباشر: أثناء الترجمة، يُظهر المضيف حاوية باسم project-{projectId}-{userId}-{hash} تشغّل latexmk من صورة TeX Live وتنتهي برمز 0. هذه هي الوحدة التي نقيس تعددها في جميع أجزاء الورقة، والتي نُبلغ في §6.2 عن غياب أي حدود للموارد عليها تمامًا. تجعل الحاويات الشقيقة القياس نظيفًا — فكل عملية ترجمة كيان قابل للملاحظة على مستوى نظام التشغيل وتُجدوَل بشكل مستقل — لكنها تعني أيضًا أن نواة النظام الضيف، لا Overleaf، هي التي تحكم توزيع المعالج والذاكرة بين عمليات الترجمة. لذا فإن كل قانون تدرّج في هذه الورقة هو خاصية لمجدول Linux مطبَّقًا على NN عملية أحادية الخيط، وهذا سبب انتظامه الشديد.
الشكل 1. طلب ترجمة واحد، متتبَّعًا عبر الخدمات المصغّرة لإصدار المجتمع. للانقسام عند الخطوتين أهمية بالنسبة للسعة: يُنسخ نص المستند إلى جسم الطلب، بينما تُمرَّر الأصول الثنائية بالمرجع ويسحبها clsi. لا يهيمن أي منهما — فتكلفة ترجمة المشروع تحددها شجرة TeX Live البالغة 32 GiB التي تقرؤها كل عملية ترجمة متزامنة عبر ذاكرة الصفحات المؤقتة المشتركة.
الشكل 2. ثلاث طوبولوجيات للنشر وموضع سقف الترجمة لكل مثيل في كل منها؛ وتشير اللوحات المكدّسة إلى النسخ المتماثل. يحمي ثابت الـ 65 عملية ترجمة مثيل CLSI واحدًا، لذا يضاعفه أسطول SaaS بعدد المثيلات والمناطق (a)، ويضاعفه التدرج الأفقي الذي يدعمه Server Pro وAyakaleaf Pro بعدد المثيلات (c) — على حساب MongoDB وRedis وتخزين متوافق مع S3 مركزيين، وموازن أحمال مع تقارب الجلسات عبر ملفات تعريف الارتباط (إذ تُكتب مخرجات الترجمة على القرص المحلي للمثيل، لذا يجب أن تصل الترجمة وتنزيل PDF اللاحق إلى المثيل نفسه)، وgit-bridge أحادي النسخة. أما الإعداد الافتراضي لـ Toolkit (b)، وهو ما نقيسه، فمُضاعِفه واحد، لذا يصبح ثابتٌ صُمّم لعضو واحد في أسطول سقفًا للتثبيت بأكمله.
الشكل 3. اختيار الجزء (shard) في clsi-cache. يُربط المشروع بواسطة crc32⁡(projectId-i) mod ∣shards∣\operatorname{crc32}(\text{projectId}\text{-}i)\bmod|\text{shards}|، أي أن فضاء التجزئة يُقسَّم إلى قطاعات متساوية بعدد الأجزاء. هذا تجزئة بالباقي (modulo)، وليس تجزئة متسقة قائمة على الحلقة: فتوسيع الأسطول من ثلاثة أجزاء إلى أربعة يعيد تقسيم الفضاء بأكمله ويعيد ربط كل مشروع تقريبًا (a, b). ولهذا بالضبط يحتاج التنفيذ إلى منحدر صريح لإعادة التجزئة أثناء التشغيل، ينقل جزءًا متزايدًا خطيًا من المشاريع من currentShards إلى desiredShards خلال نافذة زمنية، بدلًا من حركة K/nK/n التي كانت ستوفرها حلقة التجزئة المتسقة. وعندما يُفصَل قاطع الدائرة لجزء ما، يُزاد الملح ii ويُزال الجزء من قائمة المرشحين، فيواصل البحث التقصي بدلًا من الفشل (c).

2.3 نمطا الفشل

كل تكوين قسناه يفشل بطريقة واحدة بالضبط من طريقتين، والتمييز بينهما ظاهر في حالة الاستجابة لا مستنتَج:
  • نفاد الذاكرة — تصبح حزمة Overleaf نفسها غير مستجيبة ويُرجع الطلب HTTP 502. وعادةً ما تكون ذاكرة النظام الضيف المتاحة عند المستوى الفاشل أقل من 500 MiB.
  • انتهاء مهلة الترجمة — يُنهي CLSI الترجمة عند المهلة المحددة لكل مستخدم ويُبلغ عن الحالة timedout. وغالبًا ما تكون الذاكرة المتاحة عند المستوى الفاشل عدة غيغابايتات.
نصنّف كل تكوين وفق هذه البصمة بدلًا من الاعتماد على استدلال تقريبي من نسب الموارد، مما يجعل سؤال “أي جدار اصطدمنا به” قابلًا للإجابة من البيانات نفسها.

3. المنهجية

3.1 بيئة الاختبار والتحكم في تردد الساعة

تعمل جميع الأنظمة الضيفة على QEMU/KVM فوق مضيف واحد بمعالج Intel Core i9-14900K مع 62 GiB من ذاكرة RAM وتخزين NVMe. النظام الضيف هو Ubuntu 24.04 مع Docker 29.7 وOverleaf Toolkit الذي ينشر Ayakaleaf Pro v6.2.2 مع الترجمة المعزولة مقابل texlive-full:2025.1. معالج سطح المكتب التجاري بديل ضعيف لخادم ما لم يُتحكَّم في تردده. لا يوفر KVM أي آلية لضبط تردد افتراضي: فوحدة vCPU هي خيط على المضيف وتعمل بأي تردد تعمل به نواة المضيف. لذلك نقيّد المضيف مباشرةً، فنعطّل التعزيز (turbo) ونثبّت scaling_max_freq عند 3.0 GHz على كل نواة، ونثبّت وحدات vCPU للنظام الضيف على أنوية P فعلية باستخدام taskset. لهذا التمييز أهمية في معالج هجين الأنوية: فأنوية E في هذا المعالج تردد أساسها 2.4 GHz و_لا يمكنها_ بلوغ 3.0 GHz بعد تعطيل التعزيز، لذا فإن أي تشغيل ينحرف إليها يقيس بصمت آلة أبطأ. وتحت الحمل الكامل نتحقق من 3000 MHz بالضبط على جميع الخيوط الستة عشر المثبتة. ويتأكد سكربت حماية من هذا الثابت قبل كل اختبار أداء ويرفض البدء في غير ذلك؛ وقد رصد إعادة ضبط صامتة واحدة لمنظّم التردد (governor) خلال الدراسة.

3.2 بيئة اختبار ثانية: مشغّل واحد كبير

تعزل مصفوفة QEMU متغيرًا واحدًا في كل مرة، لكنها تبلغ حدها عند ستة عشر خيطًا مثبتًا. ولمعرفة ما إذا كانت القوانين نفسها لا تزال صامدة عند مرتبة عشرية أعلى، كررنا مسح التزامن على خادم كبير واحد: معالج AMD EPYC 7773X واحد (Milan-X، ‏64 نواة / 128 خيطًا، 768 MiB من ذاكرة L3) مع 995 GiB من ذاكرة RAM، يشغّل صورة Ayakaleaf Pro v6.2.2 نفسها مقابل texlive-full:2025.1 نفسها. وعلى عكس الأنظمة الضيفة في QEMU، فإن تردد هذه الآلة غير مثبت: إنها خادم من فئة الإنتاج ونقيسها بوصفها كذلك. كانت ثمة احتياطان تشغيليان ضروريين ويستحقان الذكر، لأنه بدونهما تقيس التجربة أداة الاختبار بدلًا من الخادم. أولًا، قُيّدت كل حاوية ضمن شريحة systemd مع MemoryMax=940 GiB، بحيث يستنفد المسح الخارج عن السيطرة مجموعة cgroup بدلًا من المضيف. ثانيًا، يُنشئ خادم المضيف الحاويات المعزولة، وكل منها يُلوِّث طبقة النسخ عند الكتابة (copy-on-write) الخاصة به — وقد قيست بـ 116 MiB لكل حاوية رغم أن الصورة الأساسية البالغة 20.6 GiB مشتركة — لذا نُقل جذر بيانات Docker إلى جهاز NVMe مخصص. فالمسح عند N=1024N=1024 يكتب نحو 119 GiB من طبقات العمل المؤقتة، وهو ما لا يتسع له نظام ملفات جذري قياسي.

3.3 عبء العمل

المستند أطروحة ماجستير حقيقية من 63 صفحة (قالب SJTU) تُترجَم بـ XeLaTeX عبر latexmk، وتحتوي على أشكال TikZ ومعالجة مراجع عبر biblatex وأصول PDF مضمّنة — أي أنه حمل واقعي لا اصطناعي. تستغرق ترجمة واحدة على نظام ضيف غير محمّل 8.6–9.8 s عبر جميع التكوينات، ونستخدم ذلك بوصفه خط الأساس للتشغيل الحر T1T_1.

3.4 توليد الحمل

نُنشئ 512 حساب مستخدم حقيقيًا ونعطي كلًا منها نسخته الخاصة من المشروع، بحيث تتنافس عمليات الترجمة المتزامنة تمامًا كما يفعل مستخدمون مستقلون بدلًا من مشاركة قفل مشروع واحد. تُرسَل الطلبات من المضيف إلى المنفذ المُعاد توجيهه للنظام الضيف، بحيث لا يستهلك توليد الحمل أي قدر من معالج النظام الضيف. التزامن هنا متزامن فعلًا، لا متدرّج. تُنشأ كل جلسة أولًا — تسجيل الدخول، ورمز CSRF، واختيار المترجم — وعندها فقط ينام كل خيط حتى لحظة مشتركة على ساعة الحائط، تُحسب مرة واحدة وتُشارَك، قبل إرسال طلب POST /project/:id/compile. وليس هذا التمييز تدقيقًا لفظيًا. فالمنحدر المتدرّج يقيس الإنتاجية في ظل قائمة انتظار مستقرة؛ أما الدفعة المتزامنة فتقيس ما يحدث عندما يضغط طلاب قاعة محاضرات على الزر نفسه بعد إعلان الموعد النهائي نفسه، وهي الحالة التي يخشاها المشغّلون فعلًا. ويختلف الاثنان بأكثر من عامل ثابت، لأن الثانية تملأ قائمة انتظار الترجمة أسرع مما يستطيع الخادم تصريفها. كان لا بد من إزالة أربع عقبات عملية قبل أن يمكن إيصال تلك الدفعة بأمانة. وكل منها يستحق التسجيل، لأن كلًا منها يُحوّل التجربة بصمت إلى قياس لأداة الاختبار بدلًا من الخادم.

3.4.1 محدِّدان للمعدل، لا محدِّد واحد

يُقيّد Overleaf عمليات تسجيل الدخول لكل عنوان مصدر — 20 محاولة في الدقيقة — وكل حركة المرور لدينا تصدر من مضيف واحد. إن تعيين عنوان X-Forwarded-For مختلف لكل مستخدم محاكى يزيل هذا الحد، لكنه يصطدم فورًا بحد ثانٍ أكثر خشونة: ميزانية لكل شبكة فرعية تبلغ نحو 200 في الدقيقة. لذا فإن توزيع المستخدمين على كتلة متصلة يفشل عند الحساب رقم 201. وبدلًا من ذلك نشتق العنوان الاصطناعي من فهرس المستخدم بحيث يقع المستخدمون المتتالون في شبكات /24 مختلفة، 203.  ⌊i/250⌋ mod 100+1.  i mod 250+1.  i mod 200+10,\texttt{203.}\;\big\lfloor i/250 \big\rfloor \bmod 100 + 1\texttt{.}\; i \bmod 250 + 1\texttt{.}\; i \bmod 200 + 10 , مما يُبقي كلا المحدِّدين بعيدين عن التشبع لكامل المجموعة البالغة 1024.

3.4.2 الترويسة المحقونة تُهمَل افتراضيًا

ضبط الترويسة لا يكفي. فـ Express لا يعتدّ بـ X-Forwarded-For إلا من النظراء الذين أُبلغ بالثقة بهم، والقيمة الافتراضية لـ trustedProxyIps في Overleaf هي loopback. ولأن مولّد الحمل يصل إلى التطبيق عبر جسر الحاوية بدلًا من واجهة الاسترجاع (loopback)، تُحلَّل الترويسة ثم تُرمى، فيعود كل المستخدمين المحاكين إلى عنوان واحد. والعَرَض موجة من HTTP 429 عند تسجيل الدخول العشرين بالضبط، وهو ما يسهل تفسيره خطأً على أنه حمل زائد على الخادم. يجب إضافة شبكة البوابة صراحةً إلى سلسلة الثقة؛ وفي النشر العنقودي في §4.3 يجب إضافة نطاقات CIDR الخاصة بالـ pod والخدمة أيضًا.

3.4.3 موازن الأحمال سيستبدل الترويسة التي طُلب منه الحفاظ عليها

عندما يقع المثيل خلف وكيل، فإن الخيار التقليدي option forwardfor يُلحق عنوان العميل الحقيقي بالسلسلة، وهو السلوك الصحيح في الإنتاج والخاطئ تمامًا هنا: إذ يحل عنوان مولّد الحمل نفسه محل العنوان الاصطناعي. يجب تقييد التوجيه بصيغة option forwardfor if-none، بحيث لا يضيف الوكيل قيمة إلا إذا لم يزوّد العميل أي قيمة.

3.4.4 ينفد لدى العميل واصفات الملفات قبل أن تنفد سعة الخادم

عند N=1024N=1024 يحتفظ المولّد بأكثر من ألف مقبس متزامن، ويُبلَغ الحد المرن الافتراضي البالغ 1024 واصفًا أثناء إعداد الجلسات لا أثناء القياس. والفشل هنا صامت: تفشل ثلاث جلسات في الإنشاء ويُبلغ التشغيل عن 1021 بدلًا من 1024، بينما يموت خيط أخذ العينات الذي يستدعي الصدفة لعدّ الحاويات مع الخطأ EMFILE ويقتطع بيانات القياس عن بُعد بصمت. يجب رفع الحد المرن على المولّد — وكان الحد الصارم على مضيفنا 1048576 أصلًا — وإعادة التشغيل. نُبلغ عن كلا التشغيلين في §4.3: يُكمل التشغيل المصحح 1024 من 1024 بوسيط يقع ضمن 1.2 s من التشغيل المقتطع، ولهذا نعدّ الأول قابلًا للاستخدام لكن غير معتمد.

3.5 بروتوكول القياس

تبيّن أن عدة خيارات منهجية ضرورية لقابلية إعادة الإنتاج.

3.5.1 الإحماء

على نظام ضيف أُقلع حديثًا تكون ذاكرة الصفحات المؤقتة فارغة، وتقيس عمليات الترجمة الأولى عمليات الإدخال/الإخراج عند البدء البارد بدلًا من السعة في الحالة المستقرة: فالتكوين نفسه 2 vCPU / 2 GiB يعطي 36.5 s باردًا و9.8 s دافئًا، أي بعامل 3.7. لذلك يُجري كل تكوين عمليتي إحماء بترجمة واحدة تُهمَل نتائجهما بعد الإقلاع.

3.5.2 معيار النجاح

لا يُعدّ مستوى تزامن ناجحًا إلا إذا نجحت كل عمليات الترجمة وصمد المستوى عند التكرار. وهذا أكثر صرامة من عتبة لمعدل النجاح، وله أهميته: فعند 4 vCPU / 16 GiB نجح المستوى 32 مرة واحدة بوسيط 80.2 s، ثم انتهت مهلة جميع عمليات الترجمة الـ 32 عند التكرار، لذا نُبلغ عن 31.

3.5.3 البحث

تُحدَّد المستويات عبر حصر أُسّي انطلاقًا من بذرة يتنبأ بها النموذج، يليه تنصيف صحيح دقيق. ولأن المعيار إما كل شيء أو لا شيء، يُحسم المستوى بأول فشل فيه، لذا نتخلى عن الطلبات المتبقية قيد التنفيذ بمجرد فشل أحدها — إلا عند المستويات الصغيرة، حيث تُثقل عمليات الترجمة المتروكة نظامًا ضيفًا صغيرًا إلى حد أنه لا يتعافى أبدًا.

3.5.4 العزل بين المستويات

تُصرَّف حاويات الترجمة، ويُستطلع تطبيق الويب حتى يستجيب مجددًا، قبل بدء المستوى التالي. وبدون ذلك، يسجّل المستوى الذي يلي انهيارًا فشلًا زائفًا بصفر جلسات.

3.5.5 نظافة المضيف

أُوقفت الآلات الافتراضية غير ذات الصلة على المضيف: فمع تخصيص 24 GiB من ذاكرة المضيف في مكان آخر، أبلغ تكوين النظام الضيف نفسه عن متوسط حمل 11.7 بدلًا من 3.2 عند التزامن نفسه. إذ ينتقل ضغط ذاكرة المضيف إلى النظام الضيف ويُبطل القياس.

4. النتائج

4.1 مصفوفة السعة

يعطي الجدول 1 والشكل 4 السقف المقيس لكل تكوين. وقراءته عبر صف واحد هي المفاجأة الأولى. فعند 4 GiB تصل الأنظمة الضيفة ذات 2 و4 و8 vCPU جميعها إلى 9 بالضبط — مضاعفة الأنوية أربع مرات لا تغيّر شيئًا على الإطلاق. وعند 16 GiB تصل إلى 54 و45 و57: الانتقال من 4 إلى 16 نواة يشتري 6%، والنظام الضيف ذو الأنوية الثماني أسوأ فعلًا من ذي الأنوية الأربع (§5.2). وعند 48 GiB فقط يفصل عدد الأنوية بين التكوينات بشكل حاسم: 143 و268 و331.
الشكل 4. السعة المقيسة عبر مصفوفة التكوينات. (a) كل تكوين على هيئة عمود، مجمّعة حسب الذاكرة وملوّنة حسب عدد الأنوية؛ الأعمدة المصمتة مقيَّدة بالذاكرة (يموت النظام الضيف بنفاد الذاكرة) والأعمدة المخطّطة مقيَّدة بالمعالج (تنتهي مهلة عمليات الترجمة مع وجود ذاكرة فائضة). قراءة مجموعة من اليسار إلى اليمين تُظهر مدى ضآلة ما يشتريه عدد الأنوية تحت 16 GiB؛ وقراءة المجموعات عرضيًا تُظهر العائد فوق الخطي للذاكرة. (b) النقاط نفسها مقابل النموذج الملائَم Nmax⁡=min⁡(0.69R1.60, 26.4C)N_{\max}=\min(0.69R^{1.60},\,26.4C)؛ الخط المتقطع هو جدار الذاكرة والخطوط الأفقية المنقطة هي سقوف المعالج لكل عدد من الأنوية. ويتقيّد التكوين بأيهما يصل إليه أولًا. وقراءة عمود من أعلى إلى أسفل هي المفاجأة الثانية: عند عدد أنوية ثابت، تنمو السعة بشكل فوق خطي مع الذاكرة، تقريبًا بنسبة R1.6R^{1.6}، للسبب المتعلق بذاكرة الصفحات المؤقتة الذي نشرحه في §5.1. الجدول 1. الحد الأقصى لعمليات الترجمة المتزامنة التي تكتمل بنجاح، مقيسًا بمهلة ترجمة قدرها 300 s مع رفع سقف التزامن في CLSI. يشير الخط العريض إلى تكوين مقيَّد بالمعالج (تنتهي مهلة عمليات الترجمة مع وجود ذاكرة فائضة)؛ والبقية مقيَّدة بالذاكرة (تموت الحزمة مع HTTP 502). ويتضمن صف 2 GiB التصحيح الذي نناقشه في §5.2.

4.2 التزامن مشاركة زمنية

يمسح الشكل 5 كل مستويات التزامن على نظام ضيف ثابت بـ 8 vCPU / 16 GiB. يفصل بين نظامين انعطاف حاد عند عملية ترجمة واحدة لكل نواة بالضبط. تحته، يكون متوسط زمن الترجمة ثابتًا — إذ ينتقل من 8.7 s عند N=1N=1 إلى 9.1 s عند N=C=8N=C=8، أي تغيّر بنسبة 5%. وفوقه، ينمو الزمن بتناسب صارم مع N/CN/C: فعند N=16,24N=16,24 نقيس 18.5 s و27.1 s، أي نسبة 1:2.13:3.121:2.13:3.12 مقابل النسبة المثالية 1:2:31:2:3.
الشكل 5. زمن استجابة الترجمة مقابل التزامن عند عتاد ثابت. يقع الانعطاف عند N=CN=C؛ وبعده يتتبع التباطؤ المقيس N/CN/C ضمن 5–7%. نجحت جميع المستويات الخمسة عشر بالكامل.
الشكل 6. زمن استجابة الترجمة مقابل التزامن لعدة تكوينات. تُثبّت كل لوحة العتاد وتمسح الحمل المعروض؛ ويشير الخط العمودي إلى N=CN=C. المنحنيات مسطحة على يساره وخطية بدلالة N/CN/C على يمينه، وهذه بصمة المشاركة الزمنية لا التنافس: فالعمل لا يصبح أكثر تكلفة، بل ينتظر دوره فحسب. تعطي ملاءمة T(N)=T1max⁡(1,N/C)bT(N)=T_1\max(1,N/C)^{b} على جميع القياسات الناجحة في الدراسة القيمة b=0.914b=0.914 (Rlog⁡2=0.904R^2_{\log}=0.904، n=81n=81). والأُس الذي لا يمكن تمييزه عن الواحد هو التعبير الكمي عن أن الترجمة وحدة عمل أحادية الخيط مقيَّدة بالمعالج، وأن التزامن لا يفيد ولا يضر سوى بتقسيم الأنوية. والنتيجة العملية غير مريحة لتخطيط السعة: يمكن لتكوين ما أن يستوعب عددًا اعتباطيًا من المستخدمين دون أن يفشل، بينما يجعل كل واحد منهم ينتظر مدة أطول تناسبيًا. فعند N=56N=56 على هذا النظام الضيف لا تزال كل عمليات الترجمة تنجح، لكن كل مستخدم ينتظر 64.8 s بدلًا من 8.7 s.

4.3 التدرج الرأسي حتى 1024 عملية ترجمة متزامنة

يعرض الجدول 2 والشكل 7 نتائج المسح على المشغّل الكبير. كل مستوى عبارة عن ترجمة باردة: قبل كل مستوى نمسح مجلد الترجمة وذاكرة CLSI المؤقتة لكل مشروع مشارك عبر DELETE /project/:id/output، فلا يستفيد أي مستوى من عمل أنجزه المستوى الذي سبقه. خط الأساس لترجمة واحدة على هذه الآلة هو 28.8 s، وهو رقم الترجمة الباردة ولا ينبغي مقارنته بخط الأساس للحالة المستقرة 8.6–9.8 s المستخدم سابقًا؛ فخط الأساس البارد على أنظمة QEMU الضيفة هو 28.3 s، لذا فإن الآلتين متقاربتان ضمن اثنين بالمئة لكل خيط في عبء العمل هذا. الجدول 2. مسح التزامن على معالج EPYC 7773X واحد (64 نواة / 128 خيطًا، 995 GiB). جميع المستويات باردة؛ خط الأساس 28.8 s. ذروة الحاويات هي الحد الأقصى لعدد البيئات المعزولة الحية في وقت واحد.
الشكل 7. التدرج الرأسي على مشغّل كبير واحد. (a) زمن الاستجابة مقابل التزامن المعروض؛ تشير المنطقة المظللة إلى النظام الواقع بعد الانعطاف. (b) لا يتتبع عدد البيئات المعزولة الحية فعليًا العدد المطلوب أبدًا — إذ يتشبّع قرب 200 — بينما لا تستخدم مجموعة cgroup الخاصة بالترجمة أبدًا أكثر من خُمس حدها الأقصى.

4.3.1 الآلة لا تفشل أبدًا

يكتمل كل مستوى بنسبة 100%، بما في ذلك N=1024N=1024 — ثمانية أضعاف عدد الخيوط. لم نعثر على سقف سعة هذه الآلة؛ فقد نفد صبرنا قبل أن ينفد هامشها. وهذا أول تكوين في الدراسة لا تكون فيه الذاكرة هي القيد الحاكم: فعند N=1024N=1024 تبلغ ذروة مجموعة cgroup الخاصة بالترجمة 184 GiB، أي خُمس حدها البالغ 940 GiB، بينما يعمل المعالج بنسبة استخدام 100% بمتوسط حمل 166.

4.3.2 التدهور دون خطي لأن القبول محدود المعدل

تتنبأ المشاركة الزمنية الساذجة بأن 8×8\times الخيوط تكلف 8×8\times زمن الاستجابة. أما التكلفة المقيسة فهي 9.7×9.7\times نسبةً إلى ترجمة واحدة، لكنها 3.8×3.8\times فقط نسبةً إلى N=128N=128 — مقابل زيادة بثمانية أضعاف في الحمل المعروض. والسبب ظاهر في الشكل 7(b) وفي العمود الأخير من الجدول 2: رغم إرسال 1024 طلبًا في آنٍ واحد، لا يتجاوز عدد البيئات المعزولة الحية فعليًا 205 أبدًا. فالخادم لا يستطيع إنشاء الحاويات بالسرعة التي يطلبها العملاء، لذا تصطف الطلبات عند القبول بدلًا من التنافس داخل المعالج. والاصطفاف هو ما ينقذ الذيل هنا، ويفعل ذلك مصادفةً.

4.3.3 الانعطاف عند 512، لا عند نقطة الفشل

بين N=256N=256 وN=512N=512 يرتفع زمن الاستجابة p95p_{95} بمقدار 2.9×2.9\times لمضاعفة الحمل؛ بينما كلّفت كل مضاعفة سابقة ما بين 1.2×1.2\times و1.4×1.4\times. والسعة المذكورة بوصفها “أكبر NN لا يفشل” ستُبلغ عن 1024 وتكون عديمة الفائدة للمشغّل: إذ يبلغ انتظار الذيل عند تلك النقطة قرابة ثماني دقائق.

4.4 زمن الترجمة يتناسب عكسيًا مع تردد الساعة

بما أن عبء العمل مقيَّد بالمعالج، فينبغي أن تتدرج تكلفته بنسبة 1/f1/f. نختبر ذلك مباشرةً بمسح تردد ساعة المضيف عبر النطاق الكامل للآلة، 1.0–5.5 GHz في عشر خطوات، على نظام ضيف لم يتغير فيه شيء آخر (الشكل 8). ينتقل زمن الترجمة الواحدة من 26.5 s إلى 4.8 s: فزيادة التردد 5.5 مرات تشتري تسريعًا بمقدار 5.5 مرات دون تناقص في العائد في أي موضع من النطاق. ويبقى حاصل الضرب T ⁣⋅ ⁣fT\!\cdot\!f ثابتًا ضمن 2% عبر جميع الترددات العشرة. يؤدي التطبيع بحصة الأنوية إلى جمع القياسات الثلاثين كلها — ثلاثة مستويات تزامن عند عشرة ترددات — على ثابت واحد: T(N,f)  =  kf max⁡ ⁣(1,NC),k=27.9 GHz⋅sT(N,f) \;=\; \frac{k}{f}\,\max\!\left(1,\frac{N}{C}\right), \qquad k = 27.9\ \mathrm{GHz\cdot s} مع تشتت متبقٍ قدره 5.1% على نطاق يتغير فيه التردد نفسه بمقدار 5.5 مرات. وغياب أي انحناء هو بحد ذاته النتيجة: فلو كان عبء العمل مقيَّدًا بعرض نطاق الذاكرة أو بالإدخال/الإخراج، لتسطّح TT عند الترددات العالية مع تجاوز المعالج للمورد الآخر.
الشكل 8. مسح تردد الساعة. (a) T=k/fT=k/f مع القطع الزائد الملائَم. (b) بعد القسمة على max⁡(1,N/C)\max(1,N/C) تتجمع كل النقاط على ثابت واحد، مما يؤكد المعادلة (1). للمعادلة (1) نتيجة مباشرة على المشتريات يسهل ذكرها ويسهل الخطأ فيها: التردد يحسّن تجربة كل مستخدم على حدة، أما عدد الأنوية فلا يفعل سوى استيعاب مزيد منهم. فالآلة ذات التردد الأعلى بنسبة 20% تترجم أسرع بنسبة 20% للجميع، دون تناقص في العائد؛ أما مضاعفة الأنوية فلا تجعل ترجمة أحد أسرع على الإطلاق.

5. التحليل

5.1 جداران، ملائَمان كلٌّ على حدة

يُصنَّف كل تكوين وفق بصمة فشله (§2.3)، ثم يُلاءَم جدار الذاكرة وسقف المعالج فقط على التكوينات التي اصطدمت بهما فعلًا: Nmax⁡=min⁡(ARp,  kcC)N_{\max} = \min\left(A R^{p},\; k_c C\right) حيث RR بالغيبيبايت وCC بوحدات vCPU. أُس جدار الذاكرة فوق خطي باستمرار، p>1p>1: فالتكلفة الحدّية للذاكرة لعملية ترجمة متزامنة إضافية واحدة تنخفض مع نمو الذاكرة الإجمالية، من نحو 312 MiB لكل ترجمة على نظام ضيف بـ 3 GiB إلى نحو 194 MiB على نظام بـ 32 GiB. والآلية هي ذاكرة الصفحات المؤقتة المشتركة لشجرة TeX Live الموصوفة في §2.1: إذ تقرأ عمليات الترجمة المتزامنة ملفات خطوط وماكروهات متداخلة، لذا تتوزع تكلفة ذاكرة مؤقتة أكبر على عدد أكبر منها. ولهذا تقلّل القاعدة الساذجة “غيغابايت واحد لكل خمسة مستخدمين” من تقدير الآلات الكبيرة وتبالغ في تقدير الصغيرة.
الشكل 9. البيانات نفسها على هيئة سطحين فوق المستوى (C,R)(C,R). (a) السعة: السطح الملائَم سلسلة مرتفعة لا مستوى — إذ يصعد بحدة مع الذاكرة ويكاد يكون مسطحًا على محور الأنوية إلى أن تتوقف الذاكرة عن التقييد، ولهذا فإن صف 48 GiB هو الوحيد الذي يفصل فيه عدد الأنوية بين التكوينات. (b) زمن الاستجابة مقابل التزامن لكل تكوين، مع عرض المنحنى الملائَم T=12.6 (N/C)0.91T=12.6\,(N/C)^{0.91} بخط متقطع، ورسم مهلة 180 s على هيئة مستوى. يفشل التكوين حيث يخترق منحناه المصمت ذلك المستوى، مما يوضح مدى مباشرة تحديد إعداد المهلة للسعة المُبلَّغ عنها.

5.2 حيث تجعل الأنوية الإضافية الأمور أسوأ

المعادلة (2) هي الحد الأدنى لحدّين، وبالتالي فهي رتيبة بالنسبة إلى CC، لكن القياسات ليست كذلك. نلاحظ انقلابين أدت فيهما إضافة الأنوية إلى تقليل السعة: عند 16 GiB (54 مقابل 45) وعند 32 GiB (145 مقابل 135). يحدث كلاهما في النظام المقيَّد بالذاكرة، والآلية واحدة في كل منهما: مع مزيد من الأنوية، تتقدم عمليات الترجمة المتزامنة بخطى متطابقة وتبلغ ذروة حجمها المقيم في اللحظة نفسها، بينما مع أنوية أقل يُناوب المجدول بينها فتتباعد الذروات. وعلى نظام ضيف هامش ذاكرته ضئيل أصلًا، فإن هذا التباعد هو ما يبقيه حيًا. ولا يستطيع نموذج سعة مبني على متوسط استخدام الموارد التعبير عن ذلك؛ فهو خاصية لـ تزامن الذروات. أما الانقلاب الظاهري الثالث، عند 2 GiB، فنستبعده الآن. إذ يسجّل البحث سعة 2 عند 2 vCPU لكن 1 عند 4 و8 vCPU، وهو ما يبدو التأثيرَ نفسه. لكن إعادة فحص المسوح الخام تُظهر أمرًا أبسط: عند 2 GiB نجح المستوى N=2N=2 في المحاولة الأولى عند الأعداد الثلاثة للأنوية، ثم فشل في تشغيل التأكيد عند اثنين منها. فالمستوى ليس سعة بل رمية عملة، وقيمة 2 vCPU هي الرمية التي صادف أنها نجحت. لذلك نُبلغ عن القيمة القابلة لإعادة الإنتاج، 1، عند الأعداد الثلاثة للأنوية ولا نستخلص أي استنتاج من الفرق. ونسجّل التصحيح هنا بدلًا من إعادة صياغة الجدول بصمت، لأن القراءة المستبعدة من النوع الذي كان سيدعم ادعاءً مثيرًا للاهتمام.

6. نتائج على مستوى التنفيذ

6.1 سقف تزامن مُرمَّز بشكل ثابت

على الأنظمة الضيفة الكبيرة بما يكفي، توقفت السعة عند 65 عملية ترجمة متزامنة بالضبط بغض النظر عن التزامن المطلوب: فعند N=66,80,96,128N=66,80,96,128 قسنا 6565 حالة نجاح و1,15,31,631,15,31,63 استجابة unavailable فورية، مع ثبات عدد الحاويات عند 65، وعدة غيغابايتات من الذاكرة غير مستخدمة، ووسيط زمن ترجمة ثابت عند 77 s — أقل بكثير من أي مهلة. السبب ثابت في CLSI:
المقارنة غير صارمة، لذا فإن السقف الفعلي هو 64+1=6564+1=65، وهو ما يطابق القياس تمامًا. وتتلقى الطلبات الزائدة HTTP 503 — فهي تُرفض ولا توضع في قائمة انتظار، لذا يفشل زر الترجمة ببساطة من جهة المستخدم. وعلى خلاف كل معامل قابل للضبط في الملف نفسه، لا يقرأ هذا المعامل أي متغير بيئة؛ وقد أُدخل في المستودع الأصلي في أغسطس 2024 ولا يمكن تغييره إلا بتعديل الصورة. ومع رفع الحد، أبلغ النظام الضيف نفسه ذو 16 vCPU / 32 GiB الذي أبلغ عن success=65, unavailable=15 عند N=80N=80 عن success=80 بدلًا من ذلك.

6.2 حد ذاكرة للحاويات معطّل

يُظهر فحص حاوية ترجمة حية غياب أي عزل للموارد على الإطلاق:
غياب أي حصة للمعالج مقصود في التصميم، ويفسّر لماذا يكون أُس المشاركة الزمنية في §4.2 نظيفًا إلى هذا الحد: فلا شيء يشوّه التنافس بين عمليات الترجمة. أما غياب حد لـ الذاكرة فليس مقصودًا. فمشغّل Docker يطلب حدًا بالفعل:
وهذا خاطئ مرتين. فالقيمة هي 10244=1tebiB1024^4=1 tebiB بينما يقصد التعليق 102431024^3؛ والحقل موضوع في المستوى الأعلى لخيارات الإنشاء بدلًا من داخل HostConfig، حيث تتوقعه واجهة Docker البرمجية، لذا يُهمَل — وهو ما يؤكده Memory=0 الملاحَظ. كلا الخطأين موجودان في الإيداع الذي أدخل الملف (9a519f0d3d، مارس 2018)، وقد صمدا عبر التحويل من CoffeeScript، وإعادة تنسيق شاملة للمستودع، وترحيل من CJS إلى ESM، ولم يُعِد أيٌّ منها النظر في الدلالات. والجدير بالملاحظة أن MAX_OUTPUT = 1024 * 1024 // 1MB في الإيداع نفسه صحيح، مما يشير إلى زلة لا إلى سوء فهم. والنتيجة ظاهرة في قياساتنا منخفضة الذاكرة. فلأن عمليات الترجمة غير مقيدة، لا يتجلى نفاد الذاكرة في إنهاء Docker لحاوية مخالفة واحدة؛ بل يُسقط النظام الضيف بأكمله. ففي تكوين 2 vCPU / 2 GiB لاحظنا أن جلسة SSH المخصصة للمراقبة توقفت لمدة 300 s، ومتوسط حمل 68 على نواتين، ثم أعاد النظام الضيف إقلاع نفسه في النهاية. ولو كان حد الحاوية الواحدة يعمل، لكان التدهور أكثر سلاسة بكثير: إذ ستفشل الترجمة المفرطة الحجم وتبقى الخدمة. الحد الوحيد الذي يسري فعلًا هو RLIMIT_CPU، المضبوط على timeout+5\text{timeout}+5 ثانية. وهو يقيّد زمن المعالج لا زمن ساعة الحائط، ولا تستهلك الترجمة الواحدة سوى نحو 9 s من زمن المعالج، لذا لا يقيّد أبدًا عند أي مستوى تزامن؛ بل يحمي من المدخلات المرضية مثل ماكرو خارج عن السيطرة. ومع ذلك فهو مؤشر مفيد: فملاحظة Soft:305 تؤكد أن إعداد مهلة 300 s قد انتقل فعلًا إلى الحاوية.

6.3 مهلة الترجمة هي المعامل القابل للضبط الأكثر تأثيرًا

القيمة الافتراضية للحقل الخاص بكل مستخدم features.compileTimeout هي 180 s. وبالنسبة لأي تكوين مقيَّد بالمعالج، فهذه ليست هامش أمان بل إعداد للسعة، لأن آلة لا تزال تحسب بشكل صحيح تُعلن فاشلة. ورفعها إلى 300 s — بتحديث واحد في MongoDB — يغيّر السعة المقيسة بعامل يصل إلى 4.2 (الجدول 3). والسقف هو 600 s، يفرضه RequestParser.MAX_TIMEOUT، وتُقتطع القيمة بصمت فوقه. الجدول 3. تأثير مهلة الترجمة على السعة المقيسة. الصفان الأخيران هما النصف المخالف للحدس من النتيجة، والسبب في أننا أعدنا قياس كل تكوين تحت مهلة واحدة. فبالنسبة للتكوينات المقيَّدة بـ الذاكرة، تؤدي المهلة الأطول إلى تقليل السعة، لأن كل ترجمة تحتفظ بمجموعتها المقيمة مدة أطول ويتداخل عدد أكبر منها. لذلك فإن رقم السعة لا معنى له دون ذكر المهلة التي قيس في ظلها، ولا يمكن خلط الاثنين في جدول واحد.

7. الأعمال ذات الصلة

7.1 إرشادات المورّد

تذكر وثائق العتاد الخاصة بـ Overleaf الحقائق النوعية التي نقيسها كميًا هنا: أن LaTeX أحادي الخيط، وأن أداء النواة الواحدة يحكم بالتالي زمن الترجمة، وأن “more cores will only help if you are trying to compile more documents than you have free CPU cores” [1]. ثم تقدّم قاعدة التحجيم الخطية — قاعدة أساسية من نواتين/3 GiB زائد نواة واحدة وغيغابايت واحد لكل خمسة إلى عشرة مستخدمين متزامنين — التي حفّزت هذه الدراسة. ومساهمتنا هي تحويل هذه العبارات إلى قوانين مقيسة (المعادلتان (1) و(2))، وإظهار أين تنهار القاعدة الخطية: فليس فيها حدّ لذاكرة الصفحات المؤقتة المشتركة التي تجعل جدار الذاكرة فوق خطي، ولا حدّ للمعاملين البرمجيين اللذين يهيمنان على النتيجة.

7.2 دراسات سعة البناء والتكامل المستمر

قياس أنظمة البناء تحت التزامن راسخ خارج سياق LaTeX. تُبلغ LightSys أن أنظمة CI التقليدية التي تترجم داخل حاويات Docker تتدهور في الإدخال/الإخراج مع ارتفاع معدل وصول طلبات السحب، مع ظهور عنق زجاجة عند نحو أحد عشر طلبًا متزامنًا [17]؛ وتلاحظ TAOS-CI أن الترجمة تهيمن على زمن CI الفعلي، إذ تمثل 60–67% من إجمالي مدة خط المعالجة في المشاريع الكبيرة [18]. ويختلف نظامنا في جانب واحد يتضح أنه حاسم: ترجمة LaTeX تفاعلية. فمهمة CI التي تستغرق ضعف الوقت مصدر إزعاج؛ أما الترجمة التي تستغرق ضعف الوقت فيلاحظها مباشرةً مستخدم ينتظر لوحة المعاينة، ولهذا نتعامل مع المهلة لا بوصفها عتبة فشل بل معاملًا للسعة.

7.3 الأعباء الإضافية للحاويات

تُفكّك أعمال حديثة زمن بدء حاويات Docker عبر مستويات التخزين [19] وتصف أداء الحاويات على الحافة [20]. وفي سياقنا يُستهلَك زمن بدء الحاوية لكل ترجمة: فهو ثابت صغير نسبةً إلى ترجمة مدتها 9 s، ويستوعبه زمن التشغيل الحر T1T_1 الذي نلائمه. أما خاصية الحاوية التي تهم فعلًا فهي غياب حدود الموارد (§6.2)، الذي يحوّل تجاوز الذاكرة في ترجمة واحدة إلى فشل للمضيف بأكمله.

7.4 LaTeX بوصفه مدخلًا غير موثوق

توجد الترجمة المعزولة لأن TeX لغة برمجة والمستندات مدخلات غير موثوقة [21, 22]. وخيار التصميم هذا هو ما يجعل هذه الدراسة ممكنة — فكل ترجمة حاوية معزولة ذات سلوك موارد قابل للملاحظة — وهو أيضًا ما يجعل حد الذاكرة المفقود ذا عواقب، إذ يفترض المشغّلون الذين ينشرونه وجود العزل.

7.5 المترجم بوصفه موضوعًا للدراسة

TeX نفسه موثّق جيدًا بوصفه لغة [16]، لكن سلوكه بوصفه هدف بناء لم يجذب الانتباه إلا مؤخرًا. يترجم Tan وRigger [8] مجموعة كبيرة من مصادر arXiv عبر محركات وإصدارات توزيع مختلفة، ويجدان أن اختيار المحرك غير قابل للاستبدال: فجزء ضئيل فقط من بالمئة من المستندات ينتج مخرجات متطابقة بايتًا ببايت تحت XeTeX وpdfTeX. وتمسّ هذه النتيجة منهجيتنا مباشرةً. فالسعة خاصية لمستند ولمحرك معًا، لذا فإن اختبار الأداء الذي لا يثبّت كليهما غير قابل لإعادة الإنتاج؛ لذلك نثبّت مستندًا واحدًا ومحركًا واحدًا وتوزيعة واحدة (texlive-full:2025.1) طوال الدراسة، ونذكر المحرك في كل تعليق على الأشكال. كما أنها تحدّ من عمومية أرقامنا بطريقة تستحق التصريح بها: فهي تصف XeLaTeX على هذا المستند، لا TeX بشكل مجرد. الأعمال المتعلقة بـ أنظمة بناء LaTeX يقودها الممارسون إلى حد كبير. يوحّد l3build [13] من مشروع LaTeX3 اختبارات الانحدار والتحزيم، وتقارن اختبارات أداء مستقلة أدوات التغليف — إذ يجد مسح لـ 26 نظام بناء أن الديباجة المترجمة مسبقًا تساوي نحو 20% مقارنةً بالتشغيل العادي و40% مقارنةً بـ latexmk [14]. هذه تُحسّن الترجمة الواحدة. وهي متعامدة مع ما نقيسه وقابلة للتركيب معه: فذاكرة الديباجة المؤقتة تقصّر T1T_1، وكل رقم سعة في هذه الورقة يتدرج مع T1T_1.

7.6 التحكم في التزامن في المحرر، لا في المترجم

يرتكز النصف التعاوني من Overleaf على خط عمل راسخ. فالتحويل التشغيلي (Operational transformation) يعود إلى Ellis وGibbs [9]، وجعله نظام Jupiter عمليًا للعملاء ذوي زمن الاستجابة العالي [10]، ويمكن التعرف على تصميمه في document-updater: خادم يرتّب العمليات ومخزن مؤقت لكل مستند يتزامن العملاء معه. وتحل أنواع البيانات المتماثلة الخالية من التعارض [11] المشكلة نفسها دون مُرتِّب مركزي. وهذا التمييز هو ما يجعل طوبولوجيا §4.3 تعمل أصلًا: فلأن مخزن التحديثات المعلقة يقع في Redis مشترك لا في ذاكرة مثيل ما، فإن الترجمة الموجّهة إلى أي نسخة متماثلة ترى أحدث ضغطات المفاتيح، ويمكن اختيار تقارب الترجمة من أجل محلية الذاكرة المؤقتة لا من أجل الصحة.

7.7 نماذج السعة

يحدّ قانون Amdahl [24] من التسريع الناتج عن التوازي، ويربط قانون Little [23] الإشغال بمعدل الوصول وزمن الخدمة؛ وكلاهما مستخدم أعلاه. ويوسّع قانون قابلية التوسع الشامل لـ Gunther [12] الأول بحدّ تراجعي لتأخير الاتساق، متنبئًا بأن الإنتاجية تبلغ ذروتها ثم تنخفض. ونلاحظ أن نظامنا لا يُظهر ذلك النظام التراجعي حتى N=1024N=1024: فالإنتاجية تتشبّع وزمن الاستجابة ينمو، لكن لا شيء ينهار. والسبب بنيوي لا محض حظ — فعمليات الترجمة لا تتشارك أي حالة يلزم الحفاظ على اتساقها، لذا فإن الحد الذي يضيفه القانون قريب من الصفر، وهضبة القبول في §4.3 تحدّ من التنافس قبل أن يصبح ذا أهمية.

8. توصيات للمشغّلين

1

أصلح المعاملين البرمجيين قبل شراء العتاد

كلاهما مجاني، وكلاهما أثمن من أي ترقية عتادية منفردة قسناها. ارفع features.compileTimeout إلى قيمة سيتحمّلها مستخدموك فعلًا — الحد الأقصى الذي يقبله CLSI هو 600 s — وإذا كنت تتوقع تجاوز 65 عملية ترجمة متزامنة، فإما أن ترفع compileConcurrencyLimit في صورة مشتقة أو أن تتوسع أفقيًا. وعدم فعل أي منهما يعني الدفع مقابل أنوية ترفض البرمجيات استخدامها.
2

حدّد حجم الآلة الواحدة وفق نقطة انعطافها، لا وفق سقفها

يفصل مسح المشغّل الكبير (§4.3) بين رقمين كثيرًا ما يُخلط بينهما. السقف — أكبر قدر من التزامن لا يزال يُرجع كل ملفات PDF — يبلغ 1024 على الأقل على خادم ذي 64 نواة، ولم نصل إليه قط. أما الانعطاف — النقطة التي يتوقف بعدها زمن الاستجابة الطرفي عن النمو بلطف ويبدأ بالتضاعف — فيقع عند 512، وآخر نقطة تشغيل مريحة دونه هي 256. بين N=256N=256 وN=512N=512 ينتقل انتظار p95p_{95} من دقيقتين إلى قرابة ست دقائق؛ وبين 512 و1024 يصل إلى ثماني دقائق. والمشغّل الذي يحدد الحجم وفق السقف يقدّم نظامًا يعمل تقنيًا ولا يرغب أحد في استخدامه.لذلك فإن نقطة التشغيل الموصى بها لهذه الآلة وهذا المستند هي 256 عملية ترجمة متزامنة، أي 4×4\times عدد الأنوية الفعلية و2×2\times عدد الخيوط، وهي تُبقي p95p_{95} قرب 120 s. نقترح ضبط compileConcurrencyLimit على هذه القيمة بدلًا من تركه مرتفعًا: فقبول 1024 عملية ترجمة دفعة واحدة يجعل الجميع ينتظرون ثماني دقائق، بينما قبول 256 ووضع البقية في قائمة انتظار يخدم معظم المستخدمين في دقيقتين. الاصطفاف يضرّ بالقادمين متأخرين؛ أما التنافس فيضرّ بالجميع.
3

تعامل مع هذه الأرقام على أنها أسوأ الحالات

كل مستوى في الجدول 2 ترجمة باردة تُطلق في آنٍ واحد. ولا يتحقق أي من الشرطين في الإنتاج: فالترجمة الدافئة للمستند نفسه تستغرق 8.6 s مقابل 28.3 s للباردة، أي بعامل 3.33.3، والمستخدمون الحقيقيون لا يضغطون على الزر في الثانية نفسها. لذا فإن مجموعة مستخدمين في حالة مستقرة يعيدون الترجمة كل دقيقتين بمعدل إصابة نموذجي للذاكرة المؤقتة ستتحمّل عددًا أكبر بكثير من الكتّاب مما يوحي به رقم التزامن وحده — في حدود ألف مؤلف نشط أو أكثر عند نقطة التشغيل 256. رقم التزامن حدّ للدفعة اللحظية، لا عدد مقاعد.
4

حدّد ميزانية زمن الاستجابة أولًا، ثم استنتج الحجم

تنعكس المعادلة (1) مباشرةً. فلزمن انتظار مستهدف TT عند تردد ff على CC نواة، يكون التزامن الملائم هو N≤C fT/kN \le C\,fT/k مع k≈28GHz⋅sk\approx28 GHz·s لهذا المستند. ميزانية 60 s على 8 أنوية عند 3 GHz تعطي N≤51N\le51؛ وميزانية 120 s تضاعفها. ونشر الميزانية إلى جانب السعة هو الطريقة الأمينة الوحيدة لذكر أي منهما.
5

اشترِ الذاكرة أولًا، ثم الأنوية، وتحقق من الجدار الذي تقف عنده

تحت 32 GiB لم نقس تقريبًا أي فائدة من الأنوية الإضافية. والتشخيص رخيص: إذا ظهرت حالات الفشل على هيئة HTTP 502 مع نقص في ذاكرة النظام الضيف، فأضف ذاكرة؛ وإذا ظهرت على هيئة timedout مع وجود ذاكرة فائضة، فأضف أنوية أو ارفع المهلة. ويمكن للمشغّلين قراءة ذلك من بصمة الفشل نفسها التي استخدمناها لتصنيف التكوينات.
6

فضّل التردد من أجل التجربة، والأنوية من أجل عدد المستخدمين

لأن العلاقة T∝1/fT\propto 1/f تصمد دون انحناء (2% عبر 1.0–5.5 GHz)، فإن الساعة الأسرع تجعل كل ترجمة أسرع لكل مستخدم. أما الأنوية الإضافية فلا تجعل أي ترجمة منفردة أسرع؛ بل تستوعب فقط مزيدًا من الترجمات المتزامنة. وعمليات النشر التي تشكو من أن “الترجمة بطيئة” ينبغي أن تشتري ترددًا أعلى؛ أما تلك التي تشكو من أن “الترجمة تفشل عند المواعيد النهائية” فينبغي أن تشتري ذاكرة وأنوية.
7

توسّع أفقيًا بدلًا من رأسيًا بعد تجاوز السقف

بعد 65 عملية ترجمة متزامنة، يكون المسار المدعوم هو التوسع الأفقي (الشكلان 2c و10، والمفصّلان في §9): عدة مثيلات للتطبيق خلف موازن أحمال مع تقارب الجلسات عبر ملفات تعريف الارتباط، تتشارك MongoDB وRedis وتخزينًا متوافقًا مع S3 مركزيًا، مع إبقاء git-bridge نسخة وحيدة. وهذا يضاعف سقف المثيل الواحد بعدد المثيلات، وهو بالضبط الطريقة التي يبلغ بها نشر SaaS سعته.
8

لا تعتمد على العزل لكل عملية ترجمة

إلى أن يُصحَّح حد الذاكرة في مشغّل Docker (§6.2)، يمكن لمستند مرضي واحد أن يستنفد المضيف بدلًا من أن يُقتل وحده. وينبغي للمشغّلين الذين يحتاجون إلى هذا الضمان أن يفرضوه بأنفسهم بدلًا من انتظاره. والآلية التي استخدمناها على المضيف الكبير هي شريحة systemd تحمل سقفًا صارمًا، يُوجَّه إليها خادم Docker بعد ذلك بحيث تُحتسب كل حاوية ينشئها داخلها:
ثمة تفصيل في هذا يكلّف ظهيرة كاملة إذا فات. فالشريحة المسماة docker-capped.slice لا تقع بجانب docker.slice؛ بل تقع داخلها، لأن الشرطة هي فاصل التسلسل الهرمي لا جزء من الاسم. والسقف الذي يبدو بلا أثر يكون عادةً قد طُبّق على مستوى بعيد بمقدار درجة واحدة عن المكان الذي تعيش فيه الحاويات فعلًا. تحقّق بقراءة الذروة من memory.max_usage_in_bytes بعد التشغيل بدلًا من الوثوق بملف الإعداد — فعلى مضيفنا لم تتجاوز مجموعة cgroup الخاصة بالترجمة قط خُمس سقفها حتى عند 1024 عملية ترجمة متزامنة، وهذا بحد ذاته الدليل على أن الخادم لا الذاكرة كان القيد الحاكم.
الشكل 10. الطوبولوجيا المرجعية لنشر موسَّع أفقيًا، مرسومة من التكوين الذي تحققنا منه. نسخ التطبيق المتماثلة قابلة للتبادل ولا تحتفظ بأي شيء دائم، لذا يمكن إضافتها وإزالتها بحرية. أما ثلاثة مكونات فليست كذلك: Redis، الذي يتيح مخزنه المؤقت للمستندات للترجمة الموجّهة إلى أي نسخة متماثلة رؤية أحدث ضغطات المفاتيح؛ ومخزن الكائنات، الذي يصبح إلزاميًا لا اختياريًا عند وجود أكثر من نسخة متماثلة واحدة؛ وgit-bridge، الذي يحتفظ بالمستودعات على القرص المحلي دون مسار للنسخ المتماثل، ويجب تشغيله نسخة وحيدة بجانب نسخة متماثلة واحدة محددة.

9. نشر مرجعي متعدد الآلات

كل ما سبق يقيس آلة واحدة. ويعرض هذا القسم الشكل الموزّع بتفصيل كافٍ للبناء، و— لأن السؤال الذي يواجهه المشغّل فعلًا ليس كيف بل هل — يذكر أولًا النقطة التي يصبح عندها الأمر جديرًا بالعناء.

9.1 متى يكون الشكل الموزّع مبرَّرًا

الآلة الواحدة أرخص تشغيلًا من كل ناحية مهمة: نطاق فشل واحد، ولا حالة مشتركة يلزم الحفاظ على اتساقها، ولا توجيه قد يُخطأ فيه. وتضع بياناتنا ثلاث عتبات لمتى يجب تركها.

9.1.1 تحت 65 عملية ترجمة متزامنة، لا تفعل

سقف المثيل الواحد ثابت برمجي لا عتادي (§6.1). وإلى أن يقترب الحمل المعروض منه، فإن الآلة الثانية تضيف أنماط فشل ولا تشتري شيئًا. وقد خدم المضيف ذو 64 نواة 256 عملية ترجمة متزامنة بنجاح كامل فقط بعد رفع compileConcurrencyLimit؛ والمشغّل الذي لم يغيّر تلك القيمة الوحيدة بعد ليس مقيَّدًا بالعتاد ولا ينبغي له التسوق بحثًا عن عتاد.

9.1.2 بين 65 ونحو 500، توسّع رأسيًا أولًا

ظل التوسع الرأسي خطيًا عبر نطاقنا بأكمله ولم يدخل قط في نظام تراجعي. فقد بلغ مضيف كبير واحد 1024 عملية ترجمة باردة متزامنة بنجاح 100% (§4.3)؛ وظهر انعطاف زمن الاستجابة عند 512، لا قبله. وضمن هذا النطاق، فإن الآلة الأكبر أبسط حتمًا من عدة آلات أصغر، ووفقًا لـ §4.4، فإن الآلة الأسرع تحسّن تجربة كل مستخدم بدلًا من مجرد استيعاب مزيد منهم.

9.1.3 اتجه إلى التوزيع من أجل التوافرية، لا الإنتاجية

السبب الأمين لتشغيل أكثر من نسخة متماثلة واحدة للتطبيق تحت السقف هو أن الآلة الواحدة مزوّد طاقة واحد ونواة نظام واحدة ونافذة ترقية واحدة. وهذا سبب مشروع وهو السبب الذي سنقدّمه؛ لكنه ببساطة ليس حجة سعة، والخلط بين الاثنين يقود المشغّلين إلى شراء نسخ متماثلة بينما كانوا يحتاجون إلى ذاكرة.

9.2 الطبقات وتحديد أحجامها

يُظهر الشكل 10 الطوبولوجيا. فيها أربع طبقات، وهي تتوسع وفق كميات مختلفة — وهذا هو بيت القصيد من فصلها.

9.2.1 الحافة

موازن أحمال واحد، أو اثنان من أجل التوافرية. ينهي TLS ولا يفعل شيئًا مكلفًا؛ ويتوسع مع عدد الاتصالات لا عدد عمليات الترجمة، ويكفي مثيل صغير للأحمال المدروسة هنا. والمهم هو إعداده لا حجمه (§9.3).

9.2.2 نسخ التطبيق المتماثلة

تحمل هذه حمل الترجمة، وهي الطبقة الوحيدة التي تتوسع مع التزامن. حدّد حجم كل منها وفق قواعد §8 — الذاكرة قبل الأنوية، ثم التردد — ثم اضبط عدد النسخ المتماثلة ليغطي ذروة التزامن مقسومة على سقف النسخة الواحدة. لا تحتفظ النسخ المتماثلة بأي شيء دائم: فقرصها المحلي يحمل مساحة عمل الترجمة المؤقتة وذاكرة مؤقتة للمخرجات، وكلاهما قابل لإعادة البناء. وهذا ما يجعل إضافتها وإزالتها بحرية آمنة، ويستحق التحقق منه بدلًا من افتراضه، لأن مسار filestore واحدًا خاطئ الإعداد يحوّل الطبقة بصمت إلى طبقة ذات حالة.

9.2.3 الحالة

Redis وMongoDB ومخزن كائنات متوافق مع S3، على مضيفات منفصلة. وRedis هو الحامل للعبء والأقل وضوحًا: فهو يحتفظ بمخزن الجلسات والمخزن المؤقت الحي للمستندات، وهو ما يتيح للترجمة الموجّهة إلى أي نسخة متماثلة رؤية ضغطات المفاتيح المكتوبة على نسخة متماثلة أخرى. والمشغّل الذي يعامل Redis كذاكرة مؤقتة ويحدد حجمه للإخلاء سينتج ترجمات لمستندات قديمة يصعب تشخيصها للغاية، لأن لا شيء يفشل — فالمخرجات خاطئة فحسب. ويتوسع MongoDB مع عدد المشاريع لا مع معدل الترجمة. ومخزن الكائنات اختياري مع نسخة متماثلة واحدة وإلزامي بعدها.

9.2.4 النسخة الوحيدة

يحتفظ git-bridge بالمستودعات على القرص المحلي، ويدير فهرسًا محليًا، وليس لديه مسار للنسخ المتماثل. ويجب تشغيله مثيلًا واحدًا بالضبط، مثبتًا بجانب نسخة متماثلة واحدة محددة، وهو المكوّن الذي يجعل النشر ليس عديم الحالة تمامًا. خطّط لمضيفه وفقًا لذلك: فقرصه هو الذي يحتاج إلى نسخ احتياطي. الجدول 4. الطبقات المرجعية. طبقة التطبيق وحدها تتوسع مع التزامن؛ وتحديد حجمها هو موضوع §8.

9.3 التوجيه هو الجزء الذي يسهل الخطأ فيه

يجب أن تصل ثلاث فئات من الطلبات إلى ثلاثة أماكن مختلفة، والإعداد الافتراضي ذو القاعدة الواحدة يلبّي اثنتين منها على الأكثر. ينبغي توزيع حركة الترجمة تحت /project/ بالتجزئة المتسقة على معرّف المشروع، بحيث تبقى ذاكرة الترجمة المؤقتة للمشروع مع نسخة متماثلة واحدة. نستخدم balance hash path,field(3,/) في HAProxy مع hash-type consistent وhash-balance-factor 150. ولهذا الخيار أهمية عند التوسع: فمع تقارب ملفات تعريف الارتباط، تبقى الجلسات الحالية مثبتة على نسختها المتماثلة الأصلية إلى أجل غير مسمى، ولا تتلقى النسخة المضافة حديثًا سوى المستخدمين الجدد، لذا فإن الآلة التي دفع المشغّل ثمنها للتو لا تمتص شيئًا من الحمل الذي دفعه إلى شرائها. وقد أعادت التجزئة المتسقة توزيع 35% من المشاريع عند التوسع في تكويننا، مقابل 0% لملفات تعريف الارتباط. حركة الجلسات مختلفة. فعندما تفشل ترقية WebSocket ويعود socket.io إلى استطلاع XHR، يجب أن تصل الاستطلاعات المتتالية لجلسة واحدة إلى نسخة متماثلة واحدة، ولا يوجد معرّف مشروع في المسار لتجزئته. تحتاج هذه الحركة إلى واجهة خلفية منفصلة مع تقارب ملفات تعريف الارتباط. صمّمنا هذا الفصل لكننا لم ننشره؛ ونشير إليه بوصفه ثغرة بدلًا من ادعائه. وأخيرًا، يجب أن يصل /git/ إلى النسخة المتماثلة التي يعمل git-bridge بجانبها. ويُوجَّه إلى تلك النسخة بدلًا من git-bridge مباشرةً لأن الجسر يصادق على استدعاءاته الراجعة مقابل نقاط نهاية OAuth الخاصة بالتطبيق ويحلّ عناوين URL للكائنات الثنائية من خلاله؛ وتجاوز النسخة المتماثلة يكسر المصادقة بدلًا من أن يحسّن أي شيء.

9.4 التقليص يحتاج إلى فترة تصريف

إزالة نسخة متماثلة ليست متماثلة مع إضافتها: فالترجمة الجارية تضيع، ويرى المستخدم فشلًا لم يتسبب فيه. والتسلسل العملي هو إيقاف الحركة الجديدة أولًا، ثم الانتظار، وعندها فقط الإنهاء. نفّذنا ذلك بوصفه خطّاف ما قبل الإيقاف (pre-stop hook) يحتجز الـ pod لفترة قابلة للضبط بينما يضع الموازن الواجهة الخلفية في حالة التصريف — قصيرة بما يكفي للاختبار في دقائق، وفي الإنتاج طويلة بما يكفي لانتهاء الجلسة طبيعيًا، أي ساعات لا ثوانٍ. وهذه الفترة هي مفتاح الضبط الذي يحدد ما إذا كانت المرونة غير مرئية أم مثيرة للغضب. وثمة قيد إضافي اكتشفناه بالقياس لا بالتصميم: التوسع التلقائي بناءً على المعالج لا يعمل مع عبء العمل هذا. فقد كان استخدام الـ pod الخاص بالتطبيق نفسه 22 m core مقابل إجمالي للعقدة قدره 3997 m core، لأن عمل الترجمة يجري في حاويات شقيقة لا يحتسبها الـ pod. وأي إشارة تُستخدم لتوسيع هذه الطبقة يجب أن تعدّ حاويات الترجمة العاملة، لا معالج الـ pod.

10. تبعات تتجاوز Overleaf

لا شيء في §4.2 أو §4.3 خاص بشيفرة Overleaf. فالقوانين المقيسة تنبع من ثلاث خصائص تشترك فيها أي خدمة LaTeX مستضافة: وحدة العمل عملية أحادية الخيط، وهي معزولة في حاوية، ومجموعة عملها شجرة كبيرة للقراءة فقط يجب أن تحتفظ بها ذاكرة الصفحات المؤقتة. وتنتقل ثلاث نتائج مباشرةً إلى أي شخص يبني مثل هذه الخدمة.

10.1 وفّر الذاكرة، ثم الأنوية

أقوى نتيجة في المصفوفة سلبية: تحت 16 GiB يكاد عدد الأنوية يكون عديم الأهمية، وعند 48 GiB فقط تنفصل تكوينات 4 و8 و16 vCPU أصلًا (143 و268 و331). والمشغّل الذي يقرأ القاعدة التقليدية على أنها “أضف نواة لكل خمسة مستخدمين” يشتري المورد الخطأ. والآلية هي ذاكرة الصفحات المؤقتة المشتركة لشجرة التوزيعة، وهي خاصية لحجم TeX Live لا لأي واجهة أمامية بعينها.

10.2 معدل القبول مورد، وعادةً ما يُنسى

عند N=1024N=1024 لم يحتفظ خادمنا قط بأكثر من 205 بيئات معزولة حية (الشكل 7b) رغم أن كل الطلبات وصلت دفعة واحدة. فإنشاء الحاويات، لا الترجمة، كان العامل المقيِّد — بما يتسق مع دراسات القياس التي تعزو تكلفة بدء الحاويات إلى عبء وقت التشغيل لا إلى حجم الصورة [19, 20]. والخدمة التي لا تحدد سوى حجم المعالج والذاكرة ستجد سلوكها عند الدفعات محكومًا بكمية لم تقسها قط. والصيغة العملية لذلك هي توصية §8: قيّد القبول عمدًا، لأن قائمة انتظار تختارها خير من قائمة انتظار تكتشفها.

10.3 البيئة المعزولة التي تعيش أطول من ترجمتها تُبطل النموذج

يفترض كل رقم سعة هنا أن الحاوية تُنشأ، وتجري ترجمة واحدة، ثم تنتهي — بعمر يبلغ عشرات الثواني ودورة عمل قريبة من الواحد فقط أثناء تشغيلها. ويكسر نمطا تصميم حديثان هذا الافتراض، ويكسرانه بالطريقة نفسها. الأول هو البيئة المعزولة الدائمة لكل مستخدم. فتخصيص بيئة خاصة ثابتة لكل مستخدم يحوّل مجمّعًا متعدد الإرسال إحصائيًا إلى مجموعة من الحجوزات: فالخدمة التي يمكنها خدمة 256 عملية ترجمة متزامنة من 64 نواة بالمشاركة الزمنية لا يمكنها خدمة سوى 16 مستخدمًا إذا أُعطي كل منهم أربع أنوية مخصصة، أي أقل بمرتبة عشرية للعتاد نفسه. وبياناتنا تقيس تكلفة هذا الخيار كميًا بدلًا من أن تجادل ضده — فالحجوزات تشتري القدرة على التنبؤ، وسعر الصرف نحو 16×16\times عند نقطة التشغيل التي نوصي بها. والثاني، والأحدث، هو وكيل الذكاء الاصطناعي الذي يتشارك البيئة المعزولة مع المترجم. ففي منصات التأليف بمساعدة الوكلاء، قد تستضيف الحاوية نفسها التي تشغّل XeLaTeX أيضًا وكيل برمجة طويل التشغيل، فتكون مشغولة باستمرار لا على دفعات. ويُبلغ الممارسون عن العَرَض نفسه الذي يتنبأ به النموذج لمثل هذه النشرات — بطء مستمر في ظل أعداد متواضعة من المستخدمين [15]. ويستحق هذا التفاعل الذكر بدقة، لأنه ليس مجرد “حمل أكبر”. إذ تتضافر ثلاث من نتائجنا. فالإشغال يتوقف عن كونه على دفعات، لذا ينطبق قانون المشاركة الزمنية في §4.2 على كامل المجموعة دفعة واحدة بدلًا من الجزء الذي يترجم حاليًا. وذاكرة الصفحات المؤقتة، التي تشتري العائد فوق الخطي للذاكرة في §4.1، تصبح الآن مشتركة مع مجموعة عمل الوكيل نفسه وتتوقف عن كونها دافئة لـ TeX. ويصبح حد ذاكرة الحاوية المفقود في §6.2 أخطر بكثير، لأن الحاوية التي لا تنتهي أبدًا لا تُعيد ذاكرتها أبدًا. لم نقس مثل هذه المنصة ولا ندّعي شيئًا بشأن أي منتج بعينه. وما يمكننا قوله هو ما تعنيه أرقامنا بالنسبة للتصميم: فالبنية التي تمنح كل مستخدم بيئة معزولة متعددة الأنوية طويلة العمر ينبغي أن يُحدَّد حجمها بوصفها نظام حجوزات، لا وفق أرقام التزامن الواردة هنا، والسعة التي يمكن أن تتوقعها أقرب إلى عدد أنويتها مقسومًا على عدد الأنوية لكل مستخدم منها إلى أي شيء في الجدول 1.

11. تهديدات الصلاحية

11.1 مستند واحد

تستخدم جميع القياسات مستند XeLaTeX واحدًا من 63 صفحة. ستختلف السعات المطلقة لمستندات أخرى؛ أما قوانين التدرج، وهي نسب، فلا ينبغي أن تختلف. فالمستند ذو المجموعة المقيمة الأكبر بكثير سيحرّك جدار الذاكرة دون أن يغيّر طابعه فوق الخطي.

11.2 مضيف افتراضي

تعمل الأنظمة الضيفة تحت KVM على آلة فعلية واحدة، لذا تتضمن الأرقام المطلقة عبء المحاكاة الافتراضية، وتتشارك الأنظمة الضيفة ذاكرة صفحات مؤقتة وجهاز NVMe على المضيف. وقد خففنا أكبر عامل مُربِك بإيقاف الأنظمة الضيفة غير ذات الصلة بعد ملاحظة أن ضغط ذاكرة المضيف يضخّم متوسطات الحمل داخل النظام الضيف بأكثر من 3×3\times عند التزامن نفسه.

11.3 الوصول المتزامن

تُرسَل كل عملية ترجمة في لحظة واحدة، وهذه أسوأ الحالات. يصل المستخدمون الحقيقيون وفق عملية عشوائية، لذا فإن النشر المحدد حجمه بأرقامنا لديه هامش لا عجز — لكن الذروة عند نهاية موعد التسليم أقرب إلى نموذجنا منها إلى نموذج Poisson.

11.4 التكوينات الحدّية

عند 2 GiB يكون النظام قريبًا بما يكفي من الانهيار بحيث قد تختلف التشغيلات المتكررة للتكوين نفسه بعملية ترجمة واحدة. نُبلغ عن القيمة المتحفظة ولا نستخلص استنتاجات من فروق قدرها ±1\pm 1 في هذا النطاق.

12. الإتاحة

النظام قيد الاختبار وأدوات النشر والمشروع الأصلي الذي اشتُق منه كلها متاحة للعموم: كل موقع في الشيفرة المصدرية نستشهد به مذكور بوصفه مسارًا نسبيًا للمستودع مع رقم سطر مقابل Ayakaleaf Pro v6.2.2، والإيداعان الأصليان اللذان نؤرّخهما (9a519f0d3d و5d472e9b38) يمكن الوصول إليهما في سجل Overleaf.

13. المساهمات

صمّم Musicminion الدراسة، ووفّر بيئات الاختبار وشغّلها، ووجّه مسار البحث، وتحقق من كل قياس وارد هنا. أما Claude Opus 5 ‏(Anthropic) فقد بنى أداة اختبار الأداء وشغّلها، وأتمت عمليات النشر، وأجرى التنقيب في الشيفرة المصدرية، وأنتج الأشكال، وصاغ مسودة المخطوطة. وراجع كلا المؤلفين النص النهائي. وحيثما يُبلَّغ عن تشغيل ملوّث — مسح الـ 1021 جلسة في §4.3 والمستوى الشاذ N=256N=256 في §4.1 — فقد اكتُشف الخلل أثناء المراجعة وأُعيد التشغيل قبل النشر بدلًا من حذفه بصمت. ينبغي للقرّاء ملاحظة أن سياسات التأليف في ACM وIEEE وICMJE تحصر التأليف حاليًا في الأطراف القادرة على تحمّل المسؤولية عن العمل، وستتطلب تسجيل مساهمة المؤلف الثاني بوصفها إفصاحًا لا ضمن قائمة المؤلفين. ونذكر تقسيم العمل صراحةً هنا بحيث يكون السجل دقيقًا وفق أي من العُرفين.

14. الخاتمة

تخطيط السعة لـ Overleaf المستضاف ذاتيًا ليس مسألة توسيع مورد واحد. وثمة ثلاث نتائج ينبغي أن تغيّر طريقة القيام به. أولًا، تحت 32 GiB من ذاكرة النظام الضيف، يكاد عدد الأنوية لا يهم: فعند 16 GiB تختلف سعات الأنظمة الضيفة ذات 4 و8 و16 vCPU بأقل من 8%. فالذاكرة، عبر ذاكرة الصفحات المؤقتة المشتركة لشجرة TeX Live، هي التي تحدد الحد؛ ولا تبدأ الأنوية في الأهمية إلا عندما تكون الذاكرة وفيرة. ثانيًا، يرجح معاملان برمجيان على العتاد. فرفع سقف CLSI المُرمَّز بشكل ثابت البالغ 65 عملية ترجمة وزيادة مهلة الترجمة الافتراضية البالغة 180 s نقلا نظامًا ضيفًا بـ 8 vCPU / 48 GiB من 64 إلى 268 عملية ترجمة متزامنة — بعامل 4.2 دون أي عتاد إضافي. ولا يمكن اكتشاف أي منهما من وثائق الإعداد؛ وأحدهما غير قابل للإعداد أصلًا. ثالثًا، سؤال “كم مستخدمًا متزامنًا تدعم هذه الآلة” ناقص التحديد. فالتزامن في هذا النظام مشاركة زمنية بحتة، والسعة هي ما تسمح به المهلة. والصيغة الأمينة للإجابة تذكر الاثنين: هذه الآلة تخدم NN عملية ترجمة متزامنة إذا كان المستخدمون مستعدين للانتظار TT ثانية، حيث ترتبط NN وTT بالمعادلة (1). ونُبلغ أيضًا عن عيب كامن: حد الذاكرة لكل حاوية في مشغّل Docker غير فعّال منذ 2018، من حيث القيمة ومن حيث الموضع معًا. وأثره العملي أن نفاد الذاكرة في نشر صغير يُسقط الخدمة بأكملها بدلًا من الترجمة الواحدة المسؤولة عنه.

المراجع

[1] Overleaf. Hardware requirements، وثائق الاستضافة المحلية. https://docs.overleaf.com/on-premises/getting-started/requirements/hardware-requirements [2] Overleaf. Horizontal scaling، وثائق الاستضافة المحلية. https://docs.overleaf.com/on-premises/maintenance/horizontal-scaling [3] Overleaf. Microservices، وثائق الاستضافة المحلية. https://docs.overleaf.com/on-premises/getting-started/microservices [4] Overleaf. مستودع الشيفرة المصدرية. https://github.com/overleaf/overleaf [5] Ayaka-notes. Ayakaleaf Pro. https://github.com/ayaka-notes/ayakaleaf-pro [6] Ayaka-notes. Overleaf Toolkit. https://github.com/ayaka-notes/toolkit [7] D. Karger, E. Lehman, T. Leighton, R. Panigrahy, M. Levine and D. Lewin. Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web. STOC, 1997. [8] J. Tan and M. Rigger. Inconsistencies in TeX-Produced Documents. In Proc. 33rd ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA), Vienna, 2024. doi: https://doi.org/10.1145/3650212.3680370 [9] C. A. Ellis and S. J. Gibbs. Concurrency Control in Groupware Systems. In Proc. ACM SIGMOD, pp. 399–407, 1989. [10] D. A. Nichols, P. Curtis, M. Dixon and J. Lamping. High-Latency, Low-Bandwidth Windowing in the Jupiter Collaboration System. In Proc. ACM UIST, pp. 111–120, 1995. [11] M. Shapiro, N. Preguiça, C. Baquero and M. Zawirski. Conflict-Free Replicated Data Types. In Proc. SSS, pp. 386–400, 2011. [12] N. J. Gunther. Guerrilla Capacity Planning: A Tactical Approach to Planning for Highly Scalable Applications and Services. Springer, 2007. [13] The LaTeX3 Project. l3build — A Testing and Building System for (La)TeX. CTAN. [14] M. Isaksson. Which LaTeX Build System Is Fastest? A Benchmark. https://blog.martisak.se/latex-build-systems-comparison/ [15] تقارير ممارسين عن زمن استجابة مرتفع ومستمر في منصات التأليف بمساعدة الوكلاء التي تضع وكيل برمجة دائمًا مع مترجم LaTeX في بيئة معزولة لكل مستخدم. نستشهد بهذا بوصفه خبرة تشغيلية مُبلَّغًا عنها، لا قياسًا مضبوطًا؛ ولم نختبر أداء مثل هذه المنصة. [16] D. E. Knuth. The TeXbook. Addison-Wesley, 1984. [17] G. Lim, M. Ham, J. Moon and W. Song. LightSys: Lightweight and Efficient CI System for Improving Integration Speed of Software. arXiv:2101.07961 [cs.SE], 2021. نسخة أولية (Preprint). [18] G. Lim, M. Ham, J. Moon, W. Song, S. Woo and S. Oh. TAOS-CI: Lightweight & Modular Continuous Integration System for Edge Computing. arXiv:2101.08889 [cs.SE], 2021. نسخة أولية (Preprint). [19] S. Khan. Decomposing Docker Container Startup Performance: A Three-Tier Measurement Study on Heterogeneous Infrastructure. arXiv:2602.15214, 2026. نسخة أولية (Preprint). [20] R. Gupta and K. Nahrstedt. Performance Characterization of Containers in Edge Computing. arXiv:2505.02082, 2025. نسخة أولية (Preprint). [21] S. Checkoway, H. Shacham and E. Rescorla. Are Text-Only Data Formats Safe? Or, Use This LaTeX Class File to Pwn Your Computer. In Proc. USENIX Workshop on Large-Scale Exploits and Emergent Threats (LEET), 2010. [22] G. Lacombe, K. Masalygina, A. Tahiri, C. Adam and C. Lauradoux. Can You Accept LaTeX Files from Strangers? Ten Years Later. arXiv:2102.00856 [cs.CR], 2021. نسخة أولية (Preprint). [23] J. D. C. Little. A Proof for the Queuing Formula L=λWL=\lambda W. Operations Research, 9(3):383–387, 1961. [24] G. M. Amdahl. Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities. AFIPS, 1967.
آخر تعديل في ٥ أكتوبر ٢٠٢٦