العودة إلى المدونة

لماذا يجب عليك الانتقال من PHP إلى Go

تحجز PHP عملية عامل (worker) كاملة لكل طلب، بينما تستطيع عملية Go واحدة الاحتفاظ بآلاف الاتصالات في الوقت نفسه. هذا الفرق في طريقة تعامل كل منهما مع التزامن هو السبب الحقيقي وراء انتقال الفرق، ويمكنك نقل خدمة واحدة في كل مرة بدلًا من إعادة كتابة كل شيء.

لماذا يجب عليك الانتقال من PHP إلى Go

عندما تتعثر PHP مع ازدياد الحمل، نادرًا ما تكون سرعة اللغة نفسها هي السبب. المشكلة في طريقة تعاملها مع الطلبات المتزامنة. في PHP-FPM يحجز كل طلب عملية عامل (worker process) كاملة طوال مدته. وأي طلب يقضي وقته في الانتظار، سواء على استعلام بطيء أو استدعاء لخدمة خارجية أو اتصال يبقى مفتوحًا، يشغل عاملًا كان بإمكانه خدمة مستخدم آخر. الميزات الفورية مثل لوحات المعلومات الحية والاستجابات المتدفقة (streaming) وواجهات API ذات التفرع الواسع (high fan-out) تصل إلى هذا الحد بسرعة. وإضافة المزيد من العمال أو الخوادم تؤخر الوصول إلى هذا السقف، لكنه يبقى موجودًا.

أما نموذج التزامن في Go فليس فيه هذا السقف، وهذا هو السبب الرئيسي لانتقال الفرق من PHP إلى Go. يتناول هذا المقال لماذا يهم هذا النموذج، وما يقدمه للنشر والتكلفة، وما ذكرته الفرق بعد الانتقال، وكيف تنتقل خدمةً تلو الأخرى بدلًا من المراهنة بالمنتج كله على إعادة كتابته.

