Serverless architecture (Function-as-a-Service) fundamentally shifts backend computing from provisioning infrastructure to executing code on-demand. Instead of paying a flat rate for a virtual server that sits idle at 3 AM, you pay only for the precise milliseconds your code runs. While this eliminates server maintenance, it introduces new architectural complexities like statelessness, vendor lock-in, and the notorious 'Cold Start'.
Module 1: The Anatomy of AWS Lambda
When an HTTP request hits an API Gateway, AWS looks for an idle container holding your code. If one exists, it routes the request (Warm Start, ~20ms latency). If no idle container exists, AWS must allocate a microVM, boot the runtime (Node.js/Python), load your code, and then execute it. This is a Cold Start, and it can take anywhere from 500ms to several seconds.
Module 2: Overcoming Cold Starts
Optimizing serverless performance requires a paradigm shift in how you write code.
Cold Start Optimization Rules
- 1. Shrink the Package Size: Do not upload a 100MB
node_modulesfolder. Use Webpack or esbuild to bundle your Lambda into a single, minified file. - 2. Lazy Loading: Do not
require()heavy modules (like the AWS SDK) at the top of your file unless every single execution path uses them. - 3. Provisioned Concurrency: If you have a highly-trafficked API and cannot tolerate ANY cold starts, you can pay AWS a premium to keep a specific number of instances permanently warm.
let heavyDbDriver = null;
export const handler = async (event) => {
// Route 1: Fast, requires no DB.
if (event.path === '/health') {
return { statusCode: 200, body: 'OK' };
}
// Route 2: Needs DB. We lazily initialize it ONLY when required.
if (!heavyDbDriver) {
heavyDbDriver = await import('massive-db-sdk'); // Dynamic import
await heavyDbDriver.connect();
}
const data = await heavyDbDriver.query();
return { statusCode: 200, body: JSON.stringify(data) };
};Module 3: Event-Driven Architecture
The true power of serverless is not building HTTP APIs; it is building Event-Driven Architectures. AWS services naturally emit events that can trigger Lambdas.
// This Lambda is triggered automatically whenever an image is uploaded to an S3 Bucket
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
const s3 = new S3Client();
export const handler = async (event) => {
// Extract the bucket and filename from the event trigger
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key);
// 1. Download original image
const origImage = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const imageBuffer = await origImage.Body.transformToByteArray();
// 2. Resize via Sharp (Compiled as a Lambda Layer)
const resized = await sharp(imageBuffer).resize(200, 200).jpeg().toBuffer();
// 3. Upload thumbnail back to a different bucket
await s3.send(new PutObjectCommand({
Bucket: bucket + '-thumbnails',
Key: `thumb_${key}`,
Body: resized,
ContentType: 'image/jpeg'
}));
return { status: 'Success' };
};Module 4: Global State & Databases
Lambdas are entirely stateless. When the execution finishes, everything in memory is destroyed. You cannot rely on maintaining an open WebSocket connection or storing session data in memory.
Furthermore, Lambdas scale out massively and instantly. If you experience a traffic spike, AWS might spawn 1,000 parallel Lambdas. If they all immediately attempt to open a connection to your traditional PostgreSQL database, they will exhaust the database connection pool, crashing your DB instantly.