DevOps & Cloud

AWS Lambda: Serverless Functions — Triggers, Layers, Cold Starts & API Gateway

TOKEN

Pelajari AWS Lambda dari nol — konsep serverless, event triggers, layers, cold start optimization, API Gateway integration, Step Functions, monitoring, dan production best practices

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 CaseLong-running, stateful appsEvent-driven, API, microservices
Diagram: AWS Lambda Event-Driven Architecture
┌──────────────────────────────────────────────────────────────────┐
│                  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.js18, 20, 22API, webhooks, data processing
Python3.10, 3.11, 3.12ML inference, data analysis, automation
Java17, 21Enterprise apps, heavy processing
GoProvided.al2023High-performance, CLI tools
.NET (C#)6, 8Enterprise .NET apps
Ruby3.3Web apps, scripting
Custom Runtimeprovided.al2023Bahasa apapun (Rust, PHP, dll)

2. Function Pertama

Hello World dengan Node.js

JavaScript — handler.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

Python — lambda_function.py
# 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

Bash
# ===========================
# 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

YAML — template.yaml (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
Bash — SAM Deploy
# 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 GatewayHTTP RequestREST API, Webhook
S3Object Created/DeletedImage resize, file processing
DynamoDB StreamDatabase changesReal-time sync, audit log
SQSMessage queueAsync processing, batch jobs
SNSPub/Sub notificationEmail, push notification
CloudWatch EventsScheduled (cron/rate)Cleanup, report generation
KinesisData streamReal-time analytics, log processing
CognitoUser pool triggerCustom auth, user migration

S3 Event Trigger

JavaScript — S3 Image Processor
// 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

JavaScript — Scheduled Cleanup
// 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

JavaScript — SQS Batch Processor
// 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.

Diagram: API Gateway + Lambda Flow
┌─────────┐    ┌─────────────────┐    ┌──────────────┐    ┌──────────┐
│  Client  │───►│  API Gateway    │───►│   Lambda     │───►│ DynamoDB │
│ (Browser │    │                 │    │   Function   │    │  S3, RDS │
│  /App)   │◄───│ • Authentication│◄───│              │◄───│  etc.    │
└─────────┘    │ • Rate Limiting │    │ • Business   │    └──────────┘
               │ • Validation    │    │   Logic      │
               │ • CORS          │    │ • Data       │
               │ • Caching       │    │   Processing │
               └─────────────────┘    └──────────────┘

REST API Handler

JavaScript — REST API Lambda
// 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 sizeFunction code lebih kecil, dependencies di layer
Share codeLayer bisa dipakai banyak function
Version managementLayer punya versi sendiri
Faster deployHanya upload perubahan function code

Membuat & Menggunakan Layers

Bash — Create Lambda Layer
# ===========================
# 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
💡 Tips

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

Diagram: Cold Start vs Warm 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

Text
# ===========================
# 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)

JavaScript — Optimal Lambda Structure
// ===========================
// 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

Bash — 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

JavaScript — Secrets Manager
// 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.

JSON — Lambda IAM Role (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/*"
    }
  ]
}
⚠️ Peringatan

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

JSON — Step Functions Definition
{
  "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

Bash — Monitoring Commands
# ===========================
# 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

JavaScript — Structured Logging
// 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
PerformanceMinimalkan dependencies, gunakan layers🟢 Faster cold start
ArchitectureGunakan ARM64 (Graviton2)🟢 34% lebih murah
MemoryTune memory dengan AWS Lambda Power Tuning🟢 Cost & speed optimal
SecurityLeast privilege IAM, encrypt env vars🟢 Reduced blast radius
ConcurrencySet reserved concurrency untuk critical functions🟢 Predictable scaling
CodeInit code di module scope, handler bersih🟢 Warm start benefit
LoggingStructured JSON logging🟢 Easy debugging
DependenciesSDK v3 modular imports🟢 Smaller package size

Lambda Limit Reference

Limit Default Max
Memory128 MB10,240 MB
Timeout3 detik900 detik (15 menit)
Package size (zip)-50 MB
Package size (unzip)-250 MB
Layers per function-5 layers
Env variables-4 KB total
Concurrency1,000Custom limit
/tmp storage512 MB10,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?

🔍 Zoom
100%
🎨 Tema