الخلاصة

  • انتقل من أجل نموذج التزامن. تصميم PHP القائم على عملية لكل طلب يحجز عاملًا كاملًا طوال عمر كل طلب. أما الـ goroutines في Go فتتيح لعملية واحدة إدارة آلاف الاتصالات المتزامنة. وتتسع الفجوة كلما أضفت ميزات فورية وميزات تعتمد على كثرة الاتصالات.
  • الفرق في استهلاك الذاكرة كبير. يستهلك عامل PHP-FPM ما بين 30 و60 MB تقريبًا بعد تحميل إطار العمل. أما الـ goroutine فتبدأ بنحو 8 KB. خمسون اتصال بث متزامنًا قد تستنفد مجموعة FPM، في حين لا تكاد عملية Go واحدة تشعر بها.
  • الفرق التي انتقلت تذكر مكاسب كبيرة. يصف مهندسون من Cloudflare ومن شركة التقنية المالية Curve خدمةً انتقلت من نحو 10 طلبات في الثانية إلى آلاف الطلبات بعد نقلها إلى Go، وأصبح المهندسون الجدد منتجين خلال أسبوع إلى أسبوعين (Go Time #316).
  • النشر أبسط. يُترجم كود Go إلى ملف تنفيذي ثابت واحد (static binary). لا تحتاج إلى تثبيت بيئة تشغيل أو تشغيل Composer أو ضبط FPM، وهذا يناسب الخدمات المصغرة (microservices) و Kubernetes جيدًا.
  • لست مضطرًا إلى إعادة كتابة كل شيء. استخدم نمط الخنق التدريجي (strangler pattern). انقل الخدمة التي تسبب لك المشكلة، وشغّلها بجانب PHP، ثم توسّع بعد أن تثبت جدواها.

جدول المحتويات

السبب الحقيقي للانتقال هو التزامن

تعمل PHP التقليدية وفق نموذج share-nothing، أي عملية مستقلة لكل طلب دون أي حالة مشتركة. يصل الطلب، فيسلّمه PHP-FPM إلى عامل، ويشغّل العامل الكود من حالة نظيفة حتى الاستجابة، ثم يعيد ضبط نفسه. التصميم بسيط ومعزول جيدًا. وما يكلفك إياه هو الذاكرة.

يحتفظ عامل PHP-FPM الواحد عادةً بما بين 30 و60 MB من الذاكرة المقيمة (resident memory) بعد تحميل إطار العمل. وخدمة 50 طلبًا في الوقت نفسه تعني 50 عاملًا، أي ما يقارب 1.5 إلى 3 GB من الذاكرة تذهب في معظمها إلى الانتظار.

والآن اجعل هذه الطلبات الخمسين تدفقات Server-Sent Events أو اتصالات WebSocket يبقى كل منها مفتوحًا لدقائق. خمسون اتصالًا طويل الأمد قد تستنفد مجموعة FPM بأكملها، فينتظر المستخدم رقم 51 دوره. وحل هذه المشكلة بإضافة المزيد من العتاد يصبح مكلفًا بسرعة، لأن كل اتصال متزامن يكلف عملية كاملة.

أما Go فتعمل بالطريقة المعاكسة. تبدأ الـ goroutine، وهي وحدة التزامن في Go، بمكدس (stack) حجمه نحو 8 KB، وتوزّع بيئة التشغيل آلافًا منها على مجموعة صغيرة من خيوط نظام التشغيل (OS threads). عندما تتوقف goroutine بانتظار عمليات الإدخال والإخراج (I/O)، يركنها المجدول (scheduler) جانبًا ويشغّل غيرها على الخيط نفسه. يمكن لعملية Go واحدة أن تحتفظ بعشرات الآلاف من الاتصالات المفتوحة مع استهلاك جزء صغير من الذاكرة التي تحتاجها مجموعة خوادم FPM مكافئة. ومعالج الاتصال (handler) ليس سوى دالة عادية:

example.gogo
func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
	// Each connection is one cheap goroutine. Blocking here parks
	// this goroutine and frees the thread for others, no pool to exhaust.
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "streaming unsupported", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/event-stream")

	for event := range s.events(r.Context()) {
		fmt.Fprintf(w, "data: %s\n\n", event)
		flusher.Flush()
	}
}

يشغّل خادم net/http كل طلب في goroutine خاصة به تلقائيًا، لذلك يحتفظ هذا الكود باتصال بث حي دون حجز عامل ثقيل. إذا كان منتجك يتجه نحو الميزات الفورية أو بوابات API ذات التفرع الواسع أو الخدمات التي تحتفظ بعدد كبير من الاتصالات المتزامنة، فإن هذا الفرق يحسم الكثير. نموذج PHP يعمل ضد هذه الأحمال، بينما يتعامل معها نموذج Go دون جهد خاص. وإذا كانت الـ goroutines والـ channels جديدة عليك، فإن دورة Go Concurrency Fundamentals تشرح هذا النموذج بشكل عملي.

ما تكسبه الفرق بعد الانتقال

