# إيقاف الكتابة في بيئة واحدة

بعض أعمال المشغّل لا يمكن التراجع عنها إلا باستعادة نسخة احتياطية: ترقية يرحّل فيها إصدار Auth أو Storage أو Realtime المخططات، أو الاستعادة نفسها. الاستعادة تعيد قاعدة البيانات إلى لحظة أخذ النسخة، وتضيع كل كتابة جاءت بعدها. لذلك أوقف الكتابة في البيئة قبل أخذ تلك النسخة، وأبقِ الإيقاف حتى تنجح الفحوص التي تلي التغيير. الكتابات التي قبلتها البوابة قبيل الإيقاف إما أن تكتمل قبل أن تتوقف الخدمات التي تكتب، أي قبل أن يصبح الإيقاف قائمًا وقبل أخذ النسخة، وإما أن تُقطع ويُتراجع عنها؛ وقد يرى التطبيق خطأ حتى لكتابة اكتملت؛ وبعد ذلك لا تصل أي كتابة من التطبيقات إلى البيئة عبر البوابة أو منفذ قاعدة البيانات حتى تستأنف. لكن الكود الذي يعمل داخل التثبيت ما زال يستطيع الوصول إلى خدماتها عبر الشبكة الداخلية؛ راجع الحدود أدناه.

## الأوامر

```sh
python3 lab/write_pause.py pause <environment>
python3 lab/write_pause.py status
python3 lab/write_pause.py resume <environment>
```

يُكتب مكان `<environment>` معرّف التشغيل (`e_` و24 رمزًا ست عشريًا، وهو المسار في عنوان API الخاص بالبيئة من دون `/` في أوله).

## ماذا يفعل الإيقاف

يُكتب الإيقاف أولًا في `.lab/upstream/write-pauses/<environment>.json`. ثم بهذا الترتيب:

1. **لا تقبل البوابة أي طلب جديد.** يدخل توجيه البيئة في حالة الصيانة، وهي الحالة نفسها التي يستخدمها النقل إلى مكان آخر. تتلقى طلبات REST وAuth وStorage وFunctions ومقابس Realtime الجديدة الرمز 503 مع "Environment temporarily paused".
2. **يُغلق Studio ومنفذ قاعدة البيانات المباشر.** يُوقف Studio، ويُعطَّل حساب دخول المطوّر الخاص بالبيئة (وهو الحساب الذي يقبله منفذ قاعدة البيانات) وحساب دخول Studio، وتُنهى جلساتهما المفتوحة.
3. **يُقطع كل ما كان جاريًا.** ينتظر الإيقاف أولًا 15 ثانية، وهي أطول من الوقت الذي تحتاجه البوابة لقراءة جسم الطلب، فيكون كل طلب قُبل قبل الإيقاف قد وصل إلى خدمة. بعدها تتوقف حاويات Auth وREST وRealtime وFunctions الخاصة بالبيئة وعملية Storage المشتركة، وتُنهى كل جلسة باقية في قاعدة بيانات البيئة ويُنتظر انتهاؤها، ثم تُشغَّل من جديد ما عدا Edge Functions التي تبقى متوقفة حتى `resume`؛ ويجب أن يرد Auth وREST قبل أن يتابع الإيقاف. ينتهي كل طلب ورفع ودالة ومقبس Realtime مفتوح، ويُتراجع عن معاملته المفتوحة. إعادة تشغيل Storage توقف Storage لكل البيئات بضع ثوانٍ وتقطع عمليات الرفع الجارية في البيئات الأخرى، كما يفعل `restore-storage`. ويرفض الإيقاف بدء ذلك أثناء نسخ احتياطي أو استعادة.

بعد ذلك فقط يُسجَّل الإيقاف على أنه قائم. يعرض `status` الحالة `held`؛ خذ النسخة الاحتياطية بعدها.

تعود خدمات البيئة نفسها إلى العمل، فما زالت الترقية قادرة على ترحيلها، وما زالت فحوص الصحة الخاصة بها تفحصها مباشرة؛ ولا تُترك إلا الفحوص التي تمر عبر البوابة لبيئة موقوفة. يُرفض فتح Studio وتشغيل الوصول المباشر إلى قاعدة البيانات وتشغيل Edge Functions ما دام الإيقاف قائمًا (وتنتظر طلبات لوحة الإدارة لتشغيل الوصول إلى قاعدة البيانات أو Edge Functions، راجع الاستئناف؛ أما فتح Studio فيفشل، فافتحه من جديد بعد `resume`). ويبدأ رفضها لحظة تسجيل الإيقاف، أي عند بدئه. أما تشغيل Studio الذي فتح حساب دخوله قبل بدء الإيقاف فيكتمل أولًا، ثم يغلقه الإيقاف؛ والتشغيل الذي لم يبلغ هذه الخطوة يفشل، فافتح Studio من جديد بعد `resume`. وما دامت أي عملية أخرى على التشغيل جارية، ومنها تشغيل الوصول إلى قاعدة البيانات أو Edge Functions أو إطفاؤهما، يرفض `pause` و`resume` ويطلبان منك المحاولة بعد انتهائها؛ وقد يظهر طلب من لوحة الإدارة بدأ في تلك اللحظة نفسها على أنه فشل، فأعده بعد `resume`.

