لو عندك API على Lambda وأول مستخدم بعد 15 دقيقة سكون بياخد 3 ثواني لحد ما يرد، المشكلة مش في شبكتك ولا في الكود. دي Cold Start، وبتتحل من غير ما تحوّل الـ stack كله لـ containers.
Cold Start في AWS Lambda: ليه بيحصل وإزاي تقيسه وتحسّنه بالأرقام
المشكلة باختصار
الـ Cold Start هو التأخير اللي بيحصل لمّا Lambda محتاجة تجهّز execution environment جديد علشان تشغّل دالتك. التأخير ده مش ثابت. ممكن يكون 200ms في دالة Node.js صغيرة، وممكن يوصل 6 ثواني في Java فيها SDKs كتير. اللي بيحصل فعلاً إن المستخدم الأول بعد فترة خمول بيدفع ثمن التجهيز ده لوحده، وبيشوف تأخير غير متوقع.
مثال للمبتدئين: محل الشاورما اللي بيفتح لكل زبون
تخيّل محل شاورما مش فاتح طول الوقت. كل ما يجي زبون، صاحب المحل لازم يشغّل الفرن، يجهّز اللحمة، ويولّع الغاز. الزبون الأول بياخد 5 دقايق استنّى، واللي بعده بدقيقة بس لأن المحل لسه مفتوح. لو فضل ساعة من غير زباين، المحل بيقفل تاني، وأول زبون جديد هيدفع ثمن التجهيز.
ده بالظبط اللي بيحصل في Lambda. AWS مش شغّالة container بتاعك طول الوقت. بتفتحه لمّا بيجي request، وبتقفله بعد فترة خمول. التجهيز ده = Cold Start. بعد ما يخلّص، أي request تاني خلال فترة قصيرة بيروح على نفس الـ container ساخن = Warm Start.
التعريف الدقيق: إيه اللي بيحصل جوه Lambda
لمّا بتيجي invocation ومفيش execution environment جاهز، Lambda بتعمل 3 خطوات متتالية:
- Download: بتحمّل الـ deployment package (ZIP أو container image) من S3 أو ECR.
- Init: بتشغّل الـ runtime (Node.js/Python/Java…) وبعدها بتنفّذ الكود اللي برّه الـ handler — يعني الـ imports والـ global initializations زي فتح اتصال بقاعدة البيانات.
- Invoke: بتنفّذ الـ handler نفسه على الـ event اللي جاي.
الخطوتين الأولانيتين هما الـ Cold Start. الـ invocations اللي بعدها بتستخدم نفس الـ environment = Warm Start، وده بيتكلّف ميلي ثواني بس. Lambda بتحتفظ بالـ environment ساخن لفترة بتختلف حسب الـ load، غالبًا بين 5 و 45 دقيقة.
القياس قبل التخمين
بدل ما تعدّل بشكل عشوائي، اقيس الأول. CloudWatch Logs بيحط في كل Cold Start سطر اسمه REPORT، وفيه حقل Init Duration. ده الوقت اللي الـ container أخده في الخطوتين الأولانيتين بالظبط.
# جيب آخر Cold Starts من دالة معيّنة
aws logs filter-log-events \
--log-group-name /aws/lambda/my-api \
--filter-pattern '{ $.initDuration > 0 }' \
--max-items 100 \
--query 'events[].message'الطريقة الأوضح في CloudWatch Insights:
filter @type = "REPORT"
| stats avg(@initDuration), pct(@initDuration, 95) by bin(5m)لو الـ P95 Init Duration فوق 1000ms، عندك مشكلة تستاهل تحسين. لو تحت 200ms، التحسين هنا مش أولوية — روح حسّن حاجة تانية.
الخطوة 1: حجم الذاكرة (أرخص وأسرع تحسين)
Lambda بتربط الـ CPU بحجم الذاكرة ربط خطّي. دالة 128MB بتاخد CPU أقل بكثير من دالة 1024MB. كل ما زوّدت الذاكرة، الـ Init بيقل — لحد حد معيّن وبعد كده التحسّن بيبقى هامشي.
مثال من قياس حقيقي على دالة Node.js بتعمل import لـ AWS SDK v3 + Prisma:
- 128MB: Init Duration ~ 1800ms
- 512MB: Init Duration ~ 620ms (تحسّن 65%)
- 1024MB: Init Duration ~ 480ms (تحسّن 73%)
- 1769MB: Init Duration ~ 460ms (تحسّن 74%، هامشي بعد الـ 1024)
الـ trade-off: سعر التنفيذ بيزيد خطّيًا مع الذاكرة. بس لو الدالة بتاخد 500ms بدل 1800ms، فعليًا هتدفع أقل على الـ GB-seconds لأن الوقت قل. زوّد الذاكرة، قِس، وقارن الفاتورة.
الخطوة 2: ARM64 (Graviton) بدل x86_64
تغيير الـ architecture من x86_64 إلى arm64 بيحسّن الـ Cold Start بنسبة 15–24% تقريبًا على Node.js وPython، ومعاه سعر التنفيذ أرخص 20%. التغيير حرف واحد في الـ Lambda config:
# serverless.yml
functions:
api:
handler: src/handler.main
memorySize: 1024
architecture: arm64
runtime: nodejs20.xالافتراض هنا إن كل الـ dependencies عندك فيها arm64 binaries. sharp و bcrypt و node-canvas محتاجين native binary متوافق مع arm64. يعني لازم تبني على نفس الـ architecture، أو تستخدم Lambda Layers فيها prebuilt artifacts.
الخطوة 3: SnapStart (لو بتستخدم Java أو Python أو .NET)
SnapStart هي ميزة AWS بتاخد snapshot من الـ Firecracker microVM بعد الـ Init وبتخزّنه مشفّر. لمّا Cold Start بيحصل، Lambda بتستعيد الـ snapshot بدل ما تعيد الـ Init من الصفر. النتيجة: تجاوز الجزء الأغلى من الـ Cold Start.
الأرقام اللي AWS بتعلنها في 2026: تقليل P99 Cold Start بنسبة 94% على Java، ووصول لـ sub-second على Python 3.12+ و .NET 8. الميزة بقت مجانية على Java من نوفمبر 2024.
# serverless.yml - Java مثلًا
functions:
api:
handler: com.example.Handler
runtime: java21
snapStart:
applyOn: PublishedVersionsمهم تعرف: SnapStart بيشتغل بس على published versions ومش بيشتغل على $LATEST. ولو الكود بتاعك بيعمل non-deterministic stuff وقت الـ Init (زي توليد UUID global مرة واحدة وتخزينه، أو فتح connection بـ random state)، هتحتاج تستخدم Runtime Hooks علشان تعيد توليد الحاجات دي بعد الاستعادة — وإلا هيبقى عندك bugs غريبة في الـ production.
الخطوة 4: تقليل حجم الحزمة والاعتماديات
أكبر مصدر خفي للـ Cold Start في Node.js هو الاستيراد الضخم. AWS SDK v3 مصمم modular، يعني بدل ما تعمل:
// بطيء - بيحمّل SDK v2 كامل تقريبًا 70MB
const AWS = require('aws-sdk');
const s3 = new AWS.S3();استخدم SDK v3 بواحد client بس:
// سريع - بيحمّل client واحد بس
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({});الفرق المقاس: Init Duration ممكن يقل من 900ms إلى 250ms على نفس الدالة. كذلك في Python، الـ import الكسلان بيوفر وقت فعلي:
# بطيء - بيحمّل كل الـ libraries في الـ Init
import pandas as pd
import numpy as np
def handler(event, context):
return process(event)
# أسرع - بيحمّل بس لما تحتاجه فعلاً
def handler(event, context):
import pandas as pd
import numpy as np
return process(event)الـ trade-off: الـ lazy import بيضيف تأخير صغير على أول invocation warm، مقابل Cold Start أسرع بكثير. لو دالتك بتتندهّ كتير وبتستخدم الـ library في 100% من الـ requests، ابقى خلّيه في الأول. لو بتستخدمه في 20% بس من المسارات، lazy import يربحك.
الخطوة 5: Provisioned Concurrency (الحل النووي)
لو المستخدم بتاعك مش مستعد يستحمل Cold Start نهائي — مثلاً API بيخدم تطبيق موبايل تجاري فيه SLA — Provisioned Concurrency بتحجز عدد معيّن من execution environments جاهزة ومسخّنة طول الوقت.
functions:
api:
handler: src/handler.main
memorySize: 1024
provisionedConcurrency: 5التكلفة هنا حقيقية. 5 environments على 1024MB في منطقة us-east-1 تقريبًا $50 في الشهر، حتى لو الـ API ما تستخدمش. لو بتفكّر تدفع ده، اعمل الخطوات من 1 لحد 4 الأول. غالبًا مش هتحتاج Provisioned Concurrency بعد ما تعدّل الذاكرة والـ bundle والـ architecture.
trade-offs ملخّصة
- زيادة الذاكرة: أسرع Init، ممكن سعر أقل فعليًا. تحقّق من الفاتورة.
- ARM64: أسرع وأرخص. محتاج build متوافق.
- SnapStart: تخفيض كبير للـ Cold Start، لكن محتاج عناية مع non-deterministic code.
- Bundle أصغر: أسرع Init دايمًا. lazy imports trade-off بسيط.
- Provisioned Concurrency: حل مضمون، تكلفة ثابتة حتى بدون استخدام.
متى لا تستخدم هذه التحسينات
لو الدالة Cron job بتشتغل مرة كل ساعة وبتستحمل ثانية زيادة، ما تضيعش وقت على SnapStart أو Provisioned Concurrency. Cold Start هنا مش user-facing. لو الدالة بتتنده من SQS بـ batching 10، الـ Cold Start بيتوزّع على 10 رسائل، والتأثير غير ملحوظ في الأداء الإجمالي.
الافتراض في كل الشرح ده إن P95 latency مهم لتجربة المستخدم عندك. لو مش مهم، التحسين ده مش هيعمل قيمة حقيقية — ركّز على cost أو reliability بدل منه.
الخطوة التالية
افتح CloudWatch Insights على دالة Lambda واحدة مهمة عندك، شغّل الاستعلام اللي فوق، واعرف P95 Init Duration بتاعك. لو فوق 800ms، غيّر الذاكرة لـ 1024MB، حوّل لـ arm64، وأعد القياس بعد 24 ساعة. النتيجة غالبًا هتفرق 50% على الأقل من غير ما تلمس سطر كود واحد. بعدها لو لسه عندك مشكلة، فكّر في SnapStart أو Provisioned Concurrency حسب الـ runtime واحتياجك.
المصادر
- AWS Lambda SnapStart — Official Documentation
- AWS Compute Blog — Understanding and Remediating Cold Starts
- AWS — Optimizing cold start with SnapStart priming strategies
- maxday — Lambda Cold Starts Benchmark (updated daily)
- Mikhail Shilkov — Cold Starts in AWS Lambda
- Lumigo — AWS Lambda Performance Optimization Guide