أفضل رواية مباشرة عن انتقال حقيقي من PHP إلى Go هي الحلقة 316 من بودكاست Go Time. تحدث فيها مهندسون من Cloudflare ومن شركة التقنية المالية Curve عن نقل أنظمة PHP تعمل في بيئة الإنتاج إلى Go (Go Time #316، Changelog). هؤلاء ممارسون يصفون أنظمتهم بأنفسهم، وهذا يجعل روايتهم أنفع من دراسة حالة من مورّد أو مقال مدونة يخمّن الأرقام من الخارج.

تشير الرواية إلى أن إحدى الخدمات انتقلت من معالجة نحو 10 طلبات في الثانية في الإعداد القديم إلى آلاف الطلبات بعد الانتقال. ولخّصوا بيئة التشغيل بعبارة «حتى كود Go الرديء يتمتع بأداء مذهل»، أي أنك تحصل على إنتاجية (throughput) كبيرة قبل أن تحتاج إلى أي تحسين. وأصبح المهندسون الجدد منتجين خلال أسبوع إلى أسبوعين بدلًا من أشهر، ويرجع ذلك إلى صغر Go من حيث عدد المفاهيم والميزات.

تعود جذور خبرة Curve في Go إلى Monzo، وهي شركة تقنية مالية أخرى اعتمدت على Go بشكل كبير، وهذا جزء من طريقة انتشار هذا النمط في ذلك القطاع.

وهناك نقاط بيانات أخرى عن استخدام Go في بيئة الإنتاج أيضًا. بنت HelloFresh بوابة API الخاصة بها، Janus، بلغة Go ونشرتها مفتوحة المصدر (Janus على GitHub). والبوابة طبقة توجيه ذات تفرع واسع، وهذا بالضبط نوع العمل الذي تفيد فيه الـ goroutines أكثر من غيره. وستجد أيضًا كثيرًا من المهندسين الأفراد يذكرون أن مهمة دفعية (batch job) واحدة أُعيدت كتابتها من PHP إلى Go تعمل أسرع بكثير وبجزء صغير من الموارد.

Go مقابل PHP: المكاسب التقنية

إليك مقارنة بين اللغتين في الجوانب التي تهم خدمة الواجهة الخلفية.

الجانبPHPGo
التزامنعملية لكل طلب، مع Fibers التي تضيف التزامن لكن لا تضيف التوازيgoroutines على مجدول M:N، مع توازٍ حقيقي عبر الأنوية
الأنواعديناميكية، مع تلميحات أنواع تدريجيةثابتة، ويُتحقق منها وقت الترجمة
النشرالكود + اعتماديات Composer + FPM + خادم ويبملف تنفيذي ثابت واحد، دون بيئة تشغيل تحتاج إلى تثبيت
التنفيذمفسَّرة، بمساعدة JITتُترجم إلى كود آلة أصلي

التزامن هو الأهم. أضافت PHP 8.1 ميزة Fibers، وهي تحسين حقيقي، لكن من المفيد أن تكون دقيقًا بشأن ما تفعله. تمنحك Fibers تزامنًا تعاونيًا (cooperative concurrency) لتنظيم عمليات I/O غير المتزامنة. فهي تسمح لعامل واحد بالتنقل بين مهام تنتظر. لكنها لا تسمح لعملية PHP واحدة بتشغيل 16 نواة بكامل طاقتها في عمل حسابي كما يفعل مجدول Go.

توزّع Go عددًا كبيرًا من الـ goroutines على عدد كبير من خيوط نظام التشغيل وتشغّلها بالتوازي، وتركنها جانبًا بتكلفة زهيدة عندما تتوقف بانتظار شيء ما. الأداة الأساسية نفسها تمنحك التزامن الرخيص والتوازي الحقيقي. ومجموعة العمال (worker pool) مثال جيد. ستلجأ إليها باستمرار في Go، ولا يوجد لها في PHP التقليدية مكافئ نظيف ضمن عملية واحدة:

example.gogo
func process(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
	defer wg.Done()
	for job := range jobs {
		results <- job.Run() // each worker pulls the next job when free
	}
}

func main() {
	jobs := make(chan Job, 100)
	results := make(chan Result, 100)
	var wg sync.WaitGroup

	// Fan out to 8 workers sharing one queue, all in one process.
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go process(jobs, results, &wg)
	}

	go func() {
		for _, j := range loadJobs() { // pull the batch from your work source
			jobs <- j
		}
		close(jobs)
	}()

	go func() { wg.Wait(); close(results) }()
	for r := range results {
		record(r)
	}
}

أما النشر فلا يحظى بالاهتمام الذي يستحقه. ينتج بناء Go ملفًا تنفيذيًا ثابتًا واحدًا، ويمكن أن يكون شحنه بسيطًا مثل نسخ هذا الملف إلى خادم أو إلى حاوية scratch. لا توجد بيئة تشغيل PHP تحتاج إلى تثبيت، ولا خطوة Composer، ولا ضبط لـ FPM، ولا إعدادات خادم ويب أمامه. وإذا كنت تشغّل خدمات مصغرة في Kubernetes، فستحصل على هذا الملف الصغير المكتفي بذاته في كل عملية نشر.

