1. Pengenalan AWS Lambda & Serverless
AWS Lambda adalah layanan compute serverless dari Amazon Web Services yang memungkinkan Anda menjalankan kode tanpa perlu mengelola server. Anda cukup mengupload kode, mengkonfigurasi trigger, dan Lambda akan menangani sisanya — provisioning, scaling, patching, dan operasional server.
Konsep serverless bukan berarti tidak ada server — server tetap ada, tetapi Anda tidak perlu mengelolanya. AWS menangani seluruh infrastruktur, dan Anda hanya membayar untuk waktu eksekusi yang benar-benar digunakan (per milidetik).
Serverless vs Traditional Architecture
| Aspek | Traditional (EC2) | Serverless (Lambda) |
|---|---|---|
| Server Management | 🔴 Anda kelola OS, patch, scaling | 🟢 AWS kelola semuanya |
| Scaling | 🟡 Manual atau Auto Scaling Group | 🟢 Otomatis hingga ribuan concurrent |
| Billing | 🔴 Per jam (meski idle) | 🟢 Per milidetik eksekusi |
| Min Cost | 🔴 ~$5/bulan (t3.micro) | 🟢 $0 (free tier: 1M requests/bulan) |
| Cold Start | 🟢 Selalu running | 🟡 Ada cold start delay |
| Execution Limit | 🟢 Tidak terbatas | 🔴 Max 15 menit per invocation |
| State | 🟢 Stateful | 🔴 Stateless (perlu external storage) |
| Use Case | Long-running, stateful apps | Event-driven, API, microservices |
┌──────────────────────────────────────────────────────────────────┐ │ AWS SERVERLESS ARCHITECTURE │ │ │ │ EVENT SOURCES LAMBDA DESTINATIONS │ │ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ API │─────────►│ │──────►│ DynamoDB │ │ │ │ Gateway │ │ │ └──────────────┘ │ │ └──────────┘ │ │ ┌──────────────┐ │ │ ┌──────────┐ │ Lambda │──────►│ S3 Bucket │ │ │ │ S3 │─────────►│ Function │ └──────────────┘ │ │ │ Event │ │ │ ┌──────────────┐ │ │ └──────────┘ │ ┌────────┐ │──────►│ SQS / SNS │ │ │ ┌──────────┐ │ │ Your │ │ └──────────────┘ │ │ │ CloudWatch─────────►│ │ Code │ │ ┌──────────────┐ │ │ │ Schedule │ │ └────────┘ │──────►│ External API │ │ │ └──────────┘ │ │ └──────────────┘ │ │ ┌──────────┐ │ ┌────────┐ │ │ │ │ DynamoDB │─────────►│ │ Layers │ │ │ │ │ Stream │ │ └────────┘ │ │ │ └──────────┘ └──────────────┘ │ │ ┌──────────┐ │ │ │ SQS/SNS │─────────► Auto-scales dari 0 hingga ribuan │ │ └──────────┘ instances dalam hitungan detik │ └──────────────────────────────────────────────────────────────────┘
Bahasa Pemrograman yang Didukung
| Bahasa | Runtime | Cocok Untuk |
|---|---|---|
| Node.js | 18, 20, 22 | API, webhooks, data processing |
| Python | 3.10, 3.11, 3.12 | ML inference, data analysis, automation |
| Java | 17, 21 | Enterprise apps, heavy processing |
| Go | Provided.al2023 | High-performance, CLI tools |
| .NET (C#) | 6, 8 | Enterprise .NET apps |
| Ruby | 3.3 | Web apps, scripting |
| Custom Runtime | provided.al2023 | Bahasa apapun (Rust, PHP, dll) |
2. Function Pertama
Hello World dengan Node.js
// handler.js — Lambda function Node.js
// BeebaneLabs - AWS Lambda Tutorial
exports.handler = async (event, context) => {
console.log('Event:', JSON.stringify(event, null, 2));
// Parse request body (untuk API Gateway)
let body;
if (event.body) {
body = JSON.parse(event.body);
}
// Business logic
const response = {
message: 'Halo dari AWS Lambda!',
timestamp: new Date().toISOString(),
requestId: context.awsRequestId,
input: event
};
// Return response (format API Gateway)
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(response)
};
};
Hello World dengan Python
# lambda_function.py — Lambda function Python
# BeebaneLabs - AWS Lambda Tutorial
import json
import os
from datetime import datetime
def lambda_handler(event, context):
"""Handler utama Lambda function"""
print(f"Event: {json.dumps(event)}")
# Parse request body (untuk API Gateway)
body = {}
if event.get('body'):
body = json.loads(event['body'])
# Business logic
response_data = {
'message': 'Halo dari AWS Lambda!',
'timestamp': datetime.utcnow().isoformat(),
'requestId': context.aws_request_id,
'functionName': context.function_name,
'memoryLimit': context.memory_limit_in_mb,
'remainingTime': context.get_remaining_time_in_millis(),
'environment': os.environ.get('ENVIRONMENT', 'development')
}
# Return response (format API Gateway)
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps(response_data)
}
Deploy dengan AWS CLI
# ===========================
# DEPLOY LAMBDA FUNCTION
# ===========================
# 1. Buat deployment package (Node.js)
zip function.zip handler.js
# 2. Buat IAM role untuk Lambda
aws iam create-role \
--role-name lambda-execution-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# 3. Attach basic execution policy
aws iam attach-role-policy \
--role-name lambda-execution-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# 4. Create Lambda function
aws lambda create-function \
--function-name my-hello-function \
--runtime nodejs20.x \
--role arn:aws:iam::ACCOUNT_ID:role/lambda-execution-role \
--handler handler.handler \
--zip-file fileb://function.zip \
--timeout 30 \
--memory-size 128 \
--environment Variables={ENVIRONMENT=production}
# 5. Invoke function
aws lambda invoke \
--function-name my-hello-function \
--payload '{"name": "BeebaneLabs"}' \
--cli-binary-format raw-in-base64-out \
response.json
# 6. Cek hasil
cat response.json
# 7. Update function code
zip function.zip handler.js
aws lambda update-function-code \
--function-name my-hello-function \
--zip-file fileb://function.zip
# 8. Update function configuration
aws lambda update-function-configuration \
--function-name my-hello-function \
--timeout 60 \
--memory-size 256
Deploy dengan AWS SAM
# template.yaml — AWS SAM (Serverless Application Model)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: BeebaneLabs Lambda Tutorial
Globals:
Function:
Timeout: 30
MemorySize: 128
Runtime: nodejs20.x
Environment:
Variables:
ENVIRONMENT: production
LOG_LEVEL: info
Resources:
# Lambda Function
HelloFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: my-hello-function
Handler: handler.handler
CodeUri: ./src/
Description: Hello World Lambda
# API Gateway trigger
Events:
ApiEvent:
Type: Api
Properties:
Path: /hello
Method: ANY
RestApiId: !Ref ApiGateway
# Schedule trigger (setiap 5 menit)
ScheduledEvent:
Type: Schedule
Properties:
Schedule: rate(5 minutes)
Enabled: true
# API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization'"
AllowOrigin: "'*'"
# DynamoDB Table
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: users
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
Outputs:
ApiUrl:
Description: "API Gateway endpoint URL"
Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/hello"
FunctionArn:
Description: "Lambda Function ARN"
Value: !GetAtt HelloFunction.Arn
# Build dan deploy dengan SAM sam build sam deploy --guided # Pertama kali (interactive) sam deploy # Deploy berikutnya # Invoke function locally sam local invoke HelloFunction --event events/api-event.json # Start local API Gateway sam local start-api # View logs sam logs -n HelloFunction --tail
3. Event Triggers
Salah satu kekuatan terbesar Lambda adalah kemampuannya merespons berbagai event dari layanan AWS lainnya. Setiap event memiliki format (schema) yang berbeda.
Common Lambda Triggers
| Trigger | Event Source | Contoh Penggunaan |
|---|---|---|
| API Gateway | HTTP Request | REST API, Webhook |
| S3 | Object Created/Deleted | Image resize, file processing |
| DynamoDB Stream | Database changes | Real-time sync, audit log |
| SQS | Message queue | Async processing, batch jobs |
| SNS | Pub/Sub notification | Email, push notification |
| CloudWatch Events | Scheduled (cron/rate) | Cleanup, report generation |
| Kinesis | Data stream | Real-time analytics, log processing |
| Cognito | User pool trigger | Custom auth, user migration |
S3 Event Trigger
// Lambda: Auto-resize image saat upload ke S3
const AWS = require('aws-sdk');
const sharp = require('sharp'); // Image processing library
const s3 = new AWS.S3();
exports.handler = async (event) => {
console.log('S3 Event:', JSON.stringify(event, null, 2));
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
const size = record.s3.object.size;
console.log(`Processing: s3://${bucket}/${key} (${size} bytes)`);
// Hanya proses file gambar
if (!key.match(/\.(jpg|jpeg|png|webp)$/i)) {
console.log('Skipping non-image file');
continue;
}
// Download original image
const original = await s3.getObject({
Bucket: bucket,
Key: key
}).promise();
// Resize ke beberapa ukuran
const sizes = [
{ suffix: 'thumb', width: 150, height: 150 },
{ suffix: 'medium', width: 800, height: 600 },
{ suffix: 'large', width: 1920, height: 1080 }
];
for (const size of sizes) {
const resized = await sharp(original.Body)
.resize(size.width, size.height, { fit: 'inside' })
.jpeg({ quality: 80 })
.toBuffer();
const resizedKey = key.replace(/(\.\w+)$/, `_${size.suffix}$1`);
await s3.putObject({
Bucket: bucket,
Key: `resized/${resizedKey}`,
Body: resized,
ContentType: 'image/jpeg'
}).promise();
console.log(`Created: resized/${resizedKey}`);
}
}
return { statusCode: 200, body: 'Images processed successfully' };
};
Scheduled (Cron) Trigger
// Lambda: Cleanup otomatis setiap hari jam 2 pagi
// Schedule: cron(0 2 * * ? *)
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
console.log('Starting scheduled cleanup...');
// Hapus records yang lebih tua dari 30 hari
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const params = {
TableName: process.env.TABLE_NAME || 'sessions',
FilterExpression: 'createdAt < :cutoff',
ExpressionAttributeValues: {
':cutoff': thirtyDaysAgo.toISOString()
}
};
let totalDeleted = 0;
// Scan and delete (untuk tabel kecil)
// Untuk tabel besar, gunakan query dengan pagination
const result = await dynamodb.scan(params).promise();
for (const item of result.Items) {
await dynamodb.delete({
TableName: params.TableName,
Key: { id: item.id }
}).promise();
totalDeleted++;
}
console.log(`Cleanup complete. Deleted ${totalDeleted} records.`);
return {
deletedCount: totalDeleted,
cutoffDate: thirtyDaysAgo.toISOString()
};
};
SQS Queue Trigger
// Lambda: Process messages dari SQS queue
// SQS trigger akan mengirim batch messages
exports.handler = async (event) => {
console.log(`Processing ${event.Records.length} messages`);
const results = {
success: 0,
failed: 0,
errors: []
};
for (const record of event.Records) {
try {
const message = JSON.parse(record.body);
console.log(`Processing message: ${record.messageId}`);
// Process each message
await processMessage(message);
results.success++;
} catch (error) {
console.error(`Failed to process ${record.messageId}:`, error);
results.failed++;
results.errors.push({
messageId: record.messageId,
error: error.message
});
// JANGAN throw error agar batch bisa lanjut
// SQS akan retry message yang gagal
}
}
console.log('Results:', JSON.stringify(results));
// Jika ada yang gagal, return partial failure
// SQS akan retry message yang gagal saja
if (results.failed > 0) {
return {
batchItemFailures: results.errors.map(e => ({
itemIdentifier: e.messageId
}))
};
}
return { statusCode: 200 };
};
async function processMessage(message) {
// Business logic here
console.log('Processing:', message);
}
4. API Gateway Integration
API Gateway + Lambda adalah kombinasi populer untuk membuat REST API tanpa server. API Gateway menangani HTTP routing, authentication, rate limiting, dan caching, sementara Lambda menangani business logic.
┌─────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────┐
│ Client │───►│ API Gateway │───►│ Lambda │───►│ DynamoDB │
│ (Browser │ │ │ │ Function │ │ S3, RDS │
│ /App) │◄───│ • Authentication│◄───│ │◄───│ etc. │
└─────────┘ │ • Rate Limiting │ │ • Business │ └──────────┘
│ • Validation │ │ Logic │
│ • CORS │ │ • Data │
│ • Caching │ │ Processing │
└─────────────────┘ └──────────────┘
REST API Handler
// Lambda: Full REST API handler dengan routing
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const TABLE_NAME = process.env.TABLE_NAME || 'users';
exports.handler = async (event) => {
console.log('Event:', JSON.stringify(event, null, 2));
const { httpMethod, path, pathParameters, queryStringParameters, body } = event;
const userId = pathParameters?.userId;
try {
// Simple router
switch (httpMethod) {
case 'GET':
if (userId) {
return await getUser(userId);
}
return await listUsers(queryStringParameters);
case 'POST':
return await createUser(JSON.parse(body));
case 'PUT':
return await updateUser(userId, JSON.parse(body));
case 'DELETE':
return await deleteUser(userId);
default:
return response(405, { error: 'Method not allowed' });
}
} catch (error) {
console.error('Error:', error);
return response(500, { error: 'Internal server error' });
}
};
// Helper: Format response
function response(statusCode, body) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
},
body: JSON.stringify(body)
};
}
// CRUD Operations
async function getUser(userId) {
const result = await dynamodb.get({
TableName: TABLE_NAME,
Key: { userId }
}).promise();
if (!result.Item) {
return response(404, { error: 'User not found' });
}
return response(200, result.Item);
}
async function listUsers(queryParams) {
const limit = parseInt(queryParams?.limit) || 20;
const params = {
TableName: TABLE_NAME,
Limit: limit
};
// Pagination
if (queryParams?.lastKey) {
params.ExclusiveStartKey = JSON.parse(
Buffer.from(queryParams.lastKey, 'base64').toString()
);
}
const result = await dynamodb.scan(params).promise();
return response(200, {
items: result.Items,
count: result.Count,
lastKey: result.LastEvaluatedKey
? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString('base64')
: null
});
}
async function createUser(data) {
const user = {
userId: data.userId || AWS.util.uuid.v4(),
name: data.name,
email: data.email,
createdAt: new Date().toISOString()
};
await dynamodb.put({
TableName: TABLE_NAME,
Item: user,
ConditionExpression: 'attribute_not_exists(userId)'
}).promise();
return response(201, user);
}
async function updateUser(userId, data) {
const result = await dynamodb.update({
TableName: TABLE_NAME,
Key: { userId },
UpdateExpression: 'SET #name = :name, email = :email, updatedAt = :now',
ExpressionAttributeNames: { '#name': 'name' },
ExpressionAttributeValues: {
':name': data.name,
':email': data.email,
':now': new Date().toISOString()
},
ReturnValues: 'ALL_NEW'
}).promise();
return response(200, result.Attributes);
}
async function deleteUser(userId) {
await dynamodb.delete({
TableName: TABLE_NAME,
Key: { userId }
}).promise();
return response(204, null);
}
5. Lambda Layers
Lambda Layers memungkinkan Anda memisahkan kode yang bisa di-reuse (libraries, dependencies, custom runtime) dari function code. Layers bisa di-share antar functions dan bahkan antar accounts.
Mengapa Menggunakan Layers?
| Keuntungan | Penjelasan |
|---|---|
| Reduce size | Function code lebih kecil, dependencies di layer |
| Share code | Layer bisa dipakai banyak function |
| Version management | Layer punya versi sendiri |
| Faster deploy | Hanya upload perubahan function code |
Membuat & Menggunakan Layers
# ===========================
# CREATE LAMBDA LAYER
# ===========================
# Struktur folder layer:
# my-layer/
# nodejs/
# node_modules/
# sharp/
# uuid/
# package.json
# 1. Install dependencies ke folder layer
mkdir -p my-layer/nodejs
cd my-layer/nodejs
npm init -y
npm install sharp uuid axios
cd ../..
# 2. Package layer
cd my-layer
zip -r ../my-layer.zip .
cd ..
# 3. Publish layer
aws lambda publish-layer-version \
--layer-name my-node-utils \
--description "Common Node.js utilities (sharp, uuid, axios)" \
--zip-file fileb://my-layer.zip \
--compatible-runtimes nodejs20.x nodejs22.x \
--compatible-architectures x86_64 arm64
# Output:
# LayerVersionArn: arn:aws:lambda:REGION:ACCOUNT:layer:my-node-utils:1
# 4. Attach layer ke function
aws lambda update-function-configuration \
--function-name my-function \
--layers arn:aws:lambda:REGION:ACCOUNT:layer:my-node-utils:1
# 5. Function bisa akses layer di /opt/
# Contoh: const sharp = require('sharp'); // dari /opt/nodejs/node_modules/
# ===========================
# PYTHON LAYER EXAMPLE
# ===========================
# Struktur:
# python-layer/
# python/
# requests/
# boto3/
mkdir -p python-layer/python
cd python-layer/python
pip install requests boto3 -t .
cd ../..
cd python-layer
zip -r ../python-layer.zip .
cd ..
aws lambda publish-layer-version \
--layer-name python-utils \
--zip-file fileb://python-layer.zip \
--compatible-runtimes python3.12
Gunakan Lambda Layers untuk dependencies yang jarang berubah (SDK, utility libraries) dan function code yang sering di-update. Ini mempercepat proses deploy karena function package menjadi lebih kecil.
6. Cold Starts & Optimization
Cold start adalah delay saat Lambda pertama kali diinisialisasi untuk menjalankan function. Ini termasuk waktu download code, inisialisasi runtime, dan menjalankan init code. Cold start bisa berlangsung dari puluhan milidetik hingga beberapa detik.
Warm Start vs Cold Start
COLD START (Pertama kali / setelah idle ~15 menit) ┌──────────────┬───────────────┬──────────────┬──────────────┐ │ Download │ Initialize │ Init Code │ Handler │ │ Code │ Runtime │ (module │ Execution │ │ (S3→disk) │ (node/python)│ loading) │ (your fn) │ │ ~100ms │ ~200ms │ ~100-2000ms│ ~varies │ └──────────────┴───────────────┴──────────────┴──────────────┘ Total: 400ms - 5000ms+ WARM START (Container reuse) ┌──────────────────────────────────────────────────────────────┐ │ Handler Execution │ │ (your function) │ │ ~varies │ └──────────────────────────────────────────────────────────────┘ Total: 1ms - varms
Cold Start Optimization Strategies
# ===========================
# COLD START OPTIMIZATION
# ===========================
# 1. PILIH RUNTIME YANG CEPAT
# Fast: Node.js, Python
# Slow: Java, .NET (butuh JVM/CLR init)
# Java fix: Gunakan SnapStart (AWS Lambda SnapStart)
# 2. MINIMALISASI DEPENDENCIES
# ❌ Import seluruh AWS SDK
# const AWS = require('aws-sdk'); // ~50MB
# ✅ Import hanya yang dibutuhkan
# const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); // ~2MB
# 3. KURANGI PACKAGE SIZE
# - Tree-shaking (webpack/esbuild)
# - Exclude devDependencies
# - Use minified packages
# 4. GUNAKAN LAYERS
# - Taruh dependencies besar di layers
# - Function code jadi lebih kecil
# 5. PILIH ARCHITECTURE ARM64 (Graviton2)
# - ~34% better price-performance
# - Set di Lambda config: Architectures: ['arm64']
# 6. PROVISIONED CONCURRENCY
# - Pre-warm sejumlah container
# - Eliminasi cold start untuk traffic dasar
# - Biaya: tetap bayar seperti reserved capacity
# 7. INIT CODE DI LUAR HANDLER
# - Database connections, SDK clients → module scope
# - JANGAN buat koneksi baru di setiap invocation
# 8. GUNAKAN SNAPSTART (Java)
# - Snapshot JVM state saat init
# - Restore dari snapshot (milidetik)
Init Code Pattern (Best Practice)
// ===========================
// COLD START OPTIMIZATION PATTERN
// ===========================
// Module scope: HANYA diinisialisasi sekali (cold start)
// Ini akan di-reuse di warm starts!
// ✅ Import yang spesifik
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand, PutCommand } = require('@aws-sdk/lib-dynamodb');
// ✅ Client di module scope (reuse warm container)
const dynamoClient = new DynamoDBClient({
region: process.env.AWS_REGION || 'ap-southeast-1',
maxAttempts: 3
});
const docClient = DynamoDBDocumentClient.from(dynamoClient);
// ✅ Config di module scope
const TABLE_NAME = process.env.TABLE_NAME;
const CACHE = new Map(); // In-memory cache (warm starts only)
// ❌ JANGAN buat koneksi baru di handler!
// exports.handler = async (event) => {
// const client = new DynamoDBClient({...}); // BAD!
// }
exports.handler = async (event) => {
// Handler code: hanya business logic
const userId = event.pathParameters?.userId;
// Cek cache dulu (warm start benefit)
if (CACHE.has(userId)) {
console.log('Cache hit!');
return response(200, CACHE.get(userId));
}
const result = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: { userId }
}));
if (result.Item) {
CACHE.set(userId, result.Item); // Cache for warm starts
}
return response(200, result.Item);
};
function response(statusCode, body) {
return {
statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || { error: 'Not found' })
};
}
7. Environment & Configuration
Environment Variables
# Set environment variables
aws lambda update-function-configuration \
--function-name my-function \
--environment Variables="{
TABLE_NAME=users,
ENVIRONMENT=production,
LOG_LEVEL=info,
REGION=ap-southeast-1
}"
# Untuk secrets, gunakan AWS Secrets Manager
# Lambda akan mengambil secrets dari Secrets Manager saat runtime
# Encrypt environment variables
# Enable helpers encryption in Lambda config
# KMS key: alias/lambda-key
Secrets Manager Integration
// Mengambil secrets dari AWS Secrets Manager
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
const secretsClient = new SecretsManagerClient({ region: 'ap-southeast-1' });
// Cache secrets (module scope - warm starts)
let cachedSecret = null;
async function getSecret(secretName) {
// Return cached if available
if (cachedSecret) return cachedSecret;
const result = await secretsClient.send(new GetSecretValueCommand({
SecretId: secretName
}));
cachedSecret = JSON.parse(result.SecretString);
return cachedSecret;
}
exports.handler = async (event) => {
// Get database credentials
const dbSecret = await getSecret('prod/database/credentials');
// Use credentials
const connection = await createConnection({
host: dbSecret.host,
port: dbSecret.port,
user: dbSecret.username,
password: dbSecret.password,
database: dbSecret.dbname
});
// ... business logic
};
8. IAM Roles & Permissions
Setiap Lambda function memiliki execution role — IAM role yang menentukan apa yang boleh dilakukan function. Selalu ikuti prinsip least privilege.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Sid": "DynamoDBAccess",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:ap-southeast-1:*:table/users"
},
{
"Sid": "S3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Sid": "SecretsManagerRead",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:ap-southeast-1:*:secret:prod/*"
}
]
}
JANGAN menggunakan "Action": "*" atau "Resource": "*" pada Lambda execution role. Ini memberikan akses penuh ke semua layanan AWS — sangat berbahaya jika Lambda function terkompromi. Selalu tentukan resource spesifik yang dibutuhkan.
9. Step Functions & Orchestration
AWS Step Functions memungkinkan Anda mengorkestrasi beberapa Lambda function dalam workflow yang kompleks — termasuk conditional logic, parallel execution, error handling, dan retry.
Step Functions State Machine
{
"Comment": "Order Processing Workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:validate-order",
"Next": "CheckInventory",
"Retry": [
{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 3,
"MaxAttempts": 2,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError",
"ResultPath": "$.error"
}
]
},
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:check-inventory",
"Next": "IsInStock"
},
"IsInStock": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.inStock",
"BooleanEquals": true,
"Next": "ProcessPayment"
}
],
"Default": "NotifyOutOfStock"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:process-payment",
"Next": "ParallelNotifications"
},
"ParallelNotifications": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "SendEmail",
"States": {
"SendEmail": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:send-email",
"End": true
}
}
},
{
"StartAt": "SendSMS",
"States": {
"SendSMS": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:send-sms",
"End": true
}
}
}
],
"Next": "OrderComplete"
},
"OrderComplete": {
"Type": "Succeed"
},
"NotifyOutOfStock": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:notify-out-of-stock",
"End": true
},
"HandleError": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT:function:handle-error",
"End": true
}
}
}
10. Monitoring & Debugging
CloudWatch Logs & Metrics
# ===========================
# MONITORING LAMBDA
# ===========================
# Tail logs secara real-time
aws logs tail /aws/lambda/my-function --follow
# Search logs
aws logs filter-log-events \
--log-group-name /aws/lambda/my-function \
--filter-pattern "ERROR" \
--start-time $(date -d '1 hour ago' +%s)000
# Get function metrics
aws lambda get-function \
--function-name my-function \
--query '{Status: Configuration.State, LastModified: Configuration.LastModified, MemorySize: Configuration.MemorySize, Timeout: Configuration.Timeout}'
# Get function URL (jika dikonfigurasi)
aws lambda get-function-url-config \
--function-name my-function
Structured Logging Best Practice
// Structured logging untuk CloudWatch Insights
const logger = {
info: (message, data = {}) => {
console.log(JSON.stringify({
level: 'INFO',
message,
timestamp: new Date().toISOString(),
...data
}));
},
error: (message, error, data = {}) => {
console.error(JSON.stringify({
level: 'ERROR',
message,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
...data
}));
},
warn: (message, data = {}) => {
console.warn(JSON.stringify({
level: 'WARN',
message,
timestamp: new Date().toISOString(),
...data
}));
}
};
exports.handler = async (event, context) => {
logger.info('Function started', {
requestId: context.awsRequestId,
functionName: context.functionName,
remainingMs: context.get_remaining_time_in_millis()
});
try {
// ... business logic
logger.info('Processing complete', {
itemCount: items.length,
durationMs: Date.now() - startTime
});
} catch (error) {
logger.error('Processing failed', error, {
input: event
});
throw error;
}
};
// CloudWatch Insights Query contoh:
// fields @timestamp, level, message, requestId
// | filter level = "ERROR"
// | sort @timestamp desc
// | limit 20
11. Production Best Practices
Lambda Best Practices Summary
| Area | Best Practice | Dampak |
|---|---|---|
| Performance | Minimalkan dependencies, gunakan layers | 🟢 Faster cold start |
| Architecture | Gunakan ARM64 (Graviton2) | 🟢 34% lebih murah |
| Memory | Tune memory dengan AWS Lambda Power Tuning | 🟢 Cost & speed optimal |
| Security | Least privilege IAM, encrypt env vars | 🟢 Reduced blast radius |
| Concurrency | Set reserved concurrency untuk critical functions | 🟢 Predictable scaling |
| Code | Init code di module scope, handler bersih | 🟢 Warm start benefit |
| Logging | Structured JSON logging | 🟢 Easy debugging |
| Dependencies | SDK v3 modular imports | 🟢 Smaller package size |
Lambda Limit Reference
| Limit | Default | Max |
|---|---|---|
| Memory | 128 MB | 10,240 MB |
| Timeout | 3 detik | 900 detik (15 menit) |
| Package size (zip) | - | 50 MB |
| Package size (unzip) | - | 250 MB |
| Layers per function | - | 5 layers |
| Env variables | - | 4 KB total |
| Concurrency | 1,000 | Custom limit |
| /tmp storage | 512 MB | 10,240 MB |
| Payload (sync) | - | 6 MB |
| Payload (async) | - | 256 KB |
12. Quiz Pemahaman
Uji pemahaman Anda tentang AWS Lambda:
1. Apa itu cold start di AWS Lambda?
2. Apa fungsi Lambda Layers?
3. Mengapa harus mengimpor SDK client secara spesifik (modular)?
4. Apa batasan waktu eksekusi maksimum Lambda?
5. Mengapa init code (koneksi DB, SDK clients) harus di module scope?