Terminal.skills
Use Cases/Debug Serverless Functions and Eliminate Cold Starts with AI

Debug Serverless Functions and Eliminate Cold Starts with AI

Diagnose failing Lambda functions, trace timeout issues, and reduce cold start times from seconds to milliseconds.

DevOps#serverless#lambda#debugging#cold-start#cloudwatch
Works with:claude-codeopenai-codexgemini-clicursor
$

The Problem

Your team runs 35 Lambda functions behind an API Gateway. Three of them randomly timeout under load, but you can't reproduce it locally because local invocation doesn't simulate cold starts. Cold starts add 4-6 seconds to the first request after idle periods, which makes your checkout flow fail because the payment processor's webhook times out at 5 seconds. CloudWatch logs are a wall of unstructured text across multiple log groups, and correlating a single request across three chained Lambdas takes 20 minutes of manual searching.

The checkout failure rate is 2.3% and climbing. Every failed checkout is lost revenue — and the failures are worse than they look, because in some cases the payment actually succeeds but the webhook handler cold-starts past the timeout window. The customer gets charged, sees "payment failed," and now you have both a support ticket and a refund to process.

At $95 average order value and 800 daily checkouts, 2.3% failure rate means 18 failed checkouts per day — $1,710 in daily revenue at risk, plus the support cost of handling each complaint. Over a month, that's $51K in jeopardized revenue from an infrastructure configuration issue.

The Solution

Using the serverless-debugger to trace and diagnose issues across function invocations and the data-analysis skill to crunch invocation metrics, CloudWatch logs become a readable timeline and cold start patterns become visible optimization targets.

Step-by-Step Walkthrough

The approach has two phases: first, trace a specific failure to understand the mechanism; then, analyze patterns across all functions to prioritize which ones to optimize. Fixing the symptom (one failed checkout) is different from fixing the cause (bloated bundles causing slow cold starts).

Step 1: Trace a Failing Request Across Functions

Start with a specific failure. A customer reported their checkout failed at 14:32 UTC — trace the request through every Lambda it touched:

Here are CloudWatch logs from three Lambda functions (auth-handler, order-processor, payment-webhook) for the last 2 hours. A customer reported their checkout failed at 14:32 UTC. Find the request trace, show me where it failed, and why.

The logs get correlated by request ID into a single timeline. What takes 20 minutes of manual searching across three log groups becomes visible in seconds:

Request Trace — correlation ID: req-8f4a2c91

TimeFunctionEventDuration
14:32:01.203auth-handlerToken validated for user_id=884215ms
14:32:01.340order-processorOrder #ORD-29481, $147.50
14:32:01.412order-processorCalling payment-webhook via API Gateway
14:32:01.890payment-webhookCOLD START — Init: 4,847ms
14:32:06.741payment-webhookProcessing payment for ORD-29481
14:32:07.115payment-webhookStripe charge created: ch_3Pq8x2...377ms
14:32:31.413order-processorTIMEOUT after 30,000ms30,000ms

Here's the painful part: the payment actually succeeded. Stripe charged the customer $147.50 at 14:32:07. But the payment-webhook had a 4.8-second cold start, and by the time it responded, the order-processor had been waiting for 30 seconds and timed out. The customer was charged $147.50, saw "payment failed," and now needs a manual refund or a support agent to provision their order.

The root cause isn't a bug in the code. The code works perfectly — when it runs. The problem is infrastructure: Lambda took 4,847ms to initialize the payment-webhook function before the code even started executing. During that 4.8 seconds, the function was loading 47 npm packages, parsing JavaScript, and initializing connections. The actual business logic — validate the webhook, create the Stripe charge, update the database — took only 377ms. The initialization overhead is 13x longer than the work itself.

Step 2: Analyze Cold Start Patterns Across All Functions

One trace shows the problem for one customer. But is this a rare edge case or a systematic issue? The answer determines whether the fix is "increase the timeout" or "fundamentally restructure how the function loads."

Pull the last 7 days of invocation data for all 35 functions. Show me which ones have cold starts, how often, the P50/P95/P99 init durations, and what time of day they're worst. Identify the top 5 functions to optimize.

Seven days of data across 284,391 invocations paints the full picture:

FunctionRuntimeMemoryCold Start RateP50 InitP95 InitP99 Init
payment-webhookNode.js 18512MB3.7% (312/8,401)3,200ms4,900ms6,100ms
report-generatorNode.js 181024MB7.4% (89/1,204)5,400ms8,200ms11,300ms
image-resizerNode.js 18256MB1.3% (201/15,302)1,800ms2,400ms3,100ms
auth-handlerNode.js 18128MB0.8%900ms1,400ms1,800ms
notification-senderPython 3.11256MB1.1%1,200ms1,900ms2,500ms

Cold starts peak between 06:00-08:00 UTC — right after overnight idle — which is exactly when the US East Coast starts their workday and hits the checkout flow. The morning spike in checkout failures maps perfectly to the morning spike in cold starts. This isn't a coincidence — it's the same event. Functions that were warm all afternoon cool down overnight, and the first requests of the business day pay the full cold start penalty.

