شهد التطور الكبير في مجال الانترنت والاجهزة الذكية واللوحية طفرة كبيرة خلال السنين العشر الاخيرة , ادى ذلك لدخول الكثير من المستفيدين لهذه الخدمات لعالم الانترنت ومحاولة نقل اعمالهم الي المنصات , ولم يكتفو فقط بنقل الاعمال بل حتى المتعة entertainment والتواصل الاجتماعي social media platforms واصبحت حياة معظم الناس تعتمد بشكل اساسي على هذه الخدمات .
الإقبال الكبير على هذه الخدمات وضع مطوريها في تحدي دائم نحو “قابلية التوسع” اي كيفية جعل النظام او المنصة قادرة على إستيعاب أكبر كمية من المستخدمين دون التأثير على سرعة الاستجابة latency وتوقف الخدمات down time , availability وتماسك هذه الخدمات والحفاظ على بيانات المستخدم بشكل دائم durability
لكن يظل السؤال المركزي في قابلية التوسع , كيف نقوم بذلك.؟ هل هنالك شكل محدد ام قواعد وتوجيهات للقيام بذلك ؟ , كيف نوفق بين الكتب النظرية والمشاكل العملية التي تواجه المطورين في جعل انظمتهم قابلة للتوسع ؟ في هذا المقال سنشرح بعض النصائح التي لو اتبعتها اختصرت جهدآ وسنوات كثيرة في التعلم من الأخطاء والمشاكل التي تصاحب هذه العملية .
1- المقارنة بين نظامك وبين الانظمة الاخرى غالبا غير مجدية :
بعض المطورين يسئ التقدير والمقارنة بين الخدمات المختلفة , مثلا قياس تطبيق يقوم بالإستجابة لـ 10 الف طلب في الثانية ولكن هذه الطلبات مجرد صفحة تحتوي على HTML ولا يوجد بها اي عمليات في ال backend ولا استعلامات من قواعد البيانات querying from databases او البحث في ملايين الملفات النصية full text search , مثل هذه المقارنات لا تعطي اي تصور ولا فائدة وقد تزيد التشويش على المطور ,
ايضا من اشكال هذه المقارنات , المقارنات التي يقوم بها مطوري او مروجي خدمة معينة او بروتوكل او لغة برمجة معينة او حتى إطار عمل مثل عمل اكثر من 2000 اتصال على بروتكول X بنفس الوقتsimultaneously connections (قد تكون الظروف مختلفة , البيانات المرسلة صغيرة لا تتعدى 10 بايت مثلا , العتاد عالي جدآ اثناء الاختبار ) , او حتى اختبار إطار عمل معين يقوم بارجاع نتيجة خلال اقل من ثانية (وفي الغالب النتيجة عبارة عن نص ولا يوجد اي معالجة للطلب من قواعد بيانات او تنفيذ خوارزميات تستهلك وقت وموارد) , مثل هذه المقارنات تعطي التصور الخاطئ والمحصلة النهائية لها انك تنتج نظامآ غير قابلآ للتوسع لانك تعتمد على امور اكاديمية علمية غير دقيقة , كانت على اساس بنية معينة specific environment او على هوى الذي قام بها لكي يروج لمنتج او اداة معينة.
2- قابيلة التوسع تبدأ من كتابة الكود الجيد :
كثير من المبرمجين يهمل تفاصيل الاكواد , والكثير منهم يقوم بنسخ الأكواد فقط من مواقع مثل stack overflow وغيرها , مثل هذه الممارسات تضر بالكود والنظام عند المحاولة لجعله قابلآ للتوسع , فمثلا كتابة اسطر معينة بلغة java( او نسخها من مكان ما) قد يكلفك سرفر بإمكانيات قيمتها 100 دولار , وان قمت بتحسين الكود بما يتناسب مع المشكلة فقد تقلل هذه القيمة الي 25 دولار فقط , ايضا الوقت الطويل الذي ياخذه الكود السئ (وان كان سطرآ واحدآ) يؤثر حتمآ على سرعة الاستجابة في الخدمة
دائما إهتم بالتفاصيل الصغيرة , قم بكتابة الكود بافضل طريقة بحيث يكون سريعآ في التنفيذ , سريع في الاستجابة, بدون مشاكل وأخطاء , ونظيفآ وسهل القراءة
3- الطفل الذي يولد قبل أوانه في الغالب يموت :
كثير من المطورين يريد كتابة انظمة واكواد قابلة للتوسع من اليوم الاول day one , وهذا المفهوم خاطئ ويجعل المبرمج يركز مع التفاصيل التقنية واهمال المتطلبات الفعلية من البزنس business requirements وفي الغالب لا يستطيع تسليم المشروع في الوقت المحدد deadline ولا الحصول على قابيلة التوسع المناسبة.
حاول ان تبدأ بكود بسيط واهتم بالتحسينات الاولية premature optimizations وقم مع الوقت بعمل ال refactoring اللازم واضافة الميزات التي تجعل النظام قابلآ للتوسع.
4- وهم التعقيد في البنية المعمارية :
كثير من المطورين يتبنو عبارة complexity implies sophistication and simplicity implies primitiveness ( أن التعقيد يعني ان النظام قوي وقابل للتوسع , وان البساطة تعني البدائية) وهذا المفهوم خاطئ تمامآ ف كثير من المشاكل قد تحل بابسط الطرق دون اللجوء للتعقيد وكتابة مئات الاسطر البرمجية , ان هذا الوهم الذي يصاحب المطورين يجعل من المطور المبالغة في كتابة الكود وصياغة المعمارية overengineering .
إبدأ بسيطآ وحسن مع الوقت , اذا كان الكود البسيط يحل المشكلة فلما التعقيد والتغير ؟
5- عنق الزجاجة دائمآ في قواعد البيانات :
في معظم الانظمة القابلة للتوسع تمثل قواعد البيانات “عنق الزجاجة” bottleneck للخدمة ومنعها من ان تكون قابلة للتوسع بشكل سريع وجيد , معظم اطر العمل تستطيع ان تستقبل اكثر من مليوني طلب في الثانية اذا كانت ترجع او تعالج نصآ دون الرجوع لقواعد البيانات, ما ان ظهرت قواعد البيانات وتزيد كمية البيانات فيها عندها لن تستطيع من تلبية 5 الف طلب بشكل سريع , لذلك لا بد من الاهتمام بمجال ال data system ومعرفة انواع قواعد البيانات (base , acids) , معرفة طرق التحسين مثل ال indexing , sharding , partioning , التعرف على انواع قواعد البينات القابلة للكتابة بسرعة مثل nosql databases , wide column databases , التعرف على عالم ال streaming processing وادواته مثل apache Kafka
التعمق في علم انظمة البيانات ومعرفة الفروق بينها ودراستها بعمق يسهل من تخطي عقبة عنق الزجاجة في قواعد البيانات.
6- قواعد البيانات العلائقية جيدة :
عند التفكير في الانظمة القابلة للتوسع تجد كثير من المطورين ينظر لقواعد البينات العلائقية RDBMS على انها اداة لا تنفع في مثل هذه الانظمة ويجب استبدالها فورآ بال no sql database او اطر العمل للبيانات الضخمة مثل apache spark , Hadoop وهذا المفهوم خاطئ تمامآ وغير صحيح وهو مجرد حالة شعورية صنعتها بعض المقالات التي تروج لل no sql databases على انها الرصاصة الفضية silver bullet والحل لكل مشاكل البيانات الكبيرة .
قواعد البينات العلائقية RDBMS جيدة وتحل اغلب المشاكل , مثلا في قواعد البيانات PostgreSQL تستطيع تخزين ملاين السجلات ومئات التيرا بايتس من البينات اذا قمنا بتنفيذ بعض التقنيات الذكية مثل ال indexing , partioing , replicating nodes
في غالب الاحيان تستطيع تخزين بياناتك في قواعد بيانات علائقية والاستفادة من خصايص ال acids بدلا من اعادة اختراعها داخل الكود.
7- الكل ينسى فهرسة قواعد البيانات :
الجزء الاكبر من المطورين دائما ما ينسى تفعيل الفهرسة , فهرسة قواعد البينات database indexing هي عملية مشابهة تمامآ للفهرسة اليدوية في الكتب الكبيرة او حتى في المصحف الشريف , عملية الفهرسة تغير مجرى البرنامج الكلي وتؤثر بشكل فعال في سرعة الاستجابة للنتائج فبدلا من البحث في كل الجداول full tables scanning يتم البحث في جزئية صغيرة مما يعني توفير العتاد , الوقت
حاول دائمآ فهرسة الحقول التي تبحث بها كثيرآ لتحسين عملية البحث وسرعة الإستجابة .
8- قلل الاتصال بين الخدمات عن طريق الشبكات IO/ networking :
يلجئ الكثير من المبرمجين اثناء بناء معمارية “قابلية التوسع” إلي فصل الخدمات (حتى وان لم يكن مؤثرآ او ضروريآ) عن بعضها البعض ومن ثم وضع وسيلة تواصل بينها (RPC , HTTP , Websocket) , مما يضيف الكثير من التعقيد لان التواصل ما بين الخدمات عن طريق ال network يعني الدخول في تفاصيل مثل , ال bandwidth , timeouts , load balancing , routing , congestion ويعني ذلك ضمنيآ زيادة المشاكل والاخطاء ووقوف الخدمات servers downs.
لا تقم بفصل الخدمات ان لم يكن ذلك ضروريآ وضروريآ جدآ , حاول جعل الخدمات قريبة من بعضها monolithic way , سريعة التواصل بشكل مباشر دون وجود طبقة network حتى تحتاج لذلك لاحقآ
9- ثبات سرعة الإستجابة هي المقايس :
قياس سرعة استجابة الخدمات latency metric هو بالتأكيد اهم عناصر التأكد من ان النظام او الخدمة قابلة للتوسع فنظام ينفذ الطلب في ثانية واحدة والنسخة من هذه الخدمة تستطيع تنفيذ 1000 طلب في الثانية الواحدة هذا يعني عمليا اذا اردنا ان نستوعب اكثر من 200000 طلب ان نقوم بإنشاء 20 نسخة من هذه الخدمة 20 instance of service ولكن هل يكفي القياس وحده؟
بالتأكيد لا ! ثبات سرعة استجابة الخدمات latency consistency هو المعيار الذي يخطئ كثير من المطورين في حسابه ولا يلقي له بالآ ويتم تعريف ذلك بان سرعة استجابة الخدمة ثابتة لا تتاثر مع الوقت او الظروف ويعتبر هذا التحدي الاصعب في تصميم معماريات الانظمة القابلة للتوسع , ونجد ان كثيرآ من خدمات الكلاود cloud services providers في تروجيهم لخدمات يكتبون عبارات مثلا we guarantee single digit response time وفيما معناه (اننا نضمن رد واستجابة الخدمة فيما لا يزيد عن خانة واحدة من الارقام اي من 1 الي 9 جزء من الثانية مثلا ولا يمكن ان تصبح 10 او 11 او اي رقم مكون من خانتين).
قياس سرعة استجابة الخدمات جيد , لكن لضمان المحافظة على سرعة الاستجابة لا بد من التأكد من ثبات هذه القياسات على المدى الطويل latency consistency.
10- الـ caching هو الرصاصة الفضية :
في كثير من الأحيان تعد الاستعلامات من قواعد البيانات مكلفة جدآ very expensive & resource intensive , وايضا عمليات الحسابات المعقدة computinonal algorithms والقراءة من الشبكات والعتاد io& networking request كل هذه الاشياء تستهلك وقتآ طويلآ في المعالجة وبالتالي تأخير سرعة الإستجابة.
هنا يأتي دور ال caching في توفير الوقت والجهد المبذول واستهلاك العتاد.
يعتبر ال caching هو رصاصة الفضية في معظم الاحيان , قم بعمل caching للاستعلامات المعقدة والتي تاخذ وقت طويل , للبيانات التي تقرأها كثيرآ من ال hardDisk , قم بعمل cache لكل شئ يستدعيه المستخدم بشكل متكرر وسريع .
11- قلل الضغط على السرفرات وال IO:
يلعب القرص الصلب hardDisk دورآ اساسيآ في سرعة الاستجابة حيث يتم منه قراءة معظم البيانات (مثلا قواعد البيانات تنفذ الاستعلامات على القرص الصلب) , تقليل الضغط على القرص الصلب يتم بإسناد مهام قراءة الملفات (ايآ كان نوعها css , js , png , vidoes , ziped files , documents ) الي خدمات خارجية او سرفرات مخصصة , حاول ان تفصل الملفات عن سرفرات الخدمات العادية التي تستعلم من قواعد البيانات او تقوم بتنفيذ خوازرميات رياضية لتوفير جهد القرص الصلب .
يمكن استخدام خدمات جاهزة من مقدمي الخدمات السحابية cloud providers مثل AWS S3 من امزون , و GCP Buckets من قوقل , ويمكنك ايضآ تحميل ادوات مفتوحة المصدر على السرفر الخاص بك مثلا blobTFS و azureMOck
كفاءة القرص الصلب وعدم الضغط عليه مهم في معماريات الانظمة القابلة للتوسع , حاول بقدر الإمكان تخفيف الضغط على القرص الصلب بإبعاد قراءة وكتابة الملفات من سرفرات الخدمات المستخدمة لديك
12- التحويل لصيغة ال JSON مكلف جدآ :
كثير من المطورين لا يعلم هذه الحقيقة ان ال json serialization مكلف جدآ لل cpu وال hardDesk reading & writing وكلما زاد حجم البينات المحولة payload كلما زاد الاستهلاك والكلفة واصبحت العملية مكلفة اكثر وقد تصل في كثير من الأحيان ان تتحول هذه المشكلة إلي bottleneck في النظام خصوصا في اللغات التي تستخدم ال garbage collection لتنظيف الذاكرة بعد الاستخدام , وقد يؤدي ذلك لتوقف الخدمة نفسها service down.
حاول بقدر الإمكان تقليل عمليات التحويل الي json وحاول تقليل حجم البيانات المحولة json payloads
13- Allocation is expensive :
هذا العنوان الوحيد الذي كتبته باللغة الانجليزية لصعوبة ترجمته , ال allocation تعني عملية تفريغ الذاكرة ونظافتها من المتغيرات والبيانات الموجودة بها وهي ميزة في كثير من لغات البرمجة المعتمدة على JVM مثل java , csharp.
الحقيقة أن هذه العملية مكلفة جدآ وتاخذ وقتآ كبيرآ وجهد ومصادر خصوصا حينما نتكلم في خدمات تأتيها ملاين الطلبات اليومية.
لغات ال jvm جيدة لكن حاول ان تستخدم لغات اخرى لا تعتمد على ال memory allocating في الخدمات التي عليها ضغط عالي وطلبات كثيرة استخدم لغات اخرى تعطي تحكما اكبر مثل c++ , go , خصوصا ان كنت تعمل على معمارية microservices يمكنك كتابة ال services التي تتطلب سرعة استجابة عالية وفيها كثير من عمل ال memory بلغات اخرى غير لغات ال JVM
14- ال threading & concurrency مهمة ولكن صعبة :
قدرة تحمل السرفرات على اكبر كمية من الطلبات ومعالجتها بشكل سريع يعتمد على thread modeling المستخدم فتارة نجد ان ال parallelism جيد (حيث يقوم بتنفيذ اكثر من مهمة في نفس الوقت بعدة cpu cors) وتارة نجد ان ال async programing افضل (حيث يرسل الطلب الي الجهة المراد سحب البيانات منها سواء شبكة الانترنت او قاعدة البيانات , وبعدها يتحرر ال thread ليخدم طلبآ اخر وعند اكتمال النتيجة يعود ليرسلها للمستخدم) , وتارة نجد ال sync programing هي الافضل (حيث يقوم بوضع المهام في صف انتظار queue وينتظر حتى اكتمال المهمة ثم ينفذ التي تليها) .
ان اختيار ال thread modeling المناسب للعميلة المطلوب او الخدمة هو تحدي كبير ويحتاج الاجابة على اسئلة كثيرة مثل استهلاك الموارد , سرعة الرد , عدم تعارض البيانات او تداخلها ومشاكل ال race conditions & dead lock.
15-بعض التقنيات ضعيفة بطبيعتها واصلها :
بعض التقنيات ولغات البرمجة واطر العمل هي بطيئة بطبيعة تصميمها genuinely slow , مثلا في python لا تستطيع ان تبني web socket يستقبل ملاين الطلبات وقابل للتوسع بشكل كبير لان التصميم الغة نفسه لا يسمح بذلك , كذلك نجد ان ruby بطيئة بعض الشئ حينما نتحدث عن الاف وملاين الطلبات في جزء من الثانية , فمهما تقوم بتحسينات مستمرة وتحسن من جودة الكود يزال تصميم اللغة عائق اساسي امام الكود ليتوسع
حاول ان تختبر اللغة , اطار العمل بشكل جيد قبل البدء وتاكد من انه يلبي احتياجاتك المستقبلية بشكل جيد , اتذكر في مرة ان احد الفرق في شركة linked in تحدث عن بناء خدمة بلغة ruby وبعد الجهد الطويل من كل التحسينات وال best practices لم يستطيعو جعل الخدمة تقبل اكثر من عدد معين الطلبات , فقامو بإعادة كتابة الخدمة بلغة Go فاعطتهم تحكم اكبر في ال memory , threads فانتجت خدمة سريعة قابلة للتوسع لمليارات الطلبات .
16- توسع عموديآ بقدر الإمكان :
كثير من مطوري الخدمات القابلة للتوسع يفكر ابتداء بالتوسع بشكل عمودي horizontally وهذا جيد لكن لو استخدمنا مفهوم ال trade – offs ف يمكن ان نتوسع افقيا vertically (ما دامت الخدمة جيدة وسريعة ولا يوجد مشاكل) وندفع المزيد من المال (لانك غالبا سترفع امكانيات العتاد) مقابل التخلص من التعقيد في التوسع العمودي (لانك تحتاج ان تجعل الكود وقواعد البيانات تقبل هذا النوع من التوسع وهذا يضيف طبقة تعقيد كبيرة جدآ )
دائما ابدأ التفكير بالتوسع الافقي vertically scaling ولا بأس بدفع المزيد من المال مقابل التخلص من التعقيد المصاحب لل horizontally scaling
17- العائق البشري :
يوجد مقالة قديمة راسخة ابان تطوير الذكاء الصناعي تقول if human found , problems comes (اي اذا وجد الانسان داخل عملية معينة , هذا يعني بالتأكيد وجود المشكلة) , يعتبر البشر احد العوائق عند بناء الأنظمة القابلة للتوسع فتجد ان الشركة لتوفير المال توظف مطورين بأجر بسيط وبخبرة ضعيفة تتسبب في بناء خدمات تسقط من اول 1000 طلب لها , وكذلك العكس قد يوجد خبراء ولكن يحل المشكلة بشكل اكثر كلفة ووقتآ (قد يكون حل المشكلة ملف sqlite او حتى ملف نصي text file ولكن المطور استخدم خدمات من aws مثلا full text search و redis و nosql database) قام بوضع تكلفة وقت ومال لمشكلة قد تحل فقط بملف نصي او حتى قاعدة بيانات علائقية عادية مفتوحة المصدر وبسيطة.
ان اختيار المبرمج الخبير والحكيم بنفس الوقت يوفر عليك الكثير من المال والوقت , وان الاعتماد على مبرمج سئ بخبرة ضعيفة قد يؤدي لفشل الخدمة ويصبح هو ال bottleneck للمعمارية.
بهذا نكون قد سردنا اغلب النقاط العملية بعيدآ عن الكتب النظرية والمقالات التي تتكلم بشكل نظري بحت , تعلمنا فيها بعض الممارسات الجيدة والنقاط التي قد تغيب عنا جميعآ اثناء بناء هذا النوع من الانظمة.
جزاك الله خييير الاخ معاذ
كفيت ووفيت
مقال رائع جدا يختصر سنين للعديد من المطورين