وتساعد الأنواع الثابتة (static typing) أيضًا. يكتشف المترجم فئة من الأخطاء وقت البناء، بينما لا تظهرها PHP إلا وقت التشغيل. وتزداد أهمية ذلك كلما كبرت الخدمة وزاد عدد من يعملون عليها.

ثم هناك اعتراض «PHP الحديثة أصبحت سريعة الآن». صحيح أن PHP قلّصت جزءًا كبيرًا من الفجوة في السرعة الخام، بفضل JIT في PHP 8 وبيئات تشغيل العمال مثل FrankenPHP وRoadRunner التي تبقي تطبيقك محمّلًا في الذاكرة بين الطلبات. لكن FrankenPHP و RoadRunner مكتوبتان كلتاهما بلغة Go. فعندما احتاج نظام PHP البيئي إلى خوادم تطبيقات عالية التزامن، بناها على بيئة تشغيل Go. إذا كان التزامن هو مشكلتك، يمكنك وضع بيئة تشغيل مبنية على Go أمام PHP، أو كتابة الخدمة كثيرة الاتصالات بلغة Go مباشرة والاستغناء عن الطبقة الوسيطة.

حقيقة اختبارات الأداء (والمكسب الحقيقي)

ستصادف أرقامًا لافتة تزعم أن Go أسرع من PHP بما بين 10 و30 مرة. تأتي هذه الأرقام من مجموعات اختبار اصطناعية مثل اختبارات TechEmpower، التي تختبر بيئات التشغيل في ظروف مثالية يحكمها المعالج وإطار العمل (TechEmpower Framework Benchmarks). هذه الأرقام حقيقية، لكنها لا تتنبأ بزمن الاستجابة في بيئة الإنتاج لديك، فلا تنتقل وأنت تنتظر هذا المضاعف.

في بيئة الإنتاج، يذهب معظم وقت الطلب إلى قاعدة البيانات والشبكة، وتنتظرهما Go و PHP بالطريقة نفسها تمامًا. لذلك يتقلص المضاعف بسرعة. والفرق التي تقيس النتائج بأمانة بعد الانتقال كثيرًا ما تذكر تحسنًا في متوسط زمن الاستجابة من طرف إلى طرف يتراوح بين بضعة في المئة وما يزيد على 10% بقليل.

لكن هذا المتوسط يخفي المواضع التي تتفوق فيها Go، وهي استقرار زمن الاستجابة في الأطراف (tail latency) وتكلفة الطلب الواحد. تبقى قيم p99 و p99.9 في Go مستقرة تحت الحمل، لأنه لا يوجد طلب مضطر إلى انتظار عامل متاح في مجموعة FPM. كما أنك تخدم حركة المرور نفسها بعدد أقل بكثير من الخوادم، لأن عملية واحدة تستوعب من التزامن ما لا تستوعبه مجموعة عمال. هذه هي المكاسب التي تلاحظها في بيئة الإنتاج وفي الفاتورة، ويبقى أثرها قائمًا بعد أن ينسى الجميع أي اختبار أداء اصطناعي بوقت طويل.

توظيف أسرع وإطلاق أسرع

التوظيف واندماج المطورين الجدد يرجّحان Go أيضًا. فهي لغة صغيرة لا تضم سوى 25 كلمة مفتاحية وأداة تنسيق واحدة إلزامية، ولذلك يصبح المهندس الكفء القادم من PHP أو من أي لغة أخرى منتجًا خلال أسبوع أو اثنين. وهذا يتوافق مع مدة الاندماج التي وصفها مهندسو Cloudflare و Curve.

لذلك لا تحتاج إلى توظيف من يعرفون Go مسبقًا. فالناس يتعلمونها بسرعة، والتنسيق الإلزامي والأنواع الثابتة يبقيان كودهم متسقًا منذ اليوم الأول.

