الـ serverless بيتسوّق غالبًا بوعد بسيط: إدارة أقل للسيرفرات، scaling تلقائي، ودفع على قد الاستخدام. ده صحيح في حالات كتير، لكنه محكوم بحصص وحدود تشغيل، وكل اختيار فيه تمن. فهم القيود دي هو اللي يحدد إذا كان مناسب للـ workload ولا لأ.
الـ serverless معناه إيه فعلًا
الـ serverless مش معناه إن مفيش سيرفرات — معناه إنكم مش اللي بتديروها. الـ function بتاعتكم بتشتغل جوه container، الـ cloud provider بيشغّله عند الطلب، وينفّذ فيه الـ function، وبعدين يشيله. AWS Lambda و Azure Functions و Google Cloud Functions كلهم ماشيين بالنموذج ده. إنتو بتكتبوا الـ function؛ والـ provider بيتولّى الـ provisioning والـ scaling والصيانة.
طريقة التسعير ميزة حقيقية: الدفع بيكون على كل invocation وعلى كل millisecond من وقت التنفيذ، مش على capacity واقفة من غير استخدام. ومع الـ workloads الـ event-driven اللي الضغط فيها بييجي على دفعات، ده بيطلع أرخص بفرق كبير جدًا من إنكم تسيبوا سيرفرات شغالة وجاهزة 24/7.
التحدي الأول: الـ cold starts
لما الـ function متكونش اتنادت من فترة قريبة، الـ provider محتاج يجهّز container جديد قبل ما ينفّذها. الـ latency بتاعة التهيئة دي — اللي هي الـ cold start — ممكن تبقى من 100ms لحد كذا ثانية، على حسب الـ runtime وحجم الـ function.
- Python و Node.js الـ cold starts بتاعتهم أسرع من Java أو .NET، عشان تهيئة الـ runtime فيهم أخف
- الـ deployment packages الكبيرة (dependencies تقيلة) بتخلّي الـ cold starts أسوأ — خلّوا الـ packages بتاعة الـ functions خفيفة
- الـ Lambda functions المربوطة بـ VPC كانت بتزوّد ثواني عشان تجهيز الـ network interface؛ من ساعة ما AWS نقلت لـ Hyperplane interfaces مشتركة في 2019، التأخير ده اختفى إلى حد كبير
# Expensive setup inside the handler runs on every invocation
def handler(event, context):
client = boto3.client("s3") # new client each call
model = load_model("model.bin") # reloaded each call
return predict(model, event)
# Module-level setup runs once per execution environment and is
# reused by every warm invocation that follows
import boto3
from inference import load_model, predict
client = boto3.client("s3")
model = load_model("model.bin")
def handler(event, context):
return predict(model, event)مع الـ endpoints الحساسة للـ latency، Provisioned Concurrency على AWS Lambda بيسيب capacity متهيّأة وجاهزة، فبيقلّل الـ cold starts للطلبات اللي جوه السعة المحجوزة. الطلبات اللي تزيد عنها ممكن تشتغل بسعة عادية وتتعرض لـ cold start، والتمن إنكم بتدفعوا على capacity محجوزة حتى لو مفيش استخدام. استخدموه بانتقائية على الـ hot paths.
التحدي التاني: حدود وقت التنفيذ
أقصى وقت تنفيذ في AWS Lambda هو 15 دقيقة. Azure Functions على الـ Consumption plan الحد الافتراضي فيها 5 دقايق وبتقف عند 10؛ أما الـ Premium والـ Dedicated plans فالحد الافتراضي فيها 30 دقيقة وممكن تشتغل أطول من كده. الحد الصارم ده معناه إن الـ serverless ببساطة مش الأداة المناسبة للعمليات اللي بتاخد وقت طويل — الـ batch jobs، ومعالجة الملفات الكبيرة، والـ ML training.
الحل هو التقسيم: قسّموا المهام الكبيرة لوحدات أصغر، كل وحدة تخلص خلال الوقت المسموح، وبعدين اربطوها ورا بعض بالـ event triggers أو الـ step functions.
# Instead of one large Lambda that times out:
def process_large_file(event, context):
records = load_all_records() # might be 100k records — times out
for record in records:
process(record)
# Use chunked processing with SQS:
def chunk_and_enqueue(event, context):
record_ids = get_all_record_ids()
# Split into batches of 100, enqueue each batch
for batch in chunks(record_ids, size=100):
sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({"ids": batch})
)
def process_batch(event, context):
# Each invocation handles one batch — well within time limit
for record in event["Records"]:
batch = json.loads(record["body"])
for id in batch["ids"]:
process_record(id)التحدي التالت: الـ statelessness
الـ serverless functions متصممة من الأساس إنها تبقى stateless. كل invocation ممكن يشتغل على container instance مختلف. أي state متخزّن في الـ memory بين invocation والتاني مينفعش تعتمدوا عليه — ممكن يكون موجود في الـ call الجاي، وممكن لأ.
الـ statelessness هي اللي بتسهّل الـ horizontal scaling، لكن التوسع نفسه بيقف عند quotas وحدود كل provider. وأي state محتاجين نحتفظ بيها بين invocation والتاني لازم تتحط في storage خارجي، مش في memory الـ function.
- DynamoDB أو RDS للـ persistent data — lookups بـ latency قليلة، وتقدروا تعتمدوا عليها من invocation للتاني
- S3 للتخزين المؤقت للملفات — ابعتوا بين الـ functions مراجع الملفات (الـ keys)، مش محتوى الملفات نفسه
- ElastiCache (Redis) للـ session state المشترك، أو للتنسيق المؤقت بين الـ function instances
إمتى الـ serverless يبقى الاختيار الصح
- المعالجة الـ event-driven: رفع ملفات على S3، ورسايل SQS، والـ webhook receivers — الـ functions بتشتغل لما يحصل event وبعدين بتنتهي
- الـ traffic اللي بييجي على دفعات ومش متوقع: الـ serverless بينزل لصفر في أوقات الهدوء، ويطلع لآلاف الـ instances وقت الـ spikes
- المهام المجدولة: triggers بأسلوب cron للـ jobs الخفيفة اللي بتتكرر بشكل دوري
- الـ API backends اللي الضغط عليها متغير: موفّرة في التكلفة لما الـ traffic ميكونش ماشي على وتيرة ثابتة
إمتى الـ serverless يبقى الاختيار الغلط
- الـ hot paths اللي الـ latency فيها حرجة، والـ cold starts فيها مش مقبولة، وتكلفة الـ provisioned concurrency فيها أعلى من سيرفر بيفضل شغال على طول
- العمليات الطويلة اللي بتعدّي حدود وقت التنفيذ وصعب تتقسم لأجزاء أصغر
- الـ protocols الـ stateful زي WebSockets (استخدموا API Gateway WebSocket APIs بحذر — فيه قيود)
- الـ traffic الكبير والثابت، اللي فيه الـ compute الشغال على طول بيطلع أرخص من الدفع على كل invocation
الـ serverless مناسب لما طبيعة الـ workload ماشية مع قيوده: شغل قصير، event-driven، وحمل متغيّر. لو احتجتم latency ثابتة جدًا، أو تشغيل طويل، أو compute مستمر، قارنوه بخدمة أو container شغال على طول قبل ما تقرروا.