توسيع أنظمة الباكند بدون إفراط في الهندسة: قائمة تحقق YAGNI للمعمارية
متى توسع ومتى لا. Monolith + Queue كأكثر معمارية مقللة من قيمتها، محفزات التوسع الحقيقية، اختناقات قاعدة البيانات، استراتيجية التخزين المؤقت، وقائمة تحقق YAGNI لقرارات المعمارية.
وباء الإفراط في الهندسة
شفناها كثير: فريق من 3 مهندسين يبني معمارية مايكروسيرفسز لتطبيق عنده 200 مستخدم. Kubernetes cluster، Service Mesh، Event Bus، API Gateway، 5 قواعد بيانات منفصلة، وستاك مراقبة أعقد من التطبيق نفسه. بعد ستة أشهر، يدبغون مشاكل أنظمة موزعة بدل ما يبنون ميزات.
العكس كمان يصير: مونوليث يخدم 50,000 مستخدم متزامن، وكل طلب ياخذ 5 ثواني لأن قاعدة البيانات هي الاختناق وما فيه طبقة تخزين مؤقت. الفريق يضيف سيرفرات تطبيق أكثر، لكن قاعدة البيانات هي السقف.
كلاهما فشل معماري. هالمقال يغطي متى توسع، شو توسع، وكيف تتجنب توسيع أشياء ما تحتاجه. لأنماط توسع محددة، شوف دليل معمارية النظام ودليل المعمارية المبنية على الأحداث.
قائمة تحقق YAGNI للمعمارية
قبل ما تضيف أي تعقيد معماري (خدمة جديدة، قاعدة بيانات جديدة، message broker جديد، طبقة cache جديدة)، اسأل نفسك:
| السؤال | إذا نعم | إذا لا |
|---|---|---|
| عندنا مشكلة أداء مقاسة حالياً؟ | حل المشكلة المحددة | لا تضيف تعقيد لمشكلة افتراضية |
| هل هذا يحل مشكلة واجهناها فعلاً؟ | فكر فيه، مع أدلة | لا تحل مشاكل ما صادفتها بعد |
| نقدر نحلها بطريقة أبسط أولاً؟ | استخدم الطريقة الأبسط | الحل المعقد ممكن يكون ضروري |
| هل 10x حركة مرور أكثر راح تكسر المعمارية الحالية؟ | خطط مسبقاً (لكن لا تبني بعد) | المعمارية الحالية تمام |
| الفريق كبير كفاية ليصون هذا؟ | تابع إذا على الأقل شخصين يقدرون يملكونه | لا تضيف أشياء ما حد يقدر يصونها |
| نقدر نرجع فيها إذا ما نفعت؟ | مخاطرة مقبولة | خطر كبير |
الجواب الافتراضي على "هل نضيف هذا؟" هو لا. أضف تعقيد لما عندك أدلة إنه ضروري، مش لما تتخيل إنه ممكن يكون ضروري.
Monolith + Queue: أكثر معمارية مقللة من قيمتها
تطبيق مونوليثي مع job queue للخلفية يتعامل مع 90% من أحمال عمل SaaS. كودبيس واحد، deployment واحد، قاعدة بيانات واحدة، وqueue للعمل الخلفي.
┌─────────────────────────────────────────┐
│ Monolith │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ API │ │ Admin │ │
│ │ Routes │ │ UI │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌────▼──────────────▼─────┐ │
│ │ Services │ │
│ │ (منطق الأعمال) │ │
│ └────┬────────────────────┘ │
│ │ │
│ ┌────▼──────────────────────┐ │
│ │ قاعدة البيانات (PostgreSQL)│ │
│ └───────────────────────────┘ │
│ │
│ ┌───────────────────────────┐ │
│ │ Job Queue (BullMQ/Redis) │ │
│ │ - إرسال الإيميلات │ │
│ │ - فهرسة البحث │ │
│ │ - توليد التقارير │ │
│ │ - استيراد البيانات │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────┘
ليش يشتغل
- كودبيس واحد: الـ Refactoring آمن. البحث والاستبدال يشتغل. الأنواع تتدفق عبر التطبيق كله.
- Deployment واحد: انشر مرة. ما فيه تنسيق خدمات. ما فيه rollbacks موزعة.
- قاعدة بيانات مشتركة: الـ Joins تشتغل. المعاملات ACID. ما فيه صداع Eventual Consistency.
- Job Queue: الشغل طويل المدة (إيميلات، استيراد، فهرسة) ما يحجز معالجة الطلبات.
- تصحيح أخطاء بسيط: Stack traces كاملة. اللوقات في مكان واحد. ما تحتاج Distributed Tracing.
متى Monolith + Queue يوصل حدوده
| العرض | السبب | الحل |
|---|---|---|
| وقت رد API > 2 ثانية | استعلامات قاعدة البيانات بطيئة جداً | إضافة فهارس، تحسين الاستعلامات، Read Replicas |
| CPU قاعدة البيانات > 80% مستمر | حجم أو تعقيد الاستعلامات عالي جداً | Read Replicas، طبقة تخزين مؤقت، تحسين الاستعلامات |
| الـ Deploys تخوف | الكودبيس كبيرة جداً، التيستات بطيئة جداً | تيستات أفضل، Feature Flags، Canary Deploys (مش مايكروسيرفسز) |
| ميزة واحدة تكسر كل شي | ما فيه حدود وحدات | تنظيم كود أفضل (مش مايكروسيرفسز) |
| الفريق ما يقدر يشتغل بالتوازي | تعارضات كود، جحيم الـ Merge | ملكية الوحدات، Trunk-Based Development |
لاحظ: ولا واحد من هالأعراض ينحل بإضافة مايكروسيرفسز. ينحلون بهندسة أفضل داخل المونوليث. المايكروسيرفسز تحل مشكلة مختلفة.
متى تفصل: المحفزات الحقيقية
افصل لخدمات منفصلة بس لما:
1. حجم الفريق يفرض ذلك. لما عندك 20+ مهندس وحدود الفريق ما تتوافق مع الكودبيس. فريق A يملك ميزة X وفريق B يملك ميزة Y، ويحتاجون ينشرون بشكل مستقل. هذا السبب الرئيسي للمايكروسيرفسز.
2. متطلبات توسع مختلفة. خدمة البحث تحتاج 10x حوسبة أكثر من خدمة كتالوج المنتجات. تشغيلهم في نفس العملية يهدر موارد. خدمات منفصلة تقدر توسع بشكل مستقل.
3. متطلبات تقنية مختلفة. خدمة ML Inference تحتاج Python و GPUs. خدمة API تحتاج Node.js. تشغيل كلاهما في عملية واحدة غير عملي.
4. عزل الأعطال. كراش في خدمة الدفع ما لازم يوقع خدمة كتالوج المنتجات. لما عزل الأعطال حرج للأعمال، العمليات المنفصلة تساعد.
5. حدود الامتثال. معالجة الدفع المتوافقة مع PCI لازم تنفصل عن باقي التطبيق لأغراض التدقيق. خدمة منفصلة بحدود أمنية خاصة تبسط الامتثال.
شو مش سبب للفصل
- "المايكروسيرفسز أفضل ممارسة" (هي مقايضة، مش أفضل ممارسة)
- "ممكن نحتاج نوسع" (وسع لما تحتاج، مش قبل)
- "Clean Architecture" (حدود الوحدات داخل المونوليث أنظف من حدود الخدمات)
- "تطوير مدفوع بالسيرة الذاتية" (Kubernetes على سيرتك الذاتية ما يساعد مستخدميك)
قاعدة البيانات عادةً هي الاختناق
في 80% من مشاكل الأداء، قاعدة البيانات هي الاختناق. مش كود التطبيق. مش الشبكة. قاعدة البيانات.
التشخيص
-- إيجاد الاستعلامات البطيئة (PostgreSQL)
SELECT query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
-- إيجاد الفهارس الناقصة
SELECT schemaname, tablename, seq_scan, idx_scan,
CASE WHEN seq_scan > 0 THEN round(100.0 * idx_scan / (seq_scan + idx_scan), 1) ELSE 100 END AS idx_pct
FROM pg_stat_user_tables
WHERE seq_scan > 100
ORDER BY seq_scan DESC
LIMIT 20;
-- إيجاد تضخم الجداول
SELECT tablename, pg_size_pretty(pg_total_relation_size(tablename::regclass)) AS total_size,
pg_size_pretty(pg_relation_size(tablename::regclass)) AS data_size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(tablename::regclass) DESC
LIMIT 20;
ترتيب الإصلاح
- إضافة الفهارس الناقصة (دقائق للتطبيق، تأثير ضخم)
- تحسين الاستعلامات البطيئة (إعادة كتابة استعلامات N+1، إضافة WHERE، استخدام EXPLAIN ANALYZE)
- إضافة تخزين مؤقت (Redis للبيانات كثيرة القراءة قليلة التغيير)
- Read Replicas (توجيه استعلامات القراءة للنسخ، الكتابة للأساسي)
- Connection Pooling (PgBouncer بين التطبيق وقاعدة البيانات)
- التوسع العمودي (مثيل أكبر، ذاكرة أكثر، تخزين أسرع)
- التوسع الأفقي (Sharding، بس لما كل شي ثاني ينتهي)
أغلب الفرق تقفز للخطوة 7 قبل ما تجرب الخطوات 1-3. الخطوات 1-3 مجانية أو رخيصة وغالباً تحل المشكلة بالكامل.
استراتيجية التخزين المؤقت
شو تخزن مؤقتاً
| البيانات | تخزين مؤقت؟ | TTL | الإبطال |
|---|---|---|---|
| جلسات المستخدم | نعم | مدة الجلسة | عند تسجيل الخروج |
| كتالوج المنتجات | نعم | 5-15 دقيقة | عند حدث تحديث المنتج |
| نتائج البحث | ممكن | 1-5 دقائق | عند تحديث الفهرس |
| بيانات خاصة بالمستخدم | بحذر | قصير (1-5 دقائق) | عند إجراء المستخدم |
| إعدادات / تكوين | نعم | طويل (ساعة واحدة) | عند تغيير المسؤول |
| تجميعات محسوبة | نعم | 15-60 دقيقة | عند تغيير البيانات الأساسية |
| بيانات فورية (مخزون) | لا | N/A | دائماً اجلب مباشر |
شو ما تخزن مؤقتاً
- بيانات خاصة بالمستخدم مع آثار خصوصية: محتويات سلة مشتريات مخزنة مؤقتاً تُعرض للمستخدم الغلط هي تسريب بيانات.
- بيانات سريعة التغير: مستويات المخزون، أسعار مباشرة، عروض المزادات. TTL التخزين المؤقت ما يقدر يواكب.
- عمليات كثيفة الكتابة: إذا البيانات تتغير أكثر مما تُقرأ، التخزين المؤقت يضيف تعقيد بدون فائدة.
نمط إبطال التخزين المؤقت
// إبطال مبني على الأحداث (موصى به)
eventBus.on('product.updated', async (product) => {
await cache.delete(`product:${product.id}`);
await cache.delete(`product-list:${product.categoryId}`);
// لا تحاول تحدث الكاش هنا. خل القراءة الجاية تملأه.
});
// Read-through مع stale-while-revalidate
async function getProduct(id: string): Promise<Product> {
const cached = await cache.get(`product:${id}`);
if (cached && !isExpired(cached)) {
return cached.data;
}
// قديم لكن قدمه وأنت تحدث في الخلفية
if (cached && isExpired(cached)) {
refreshInBackground(id); // async، لا تنتظره
return cached.data; // قدم القديم
}
// Cache miss
const product = await db.products.findById(id);
await cache.set(`product:${id}`, product, { ttl: 300 });
return product;
}
محادثة التوسع مع أصحاب المصلحة
المهندسون يبون يوسعون لأسباب تقنية. أصحاب المصلحة يبون يوسعون لأسباب تجارية. المحادثة تحتاج لغة مشتركة.
| أصحاب المصلحة يقولون | شو يقصدون | رد الهندسة |
|---|---|---|
| "نحتاج نتعامل مع 10x حركة مرور" | حملة تسويقية جاية، أو تمنيات | "شو الجدول الزمني المتوقع؟ خلنا نعمل load test للسعة الحالية أولاً." |
| "الموقع بطيء" | صفحة واحدة بطيئة، مش الموقع كله | "أي صفحة؟ خلني أعمل لها profile." |
| "نحتاج مايكروسيرفسز" | قرأوا مقال أو سمعوا عنها في مؤتمر | "أي مشكلة نحلها؟ خلني أوريك المعمارية الحالية." |
| "نقدر نتحمل Black Friday؟" | قلق حقيقي من حركة المرور القصوى | "خلنا نعمل load test بـ 3x الذروة الحالية ونشوف وين ينكسر." |
دائماً ابدأ بالقياس. "الموقع بطيء" مش متطلب توسع. "صفحة قائمة المنتجات تاخذ 4 ثواني مع 500 مستخدم متزامن، الهدف 500 مللي ثانية" هو متطلب توسع بهدف قابل للقياس.
أخطاء شائعة
-
مايكروسيرفسز مع 100 مستخدم. إذا المونوليث يتحمل الحمل وفريقك أقل من 10 أشخاص، المايكروسيرفسز تضيف تعقيد تشغيلي بدون فائدة.
-
تخزين مؤقت بدون استراتيجية إبطال. إضافة cache سهلة. الحفاظ على تناسقه مع قاعدة البيانات صعب. خطط للإبطال قبل ما تضيف الـ cache.
-
توسيع التطبيق لما قاعدة البيانات هي الاختناق. إضافة 5 سيرفرات تطبيق ما يساعد إذا كلهم ينتظرون نفس استعلام قاعدة البيانات البطيء.
-
ما فيه load testing. "نظن إننا نحتاج نوسع" مش دليل. اعمل load test بـ 3-5x حركة المرور القصوى الحالية. شوف وين فعلاً ينكسر.
-
التوسع العمودي كتابو. مثيل قاعدة بيانات أكبر يكلف $200/شهر أكثر وياخذ 10 دقائق للتجهيز. هذا تقريباً دائماً أرخص من أسبوع عمل معماري.
-
Sharding قبل ما تجرب كل شي ثاني. Sharding قاعدة البيانات أعقد حل توسع. جرب الفهارس، تحسين الاستعلامات، التخزين المؤقت، Read Replicas، والتوسع العمودي أولاً.
-
البناء لتوسع ما راح توصله. إذا تطبيق SaaS عندك 500 مستخدم وينمو 10% شهرياً، ما تحتاج تتعامل مع مليون مستخدم. تحتاج تتعامل مع 5,000 مستخدم السنة الجاية.
النقاط الرئيسية
-
Monolith + Queue يتعامل مع 90% من أحمال عمل SaaS. كودبيس واحد، قاعدة بيانات واحدة، deployment واحد، و job queue للعمل الخلفي. لا تفصل حتى يكون عندك أدلة إنك تحتاج.
-
قاعدة البيانات عادةً هي الاختناق. أضف فهارس، حسّن الاستعلامات، أضف تخزين مؤقت، ثم فكر في Read Replicas. أغلب مشاكل الأداء تنحل بالخطوات 1-3.
-
افصل الخدمات بسبب حجم الفريق، مش التوسع التقني. المايكروسيرفسز تحل مشكلة "20 مهندس ما يقدرون يشتغلون في كودبيس واحد"، مش مشكلة "نحتاج أداء أكثر".
-
قِس قبل ما توسع. اعمل load test بـ 3-5x الذروة الحالية. اعمل profile للصفحات البطيئة. لاقِ الاختناق الفعلي. ثم حل ذاك بالتحديد.
-
خزّن مؤقتاً القراءات، مش الكتابات. خزّن مؤقتاً البيانات اللي تُقرأ كثير وتتغير نادراً. أبطل عند أحداث تغيير البيانات. قدم بيانات قديمة وأنت تحدث في الخلفية.
-
وسّع بأرخص طريقة أولاً. مثيل قاعدة بيانات أكبر (دقائق، $200/شهر) يتغلب على شهر عمل معماري. التوسع العمودي مش فشل.
نساعد الفرق يوسعون أنظمتهم بشكل مناسب كجزء من ممارسة البرمجيات المخصصة والاستشارات. إذا تحتاج مساعدة بالأداء أو قرارات التوسع، تواصل مع فريقنا أو اطلب عرض سعر.
المواضيع المغطاة
أدلة ذات صلة
الدليل الشامل لأنظمة الذكاء الاصطناعي الوكيلي
دليل تقني لأنظمة الذكاء الاصطناعي الوكيلي في بيئات الأعمال. تعرف على البنية والقدرات والتطبيقات العملية للوكلاء المستقلين.
اقرأ الدليلالتجارة الوكيلية: كيف تخلي وكلاء الذكاء الاصطناعي يشترون بأمان
كيف تصمم تجارة وكيلية محكومة. محركات السياسات، بوابات الموافقة البشرية، إيصالات HMAC، الـ idempotency، عزل المستأجرين، وبروتوكول الدفع الوكيلي الكامل.
اقرأ الدليلالـ 9 أماكن اللي نظام AI تبعك بيسرّب بيانات منها (وكيف تسد كل وحدة)
خارطة منهجية لكل مكان البيانات بتتسرب منه بأنظمة AI. البرومبتات، الـ embeddings، السجلات، استدعاءات الأدوات، ذاكرة الـ agent، رسائل الأخطاء، الكاش، بيانات التدريب، وتسليمات الـ agents.
اقرأ الدليلجاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟
فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.
ابدأ محادثة