وتميل Go أيضًا إلى الارتباط بعلاوة في الرواتب في استطلاعات السوق، وهذا يشير إلى أن الطلب يفوق العرض. الأرقام الدقيقة المنقولة عن مصادر مثل Glassdoor تختلف حسب المنطقة والدور الوظيفي، لذلك تعامل معها كعامل مساعد في التوظيف، لا كبند في دراسة جدوى. ونظام Go البيئي في أقوى حالاته في الشبكات والبنية التحتية وأدوات سطر الأوامر (CLI) والخدمات عالية الإنتاجية، وهذا هو العمل الذي ستنقله. صُممت الـ goroutines لهذا العمل، والمكتبات والأدوات المحيطة به ناضجة.

كيف تنتقل دون أن تراهن بالشركة

لا تُعد كتابة كل شيء دفعة واحدة. إعادة الكتابة الكاملة من أضمن الطرق لتعطيل تقدم المنتج، لأن الكود العامل يحمل سنوات من إصلاحات الأخطاء والحالات الحدية التي ستتخلص منها (Joel on Software). ولم يقم فريقا Cloudflare و Curve بإعادة كتابة شاملة دفعة واحدة أيضًا. بل استخدما نمط strangler، وهو نمط يستحق أن تحذو حذوه.

ابدأ بالخدمة الوحيدة التي تسبب مشكلة فعلًا: تدفق SSE الذي يستنفد مجموعة العمال، أو واجهة API ذات التفرع الواسع التي تشغل عمالك، أو البوابة التي لا تستطيع الاحتفاظ بعدد كافٍ من الاتصالات. أعد بناء هذه الخدمة وحدها بلغة Go، وضعها خلف طبقة التوجيه نفسها، وشغّلها بجانب PHP.

وجّه جزءًا من حركة المرور إليها وراقب الذاكرة وزمن الاستجابة في الأطراف قبل أن توجّه المزيد. تواصل PHP خدمة كل ما عداها طوال الوقت، فلا يوجد يوم تحوّل واحد (flag day) ولا يتوقف شيء على لحظة انتقال واحدة. وعندما تثبت الخدمة الأولى جدواها، وهذا ما يحدث عادةً، تصبح لديك أرقام حقيقية وقالب للخدمة التالية.

وهذا يجعل الانتقال قرارًا يُتخذ لكل خدمة على حدة. لست مضطرًا إلى أن تصبح «شركة Go». أبقِ PHP حيث تناسب، مثل نظام إدارة المحتوى (CMS) أو صفحات المحتوى أو لوحة إدارة Laravel التي يطوّر فيها فريقك بسرعة، وانقل الخدمات كثيرة الاتصالات وعالية الإنتاجية إلى Go.

الأسئلة الشائعة

هل Go أسرع من PHP؟

في اختبارات الأداء الاصطناعية التي يحكمها المعالج، نعم، وغالبًا بفارق كبير (TechEmpower). أما في بيئة الإنتاج فتكون الفجوة في متوسط زمن الاستجابة أصغر عادةً، لأن معظم وقت الطلب يذهب إلى قاعدة البيانات والشبكة، وتنتظرهما اللغتان بالطريقة نفسها تمامًا. وتحافظ Go على تفوقها في التزامن وزمن الاستجابة في الأطراف تحت الحمل الثقيل. فهي تحتفظ بآلاف الاتصالات في عملية واحدة وتبقي زمن p99 مستقرًا في الحالات التي تنفد فيها عمال مجموعة FPM، لذلك تحتاج إلى عدد أقل من الخوادم لحركة المرور نفسها.

هل يجب أن أعيد كتابة تطبيق PHP بالكامل بلغة Go؟

لا. انتقل تدريجيًا. إعادة الكتابة الكاملة من أضمن الطرق لتعطيل تقدم المنتج (Joel on Software). استخدم نمط strangler: انقل الخدمة الوحيدة المرهقة عالية التزامن إلى Go، وشغّلها بجانب PHP، وحوّل حركة المرور إليها تدريجيًا، ثم توسّع بعد أن تثبت جدواها. بهذا تحصل على الفوائد دون يوم تحوّل محفوف بالمخاطر.

