الجزء 4: Valkey، Queues و Logs، الأجزاء الهادية التي تكسر أولاً
الجزء 4 من دراسة NovaCommerce: 2,854 Valkey timeouts، عامل واحد الذي لا يزال single point of failure، queue أنه أخّر merit cards بـ 115 دقيقة، AI provider الذي قال لا، و rate limiter التي احسبت البناء.
اختبار تحميل backend في 02:28 في 6 أكتوبر جاء بـ 2,854 خطأ تطبيق. كل واحد كانت جملة نفسها، ألقيت أثناء الاتصال: RedisException: Operation timed out. خوادم الويب كانت صحية وقاعدة البيانات كانت صحية. الجزء الذي قال لا كان الخدمة الهادية التي تمسك جلسات، cache و طوابير.
نفس الأسبوع أنتج رقم ثاني: 115. ذلك كم دقيقة أبطأ merit card انتظرت ليُحسب بعد امتحان. CPU العامل لم تكن السبب، و لا WebSockets. الوظيفة كانت ببساطة تقف في الطابور الخاطئ.
الجزء 3 غطى قواعس البيانات. هذا الجزء يغطي القطع التي ربط فلك بدون حالة: Valkey، العامل الثابت الوحيد، الطوابير، حدود المعدل، السجلات و الإشارات ننظر. تبدو ممل على رسم، وأعطتنا الكثير من المفاجآت الحقيقية. كل قسم أسفل فشل، سبب و تغيير.
هذا الجزء 4 من سلسلة خماسية «من خادم واحد إلى جاهز لليلة الامتحان». NovaCommerce اسم خيالي؛ المعمارية والأرقام والأخطاء حقيقية.
فلك بلا ذاكرة يحتاج مكان تتذكر
عقدة يمكن حذفها في دقيقة واحدة لا تستطيع امتلك أي شيء. على هذه المنصة الملاك قائمة قصيرة: Managed MySQL لبيانات التطبيق، Managed PostgreSQL مع pgvector لـ AI embeddings، Spaces object storage لـ ملفات، Managed OpenSearch لـ السجلات، و Managed Valkey لكل شيء صغير، سريع و مشترك: جلسات، cache و طوابير.
فكر من أمناء صندوق supermarket. درج التسجيل والقائمة مخزون تجلس في مكتب خلفي. أمين صندوق يمكن يذهب البيت شِبه وآخر يأخذ الحارة، لأن لا شيء كان في جيب أمين الصندوق.
عقد فوق تمسك بلا شيء. كل شيء يجب أن ينجو حذف عقدة يعيش في أحد الخمس متاجر أسفل.
فحصنا ذلك بدلاً من افتراض. في 7 أكتوبر backend nodes كتب 0 ملفات لـ local disk في 24 ساعة. تحميلات، يشمل student written-answer PDFs، تذهب مباشرة إلى Spaces؛ جلسات و cache في Valkey؛ السجلات تذهب stdout. Implemented و verified. ذلك لماذا autoscale pools قد تحذف أي عقدة في أي وقت.
الحالة
حيث تعيش
ما تشتري
الجلسات
Valkey (primary + standby)
أي backend عقدة يمكن تخدم أي طالب
Cache
Valkey
Cache واحد تشارك كل عقدة
وظائف queued
Valkey
الإرسالات انتظار بأمان إذا العامل نزل
تحميلات، answer PDFs، media
Spaces مع CDN
لا ملف يجلس على disk عقدة
السجلات
stdout، شُحنت إلى OpenSearch
هم تنجو العقدة (backend done، frontend planned)
بيانات التطبيق
Managed MySQL
غطى في الجزء 3
Valkey واحد، بقصد
Cache، جلسات و طوابير تشارك Valkey managed واحد 8 مع عقدة standby. eviction policy هو noeviction: عندما ذاكرة ممتلئة، Valkey ترفض كتابات جديدة مع خطأ بدلاً من هادي حذف مفاتيح قديمة. لـ cache نقي ذلك يصوت للخلف. لكن ذلك مثال أيضاً تمسك جلسات و وظائف queued، و نحن نفضل خطأ بصراحة على وظيفة تختفي بدون أثر. نحن بعيد من ذلك الحافة: على مساء امتحان من 7 أكتوبر ذلك استخدمت حول 12.5% من ذاكرتها.
2,854 timeouts: عندما الذاكرة المشتركة لم تستطيع أجاب
خلفي إلى 02:28. الاختبار كان يضرب Valkey من 4 GB مع primary و standby. الذاكرة لم تكن المشكلة. تحت حمل لم تستطيع قبول اتصالات جديدة بسرعة كافية، وكل طلب انتظر أطول من timeout الاتصال 5 ثانية أصبح 500.
جزء من الضغط كان لنا. كل طلب فتح اتصال TLS جديد إلى Valkey، الذي قسناه في حول 5.4 ملسة من CPU لكل طلب (حول 3.3 ملسة لـ MySQL). في 65 إلى 70 طلب في الثانية عقدة backend واحد تستطيع تخدم، handshake وحده يكلّف تقريباً ثلث core. ذلك back-of-the-envelope arithmetic، ليس profile.
الإصلاح كان resize، done في المكان: Valkey ذهب من 4 GB إلى 8 GB، اثنين عقدة (primary زائد standby)، مع البيانات احتفظت والـ host name بدون تغيير. لا تغيير تطبيق، لا نقل. Implemented.
بعدها قسنا ثانية. Tested: ارتقاء تدريجي من 25 إلى 500 طلب في الثانية، ابتداء على اثنين backend عقدة بينما البركة تتسع، أعطى 0 أخطاء تطبيق و 0 backend 5xx. Load balancer أظهر 143 أخطاء، لكن فقط بينما الأنوية كانت CPU-saturated: الإشارة المتوقع «خارج الطاقة»، ليس خطأ.
الدرس المفيد هو الاختناق تحرك ثلاث مرات في عدة أيام، وكل حركة احتاجت نوع مختلف من الإصلاح.
الترتيب
اختناق
نوع مشكلة
إصلاح
1
API rate limiter مفتاح على IP
الكود و configuration
مفتاح حسب حساب (Implemented)
2
Valkey اتصالات
تحجيم طبقة البيانات
4 GB إلى 8 GB، 2 عقدة (Implemented)
3
Backend CPU لكل عقدة
طاقة عادية
عقد أكثر، pre-scaled (Implemented)
فقط آخر صف يُحل بـ إضافة خوادم. للثاني، خوادم أكثر كانت ستفتح حتى أكثر اتصالات في Valkey نفس.
عامل واحد، والوظائف فقط هو يمكن يفعل
كل شيء حول العامل يتسع. العامل نفسه لا يفعل. هو droplet ثابت واحد (4 vCPU، 8 GB) و يحمل أربع وظائف:
الطوابير. الافتراضي، امتحان، ثلاثة notification queues، OMR و enrollment reports، يشغلها Horizon.
المجدول. Timed tasks يجب تشغل بالضبط مرة واحدة.
WebSockets. Reverb تخدم updates مباشرة. منفذ الخاص به فتح فقط إلى backend nodes، بـ وسم.
كل SMS. بوابة SMS ينطبق IP عنوان واحد، لذا SMS يمكن فقط ترك من هذا الجهاز.
هذا لماذا طوابير، المجدول، WebSockets و OMR مطفأة على عقد ويب: عقدة autoscaled جديدة أبداً يجب تشغل وظيفة مرتين. ما قضيتنا يضيف إلى القاعدة الشاملة هو ذلك SMS مرتبط بـ عنوان، ليس فقط عملية.
هو أيضاً نقطة فشل مفردة، و نحن يقول ذلك بصراحة. إذا droplet هذا مات، SMS، المجدول و معالجة امتحان توقف. الطلاب تبقي إرسال، و إرسالاتهم انتظار بأمان في Valkey، وهو السبب الطوابير تعيش هناك. لكن لا شيء معالج هم حتى عامل عودة.
في مساء امتحان من 7 أكتوبر العامل قمم في 46% CPU مع 0 وظائف فشل، لذا هذا خطر خطة نحن، ليس فشل رأينا. الخطة، مع تسميات صادقة:
خطوة
لماذا
Status
محجوز IP، whitelist مع بوابة SMS
عامل استبدال يرسل SMS من عنوان معروف
Planned
العامل standby صغير
شيء جاهز للاستيلاء على طوابير و المجدول
Planned
Exam-only burst worker image، بدأت قبل امتحانات كبيرة
طاقة queue إضافية عندما تُحتاج
Planned
Snapshot واحد من العامل
إعادة بناء يبدأ من صورة، ليس من الذاكرة
Implemented
المحجوز IP يأتي أولاً: بدونها، عامل standby كان سيرسل SMS من عنوان البوابة لا تقبل.
Merit card التي انتظرت 115 دقيقة
بعد امتحان، وظيفة «merit recompute» ينعش كل طالب merit card. في 6 أكتوبر تلك البطاقات بدأت الظهور 10 إلى 115 دقائق متأخرة.
ما نفينا
المريبين الواضحين كانوا CPU العامل و خادم WebSocket. لا أحد كان السبب.
ما كان عليه
Merit job صغير: 0.34 ثانية. شاركت queue واحد، يخدم 2 workers، مع وظيفة AI التي تجعل استدعاء language-model واحد حول 10 ثوان لكل طالب. بعد امتحان، حول 2,000 من تلك وظائف AI تم enqueued، و كل merit job جلس خلفهم.
الحساب يُظهر المقياس. وظيفة AI واحدة تأخذ بقدر حول 29 merit وظائف. ألفان من هم تقريباً 20,000 ثانية من العمل، و عبر 2 workers ذلك تقريباً ثلاث ساعات من الخط. انتظار من 10 إلى 115 دقيقة يناسب ذلك الصورة.
هو express lane في supermarket: رغيف خبز يجب أن لا ينتظر خلف عربة ممتلئة، و أحنا كان بنينا تسجيل واحد للجميع.
وظائف نفس، طوبولوجيا مختلفة: على اليمين merit job أبداً لا تلتقي وظيفة AI، و AI lane لها خاصة بها pace.
التغيير
Implemented: حارة queue منفصلة و container خاص، فقط لـ AI narratives، مع 2 replicas. Merit و position وظائف أبداً لا تنتظر language model ثانية، و merit cards جاهزة في حول دقيقة واحدة. وظائف AI بالفعل انتظار تحركت إلى حارة جديدة مع script ذري واحد، لذا كل وظيفة كانت في مكان بالضبط في كل لحظة.
الإصلاح كان طوبولوجيا، ليس horsepower. القاعدة شاملة من queue واحد لكل نوع عمل في Queues and Workers at Scale؛ ما فاجأنا كم بدون ضررها وظيفة بطيئة بدت.
بعدها AI provider قال لا
حارة جديدة حمت merit cards. هي لم تجعل وظائف AI أسرع. حارة صدمت حد معدل الموفر، حول 7 إلى 10 استدعاءات دقيقة، و مرة واحد حساب موفر ببساطة نفذ من رصيد.
كلاهما فشل نفس: خدمة خارجية تقول «ليس الآن». حلقة إعادة محاولة ذلك مطرقة ذلك فقط يحرق ميزانية و يملأ جدول وظائف فشل، لذا حارة أُعيد تصميم ليكون صبور. Implemented و live على العامل منذ 6 أكتوبر:
آلية
إعداد
ما يفعل
محدِّد معدل مشترك
8 في الدقيقة عبر جميع workers
يبقي الاستدعاءات داخل ما الموفر يسمح
Release، ليس فشل
Exponential backoff، 1 إلى 15 دقيقة، مع jitter
وظيفة مرفوضة تعود إلى queue؛ jitter يوقفهم العودة معاً
نافذة إعادة محاولة
حتى 12 ساعة
الوظيفة تبقي محاولة بعد المسارعة بطويلة
رد الميزانية
عداد ميزانية AI شهري
استدعاء مرفوض لا يُشحن
Fallback
نص template
الطالب يرى narrative عام بينما
الطلاب أبداً لا يرى خطأ. نص template هناك عندما يفتحون الصفحة، و narrative حقيقي يستبدل عندما دوره يأتي.
اختبر لـ حقيقي داخل يوم. حساب موفر نفذ من رصيد، حول 3,800 narratives كومة حتى وظائف مؤجلة، و لا واحد فشل. بعد ظهر التالي رصيد أُضيف، و أول narrative جديد كُتبت في دقائق، مع لا restart و لا replay يدوي.
الحساب يُظهر لماذا النافذة ساعات: 2,000 narratives في 8 دقيقة حول أربع ساعات عمل. نافذة من دقائق قليلة حول سقط معظمهم.
تشارك محدِّد معدل مهم. replicas اثنين تلك كل pace نفسهم في 4 دقيقة عمل حتى شخص يضيف ثلث. عداد مشترك واحد يبقي الإجمالي صادقة مهما عديد workers موجود.
حدود المعدل: احسب الطالب، ليس البناء
محدِّد معدل جيد فقط بقدر الشيء يحسب. حصلنا هذا خاطئ مرتين، في اثنين طبقات، و مرتين الأعراض كانوا الطلاب استقبال HTTP 429 وسط امتحان.
عندما
ما كان مَحسوب
ما خطأ
إصلاح
25 Sep
Global API limiter، مفتاح على IP (حارس افتراضي فارغ لـ token-based طلاب)
مدرسة أو carrier جوال يضع مئات الطلاب خلف IP NAT واحد: جرة واحد مشترك
مفتاح حسب طالب أو معلم حساب، IP فقط لـ ضيوف (Implemented)
7 Oct
Route throttles مثل «30 في الدقيقة»، لا يزال بـ client IP
Backend رأى IP proxy frontend واحد للجميع: 74% من استدعاءات إلى endpoint dashboard واحد حصل 429
احسب لكل طالب لكل route، ضيوف بـ IP؛ 0 مثل 429s بعده (Implemented)
كل استدعاء browser API يذهب عبر frontend proxy، لذا قاعدة per-IP تعامل امتحان قاعة كلها كزائر واحد.
كلا الإصلاحات جاءت مع اختبارات التي تفشل بدون الإصلاح؛ الثاني تُنشرت عقدة واحدة في وقت مع zero downtime. Planned: مرر طالب IP حقيقي من frontend إلى backend في رأس موقع، لذا كل قاعدة per-IP و كل سطر log دقيق.
Proxy موثوق قديم
خطر أصغر جلس بقرب. Backend لا يزال موثوق IP الخادم frontend القديم، الذي حذفنا. إذا السحابة أي وقت أعطت ذلك عنوان لشخص آخر، كانوا يستطيعون تزييف طالب IPs. أزلناه في 7 أكتوبر مع drain-one-node method، مرة أخرى مع zero downtime. Implemented. قائمة ثقة قائمة وعود؛ حذف تلك أصحابهم ذهبوا.
السجلات التي تنجو العقدة
مع autoscaling، الجهاز تريد تفحص غالباً ذهب. لذا سجلات يجب ترك عقدة كما يُكتبة. حاويات لنا تكتب فقط إلى stdout و stderr، و Fluent Bit تحميل backend حاوية السجلات إلى Managed OpenSearch، stack ELK-style: مكان واحد قابل للبحث احتفظ بعد العقدة ذهبت. Implemented لـ backend nodes. الطريقة الشاملة في Observability for High-Volume Systems.
دفعت نفسها في عمل الطاقة. اثنين دقيقة متوسط DigitalOcean وضع 7 October backend peak في 63 طلب في الثانية؛ لكل دقيقة السجلات قالوا حول 69. متوسطات إخفاء peaks، و نموذج الطاقة يحتاج peak.
فجوة موجود. Frontend nodes لا يزالون يبقون nginx السجلات على local disk، حول 440 MB يوماً، و يخسرونها عندما عقدة تُزال. Planned: Fluent Bit على frontend عقد أيضاً. حتى ذلك الحين، frontend هي الطبقة الواحدة حيث scale-in يمسح الدليل.
ما ننظر، و ما نفعل عن ذلك
ops console لنا read-only: عينات و أبداً يتغير أي شيء. ذلك يسجل CPU لكل عقدة و container، pool CPU، MySQL threads جاري، اتصالات و lock waits، Valkey ذاكرة، clients و عمليات في الثانية، و صحة queue. DigitalOcean مراقبة نفس يضيف طلبات في الثانية و فئات الرد في load balancers.
أثناء امتحان نحن لا ننظر إلى كل ذلك. نقرأ digest مرة في دقيقة: الطلاب بدأوا و أرسلوا، طلبات و 5xx لكل عقدة، pool CPU، حمل عامل و وظائف فشلت. أرقام قليلة، كل واحد مرتبط إلى قرار.
الإشارات تغذي digest، digest تُشعل واحدة من عدة أفعال معروفة، و كل حادثة عود كقاعدة runbook.
التصرف على إشارة
الحلقة لديها مجموعة صغيرة من الأفعال، وصفت في الجزء 2:
Drain عقدة. اجعل /lb-health ترجع 503، و load balancer توقف إرسال طلبات جديدة ضمن حول 30 ثانية.
Roll back الصورة. نحتفظ آخر ثلاث صور جيدة، و نحن أبداً لا نرول template أثناء ساعات امتحان.
Pre-scale. رفع pool minimum حول 45 دقيقة قبل امتحان كبير. Reactive scaling الشبكة الأمان فقط، لأن CPU metric DigitalOcean يتخلف الحمل الحقيقي بـ 5 إلى 8 دقائق.
في rollout كامل من كلا البركتين في 7 أكتوبر، uptime probe ضرب صفحات حقيقية كل 2 ثانية: 219 probes، 0 أخطاء. ذلك حلقة إغلاق: تغيير، ننظر، قِس.
خطأ scale-in
أثناء امتحان حقيقي في 7 أكتوبر، شخص خفض backend pool minimum. DigitalOcean أزالت عقدة يخدم بدون draining، و حول 360 طلب فشل في 2 دقائق.
كانت إنسان slip و سلوك منصة في نفس الوقت: autoscale scale-in لا يفرّغ. القاعدة الآن في runbook (Implemented كقاعدة runbook): رفع minimum قبل امتحان، و خفض فقط بعد. نعرف حده: قاعدة runbook تعتمد على شخص تتذكر ذلك وسط امتحان. هو حارس، ليس قفل.
ما تعلمنا
عقد بدون حالة آمنة فقط بقدر متاجر مشترك خلفهم. حجم Valkey لـ اتصالات، ليس فقط الذاكرة.
Queue واحد لكل نوع عمل. وظيفة 0.34 ثانية يجب أبداً لا تنتظر خلف واحد 10 ثانية.
عندما موفر يحد، pace الاستدعاءات، إرجع مع jitter، أعد محاولة ساعات، رد الميزانية و أرِ fallback.
افحص ما كل محدِّد معدل يحسب. الطالب الوحدة، ليس البناء و ليس proxy.
شحن السجلات خارج العقدة كما يُكتبة، و أغلق فجوة frontend قبل تكلّف حادثة.
اكتب نقاط فشل مفردة مع status بجانب كل واحد. لنا عامل واحد، مع الإصلاحات المخطط و لا تظاهر.
Autoscale scale-in لا يفرّغ. رفع minimum قبل امتحان.
في الجزء 5، الأخير من السلسلة، نضعها معاً: مساء امتحان حقيقي من 7 أكتوبر مع أرقام إنتاج، نموذج الطاقة، التكلفة الشهرية، و load و soak اختبار ننشد أنفسنا.