المستوى: محترف — هذا المقال مبني على فرضية إنك بتكتب Go production بالفعل، وعارف الفرق بين go func() و channel، وعندك service واحد على الأقل بياخد load حقيقي. لو لسه بتتعلم concurrency من أول، ابدأ بـ Go Tour الرسمي.
لو خدمة log ingestion بتاعتك على 8 cores Hetzner CCX23 بتعالج 380K event/ثانية بس بدل 1.4M المتوقعة، والذاكرة بتنمو 280MB كل ساعة لحد ما الـ OOMKiller يقتلها كل 6 ساعات، المشكلة مش في الـ Go runtime. فيه goroutines بتنفتح وميحصلش يموتوا. المقال ده بيوريك 3 patterns مقاسة على إنتاج بـ 14 مليار event/شهر.
Goroutines Pipelines في Go: من 380K لـ 1.4M Event/ثانية بدون Leaks
المشكلة باختصار
أغلب الفرق بتتعامل مع go func() كأنها مجانية. هي مجانية في الـ allocation (2KB stack بس)، لكنها مش مجانية لو نسيت تقفلها. كل goroutine معلّقة بتحجز ذاكرتها وبتقفل reference على أي channel أو variable في الـ closure بتاعها. مع 380K req/sec، 0.1% leak rate معناه 380 goroutine جديدة بتعلق كل ثانية — يعني 1.4M goroutine بعد ساعة شغل.
المثال البسيط الأول: ساحة المطار
تخيّل ساحة طيران فيها 3 محطات بالترتيب: التذكرة، التفتيش الأمني، البوابة. كل مسافر بيدخل المحطة الأولى ويطلع من الثالثة. لو محطة التفتيش وقفت، المسافرين هيتراكموا قدامها لحد ما الطابور يوصل بره المطار. ودي مشكلتين: المطار بطل يستقبل، والموظفين في المحطة الأولى فضلوا فاضيين بياخدوا مرتب من غير شغل.
الـ Goroutines Pipeline بالظبط نفس الفكرة: كل مرحلة goroutine، كل channel طابور بين مرحلتين. لو مرحلة وقفت، الـ buffered channel بيمتص أول N من المسافرين، وبعدين كل اللي ورا بيتراكم. الفرق إن المطار الذكي بيقول للموظفين "روحوا البيت" لما يبقى الطابور أطول من حد معيّن. ده اسمه context cancellation.
التعريف العلمي: CSP و Goroutines
Go مبني على Communicating Sequential Processes من ورقة Tony Hoare 1978. الفكرة العلمية: بدل ما تشارك ذاكرة بين threads (lock، mutex، race condition)، خلّي العمليات تتواصل برسائل عبر channel. الشعار الرسمي في توثيق Go: "Do not communicate by sharing memory; instead, share memory by communicating".
الـ goroutine مش thread. هي green thread مدارة من Go scheduler نفسه (M:N scheduling). الـ Go 1.23 runtime بيشغّل آلاف الـ goroutines على عدد threads = GOMAXPROCS = عدد الـ CPU cores افتراضياً. كل goroutine بتبدأ بـ 2KB stack بيكبر تلقائياً لحد 1GB لو احتاجت.
Pattern 1: Pipeline مع Context Cancellation
ده الـ pattern الأساسي. كل مرحلة goroutine بتستقبل من channel وبترسل لـ channel ثاني. الـ context.Context بيتمرر لكل المراحل علشان لما الـ HTTP request يتلغي، كل الـ goroutines المرتبطة بيها تموت في نفس الوقت.