يبقى الإيقاف قائمًا بعد إعادة التشغيل حتى تستأنف. وتشغيل `pause` مرة أخرى وهو قائم لا يغيّر شيئًا. إذا توقف `pause` في منتصفه، فإنه يذكر ذلك ويعيد تشغيل Auth وREST وRealtime وStorage إن كان قد أوقفها؛ أما Edge Functions فتبقى متوقفة حتى `resume`، ويبقى Studio مغلقًا حتى يفتحه أحد. وإذا ضاع رد تغيير التوجيه، يقرأ الإيقاف التوجيه من جديد قبل أن يتابع، فيحتفظ بالتغيير الذي حدث على أنه تغييره، ولا ينسب إلى نفسه لاحقًا تغييرًا لم يحدث. وإذا توقف الإيقاف نفسه أثناء هذا الطلب، بإنهائه أو بانقطاعه، أو فشلت القراءة، فإن الأمر التالي `pause` أو `resume` يقرأ التوجيه: إذا لم تكن البيئة في الصيانة يتابع ببساطة. وإذا كانت فيها، فلا يستطيع الإيقاف أن يعرف لمن هذه الصيانة، فيتوقف ويطلب منك أن تتحقق أن عملية أخرى، مثل تصدير التحويل أو نقل البيئة، لا تمسك البيئة، ثم تحسم ذلك بتشغيل `write_pause.py pause <environment> --adopt-maintenance`. ويبقى السجل، فيمنع تشغيل Studio والوصول المباشر إلى قاعدة البيانات وEdge Functions حتى تشغّل `pause` مرة أخرى ليكمل، أو `resume` لتلغيه؛ لكن ما كان يعمل قبل ذلك قد يبقى يعمل حتى حينها، فلا يحمي الإيقاف شيئًا قبل أن يعرض `status` الحالة `held`. وإذا شغّلت `resume` على إيقاف لم يُسجَّل قائمًا، فإنه يعيد أيضًا تشغيل الخدمات التي أوقفها: Auth وREST وRealtime وFunctions وStorage؛ ويبقى Studio مغلقًا حتى يفتحه أحد.

## الاستئناف

يسجّل `resume` الإيقاف أولًا على أنه قيد الاستئناف، فلا يعرض `status` الحالة `held` بعد أن تصبح الكتابة ممكنة من جديد. ثم يُخرج البيئة من الصيانة، ويعيد تشغيل حساب دخول المطوّر إذا كان الوصول المباشر إلى قاعدة البيانات مفعّلًا لهذه البيئة، ويحذف السجل في النهاية؛ وإذا توقف في منتصفه يبقى الإيقاف مسجّلًا على أنه قيد الاستئناف فتشغّله مرة أخرى. الوصول الذي عُطِّل أثناء الإيقاف يبقى معطَّلًا. ويعود حساب دخول المطوّر كما يعيده تشغيل الوصول، بكلمة المرور التي حفظتها لوحة الإدارة آخر مرة، فلا تسري إعادة تعيين كلمة المرور التي جرت أثناء الإيقاف إلا الآن، وتبقى كلمة المرور القديمة ملغاة. والطلبات التي تُرسل من لوحة الإدارة أثناء الإيقاف لتشغيل الوصول المباشر إلى قاعدة البيانات أو Edge Functions، ومنها إعادة تعيين كلمة المرور، تبقى معلّقة وتُطبَّق بعد `resume`. ويبقى حساب دخول Studio مغلقًا حتى يفتح أحدهم Studio. تصل التطبيقات إلى البيئة من جديد فورًا.

## الحدود

- يرفض الإيقاف بيئة تضعها عملية أخرى في الصيانة؛ تلك العملية هي التي تحررها. ويرفض دائمًا بيئة نُقلت إلى مكان تشغيل آخر، لأن هذا الإيقاف لا يشمل إلا البيئات التي لم تُنقل قط. وإذا وضعت عملية أخرى البيئة في الصيانة بعد بدء الإيقاف، يرفض `pause` و`resume` كلاهما ويتركانها لتلك العملية؛ شغّل `resume` بعد أن تحررها.
- ليس حاجزًا في وجه المشغّل، ولا يوقف حسابات دخول الخدمات الخاصة بالبيئة نفسها.
- يغلق البوابة ومنفذ قاعدة البيانات، لا الشبكة الداخلية للتثبيت. ما دام قائمًا تعمل خدمات Auth وREST وRealtime الخاصة بالبيئة وStorage المشترك، ويمكن الوصول إليها بأسماء حاوياتها من كود يعمل داخل التثبيت، مثل Edge Functions الخاصة ببيئة أخرى. والكتابة التي تصل بهذه الطريقة أثناء الإيقاف تضيع عند استعادة لاحقة. وهذا صحيح عن الشبكة الداخلية في كل الأوقات، لا أثناء الإيقاف وحده.
- يعمل على بيئة واحدة في كل مرة. أوقف كل بيئة تمسّها الترقية.
- إعادة تشغيل صباربيز أثناء الإيقاف، مثل التي تجريها الترقية، وتدوير مفتاح التوقيع، يتركان Edge Functions الخاصة بالبيئة متوقفة؛ ويعيد `resume` تشغيلها في حاوية جديدة بالصورة الحالية ومفتاح التوقيع الحالي، كما يفعل تشغيلها من لوحة الإدارة.
- إذا أُنهيت عملية `pause` والخدمات التي تكتب متوقفة، فإنها تبقى متوقفة، ومعها Storage المشترك لكل البيئات، حتى تشغّل `pause` أو `resume` من جديد، أو يبدأ صباربيز من جديد فيشغّلها ويترك Edge Functions متوقفة.
- لا تغطيه إلا اختبارات الوحدات؛ وتمرين حي على جهاز المختبر مخطط قبل استخدامه في أي ترقية.