The payment-webhook's problem is obvious from the data: 47 dependencies in an 18.4MB bundle, including the entire AWS SDK v2 when the function only uses the S3 and DynamoDB clients. Every cold start loads all 47 packages, parses all their JavaScript, and initializes all their module-level code — even for packages the function never calls.

The report-generator is even worse at 7.4% cold start rate (imports puppeteer, which bundles a full headless Chrome binary), but it runs infrequently and isn't in the checkout path. The payment-webhook is the priority because it sits in the critical checkout flow where a cold start means a customer gets charged but never provisioned.

Step 3: Apply Targeted Cold Start Fixes

Focus on the function that matters most — the one in the checkout path:

For the payment-webhook function, give me specific fixes to reduce cold start from 4.8s to under 500ms. Consider: tree-shaking, SDK v3 migration, lazy imports, provisioned concurrency, and memory tuning.

Four optimization layers, each attacking a different part of the init time:

1. Bundle size (biggest impact): Migrate from AWS SDK v2 (monolithic) to SDK v3 (modular). Replace require('aws-sdk') with individual client imports:

javascript
// Before: loads entire SDK (18.4MB)
const AWS = require('aws-sdk');
const s3 = new AWS.S3();

// After: loads only what's needed (1.2MB total)
const { S3Client } = require('@aws-sdk/client-s3');
const s3 = new S3Client({});

This alone drops the bundle from 18.4MB to 1.2MB — a 93% reduction.

2. Memory allocation: Lambda allocates CPU proportional to memory. Bumping from 512MB to 1024MB doubles the CPU available during init. Since cold starts are CPU-bound (parsing and compiling JavaScript), this cuts init time roughly in half. The per-invocation cost increases, but the function runs for less time, so the net cost difference is minimal.

3. Provisioned concurrency: Keep 2 warm instances at all times for the payment-webhook. At $0.015/hour per instance, that's $22/month to guarantee the most critical function in the entire stack never cold-starts. This is insurance, not optimization — the bundle fix handles most cold starts, but provisioned concurrency eliminates the remaining ones. For a function in the checkout critical path where a cold start means a customer gets charged and sees "payment failed," zero cold starts is the only acceptable target.

4. Lazy imports: Move non-critical imports (logging libraries, analytics SDK, error reporting) inside the handler function. They only load when needed, not during the init phase that blocks the first request.

Step 4: Generate the Deployment Configuration

Generate the serverless.yml (or SAM template) changes for: provisioned concurrency on payment-webhook (minimum 2), memory increase to 1024MB for faster init, and the tree-shaken bundle configuration using esbuild.

The configuration codifies all four optimization layers into the deployment template. The esbuild configuration handles tree-shaking automatically — it analyzes which module exports are actually referenced in the code and strips everything else from the bundle. The result is a deployment artifact that includes only the code paths the function actually executes.

The serverless.yml changes include the memory bump (512MB to 1024MB), provisioned concurrency settings (minimum 2 warm instances), and the esbuild plugin configuration (target ES2022, bundle mode, external node_modules that are provided by the Lambda runtime). Ready to commit, review, and deploy through the normal CI/CD pipeline.

The expected results based on similar optimizations:

MetricBeforeAfterImprovement
Init duration4,847ms~340ms93% reduction
Cold start rate3.7%0%Eliminated via provisioned concurrency
Bundle size18.4MB1.2MB93% smaller
Checkout failure rate2.3%~0.1%96% reduction
Monthly cost$0~$22 (provisioned concurrency)Pays for itself with first recovered checkout

Real-World Example

Diana is a DevOps engineer at a 30-person e-commerce startup. Their serverless checkout flow has a 2.3% failure rate that spikes every morning when the US East Coast comes online and hits cold functions that have been idle overnight. The CEO keeps asking why customers see "payment failed" when their cards were actually charged.

She feeds 2 hours of CloudWatch logs to the agent, and the exact failure path traces in 15 seconds — what used to take 20 minutes of manual log correlation across three log groups. The timeline shows the payment-webhook's 4.8-second cold start causing the order-processor to timeout, even though the Stripe charge succeeded.

Cold start analysis across all 35 functions reveals the root cause: the payment-webhook's 18.4MB bundle loads 47 dependencies on every cold start, and 44 of those dependencies are unused AWS SDK modules. The esbuild config tree-shakes the bundle down to 1.2MB. Memory goes from 512MB to 1024MB. Provisioned concurrency of 2 keeps the function warm around the clock.

After deploying: cold starts drop from 4,847ms to 340ms, the morning spike disappears entirely, and checkout failure rate drops from 2.3% to 0.1%. The $22/month for provisioned concurrency pays for itself with the first recovered checkout — a single $147.50 order covers five months of warm-instance costs.

The CEO stops asking about payment failures. The support queue stops filling with "I was charged but didn't get access" tickets. And the DevOps team moves on to optimizing the report-generator (that 11-second P99 cold start is still a problem, but at least it's not in the checkout path).