ما أنواع الخدمات التي يجب أن أنقلها إلى Go أولًا؟

ابدأ بالخدمات كثيرة الاتصالات وذات التفرع الواسع: نقاط نهاية WebSocket و SSE، والميزات الفورية، وبوابات API، والاستجابات المتدفقة، وأي خدمة تبقى فيها طلبات كثيرة مفتوحة بانتظار عمليات I/O. في هذه الخدمات يضيق نموذج PHP القائم على عملية لكل طلب أولًا، وتفيد الـ goroutines فورًا، لذلك تمنحك أسرع دليل على أن الانتقال يؤتي ثماره.

هل يمكن لـ PHP التعامل مع WebSockets والميزات الفورية؟

نعم، بمساعدة إضافية. تحجز PHP-FPM التقليدية عاملًا لكل اتصال، لذلك تستنفد الاتصالات الكثيرة طويلة الأمد مجموعة العمال. تبقي بيئات تشغيل مثل Swoole و FrankenPHP العمال قيد التشغيل وتضيف تزامنًا قائمًا على الأحداث (event-driven) لجعل ذلك يعمل. أما Go فتتعامل مع عبء العمل نفسه بشكل أصلي، بمعدل goroutine رخيصة واحدة لكل اتصال. ولهذا تُعد الخدمات كثيرة الاتصالات أكثر أسباب انتقال الفرق شيوعًا.

هل تعلم Go أصعب من تعلم PHP؟

ليس من ناحية الصياغة. لا تضم Go سوى 25 كلمة مفتاحية وأسلوب تنسيق واحد إلزامي، ويصبح معظم المهندسين منتجين خلال أسبوع إلى أسبوعين. الجديد بالنسبة لمطور PHP هو نموذج التزامن (الـ goroutines والـ channels) والتعامل مع الأخطاء كقيم معادة بدلًا من الاستثناءات. يحتاج ذلك إلى بعض التدريب، وهذه أيضًا هي المهارات التي ستستخدمها أكثر من غيرها في الخدمات التي تنقلها.

هل أنت مستعد لكتابة Go لبيئة الإنتاج؟

أفضل طريقة لتقييم Go هي أن تكتب بها بعض الكود. في LevelUpGo، كل درس هو تمرين عملي تحله في المتصفح. يعمل الكود الذي تكتبه على سلسلة أدوات Go حقيقية (toolchain)، ويُتحقق منه باختبارات التمرين، لذلك تحصل على ملاحظات فورية على كود Go يعمل فعلًا بدلًا من القراءة عنه.

مسار مقترح لمطوري PHP:

  • Go Fundamentals تغطي الأنواع والـ structs ومعالجة الأخطاء، وهي الأجزاء التي تبدو مختلفة عند القدوم من PHP. البدء مجاني ولا يتطلب بطاقة ائتمان.
  • Composite Types تغطي الـ slices والـ maps والـ structs، وهي اللبنات الأساسية اليومية للبيانات التي تمر عبر الخدمة.
  • Go Concurrency Fundamentals تعلّمك الـ goroutines والـ channels و select، وهي الميزات التي تتيح لعملية Go واحدة أن تفعل ما لا تستطيعه مجموعة PHP-FPM كاملة.
  • خارطة الطريق ترسم المسار من برنامجك الأول حتى مشروع ختامي (capstone) قائم على التزامن.

لمزيد من المقارنات، اقرأ الانتقال من Python إلى Go وGo مقابل Rust في 2026 وGo مقابل Node.js ومسألة أمان سلسلة التوريد.

المصادر

المصادر المذكورة في هذا المقال، مع وضع روايات الممارسين الأولية في مقدمة القائمة:

اكتب Go كما يكتبها مهندس أول

دروس تفاعلية في متصفحك. الدروس الأولى مجانية.

جرّب درسًا مجانيًاأو أنشئ حسابًا مجانيًا