هذا نمطٌ رأيته يتكرّر عشرات المرّات: فريقٌ يُطلق منتجه بسرعة، ويكسب أوائل عملائه، ثمّ يخبرني المؤسّس أنّ الخطّة هي «التفكير في التوسّع حين نبلغه». ثمّ يبلغونه فعلاً. يتضاعف عدد الزيارات ثلاثة أضعاف في عطلة إطلاقٍ واحدة، وتبدأ قاعدة البيانات في تجاوز المهل الزمنية، وفجأةً يجد ثلاثة مهندسين أنفسهم مستيقظين في الثانية صباحاً يحاولون إقحام طبقة تخزين مؤقّت (caching) لم يُصمَّم المخطّط (schema) لدعمها أصلاً. وإعادة الكتابة التي تعقب ذلك تكلّف ستّة أشهر وجزءاً من ثقة الفريق بنفسه.
القابلية للتوسّع ليست زرّاً تضغطه حين تصبح الأرقام مخيفة، بل مجموعة قرارات تتّخذها — أو تُخفق في اتّخاذها — بينما النظام لا يزال صغيراً بما يكفي ليتغيّر بكلفة زهيدة.
خرافة "سنضيفها لاحقاً"
القصّة المريحة تسير هكذا: ابنِ النسخة البسيطة الآن، وحين يظهر الطلب سيكون لديك الإيراد والكوادر لإعادة الهندسة. والمشكلة أنّ التوسّع لا يصل بأدب، بل يصل على هيئة عطل (incident). وحين تنشغل بإطفاء الحرائق، تكون القرارات نفسها التي حبستك — قاعدة بيانات مشتركة تمدّ إليها كلّ خدمة يدها، واستدعاءات متزامنة محبوكة داخل مسارات الطلبات، وحالة (state) مثبّتة على خادم واحد — قد صارت حاملةً للأحمال، فلا تستطيع انتزاعها دون أن يهتزّ البناء كلّه.
ولأكون واضحاً: أنا لا أدعو إلى تعقيدٍ استباقيّ؛ فبناء Kubernetes وevent sourcing لتطبيق فيه أربعون مستخدماً هو نوعٌ آخر من سوء الممارسة. الهدف ليس أن تبني من أجل التوسّع في اليوم الأوّل، بل أن تتجنّب إغلاق الخيار. وهذان انضباطان مختلفان تماماً، والخلط بينهما هو ما يجعل الفرق إمّا مُفرِطةً في الهندسة أو محبوسة.
صمّم للمفاصل، لا للحجم
القرارات التي تحدّد ما إذا كان النظام قادراً على النموّ نادراً ما تتعلّق بالقوّة الخام، بل بمواضع الحدود التي ترسمها. وقد وجدت في عملي مع العملاء أنّ حفنةً من الخيارات تنجز معظم العمل:
- انعدام الحالة عند الأطراف. إذا أمكن تلبية أيّ طلب عبر أيّ نسخة (instance) دون أن يعنيها مَن عالج الطلب السابق، فبإمكانك التوسّع أفقيّاً بمجرّد إضافة خوادم. أمّا حين تربط جلسة المستخدم بخادم بعينه، فأنت تضع سقفاً لقدرتك من حيث لا تشعر.
- امتلك حدود بياناتك. أغلى خطأ أراه هو أن تتشارك خدماتٌ كثيرة قاعدة بيانات واحدة وتمدّ كلٌّ منها يدها إلى جداول الأخرى. يبدو ذلك عمليّاً لسنة، ثمّ لا يعود أحدٌ قادراً على تغيير مخطّط دون اجتماعٍ بين الفرق، وتتحوّل قاعدة البيانات إلى عنق زجاجةٍ لا يتجاوزه شيء.
- اجعل ما هو بطيء غير متزامن مبكّراً. إرسال بريد، أو توليد تقرير، أو ترميز فيديو — إن لم يكن لزاماً أن يحدث داخل الطلب، فادفعه إلى طابور (queue). أمّا إضافة طابور بأثر رجعيّ فتعني فكّ تشابك منطقٍ افترض أنّ كلّ شيء يجري لحظيّاً.
- الـidempotency بوصفها عادة. ما إن تدخل محاولات الإعادة أو الطوابير أو تتعدّد النسخ، حتى تُنفَّذ العمليّات مرّتين. صمّم عمليّات الكتابة بحيث لا يضرّ تنفيذها مرّتين، فتُلغي فئةً كاملة من علل التوسّع قبل أن تُكتَب.
لا شيء من هذه يكلّف وقتاً يُذكر ما دام النظام صغيراً، لكنّها جميعاً باهظة الكلفة إلى حدٍّ قاسٍ حين يأتي إدخالها بعد أن تكون الزيارات قد صارت معتمدةً على السلوك القائم.
عنق الزجاجة هو قاعدة البيانات دائماً تقريباً
خوادم التطبيق سهلة؛ فأنت تضيف المزيد منها. أمّا قاعدة البيانات فهي المكان الذي يذهب إليه النموّ ليموت، وهي الطبقة التي تفكّر فيها الفرق أقلّ ممّا ينبغي. وإليك أموراً أحرص على توفّرها قبل أن أحتاجها بوقتٍ طويل:
اعرف أنماط الوصول قبل أن تختار نموذجك
معظم آلام التوسّع تعود إلى مخطّطٍ مُحسَّنٍ لشكل البيانات لا لكيفيّة قراءتها. فهرِس لاستعلاماتك الحقيقيّة، وأزِل التطبيع (denormalize) عن قصدٍ حيث تهيمن القراءات، واحسم مبكّراً ما إذا كانت قطعة بياناتٍ بعينها مصدرَ الحقيقة أم نسخةً مخزّنةً مؤقّتاً منه — فالخلط بينهما هو ما يولّد عللاً يستحيل عليك إعادة إنتاجها.
القراءات والكتابات لا تتوسّع بالطريقة نفسها
النسخ القرائيّة (read replicas) والتخزين المؤقّت وشبكات توصيل المحتوى (CDNs) تشتري لك هامشاً قرائيّاً هائلاً بثمنٍ زهيد. أمّا الكتابات فأصعب، وتفرض في النهاية خياراتٍ حقيقيّة — التجزئة (sharding)، أو التقسيم (partitioning)، أو إعادة النظر فيما يجب أن يكون متّسقاً بقوّة (strongly consistent). ومعرفة أيّ ضغطٍ ستصطدم به أوّلاً هي ما يدلّك على أين تصرف انتباهك.
القابلية للتوسّع نادراً ما تكون مشكلة أداء. إنّها مشكلة اقتران (coupling) ترتدي ثوب مشكلة أداء.
من أين تبدأ يوم الاثنين
لست بحاجة إلى إعادة هندسةٍ شاملة، بل إلى معرفة مواضع مفاصلك قبل أن يكتشفها الحِمل نيابةً عنك. وإليك ثلاث خطوات ملموسة:
- ارسم خريطة الاعتماديّات الحقيقيّة. أيّ خدمةٍ تلمس أيّ مخزن بيانات؟ كلّ جدولٍ مشترك هو مشكلة اقترانٍ مؤجّلة — علّمها الآن ما دام فكّها رخيصاً.
- اختبِر الحمل حتى الانهيار، مرّةً واحدة. لا لتجتاز معياراً مرجعيّاً، بل لتكتشف أوّل ما ينكسر — ونادراً ما يكون ما يخمّنه الفريق. عندئذٍ تصبح لديك قائمة حقيقيّة مرتّبة بدل القلق.
- دوّن افتراض نموّك. «عشرة أضعاف المستخدمين خلال 18 شهراً» و«استقرارٌ مع طفراتٍ عرضيّة» يستدعيان معماريّتين مختلفتين تماماً. ومعظم الفرق لم تنطق افتراضها بصوتٍ مسموع قطّ، فلم تصمّم لأيٍّ منهما.
الهندسة من أجل النموّ ليست تذهيباً زائداً، بل هي رفضٌ لأن تتّخذ اليوم قراراتٍ لن يقدر أنتَ المستقبليّ على التراجع عنها. وأفضل وقتٍ لشراء هذه الاختياريّة هو حين تكون شبه مجّانيّة — أي الآن.
انظر إلى نظامك واطرح سؤالاً واحداً: ما الذي سينكسر أوّلاً لو جلب يوم الاثنين عشرة أضعاف الزيارات — وهل تعرف الجواب حقّاً، أم أنّك تخمّن؟