☀️Siang
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

Artikel: Aws Lambda Serverless Artikel: Aws Lambda Serverless


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

API Gateway REST / HTTP

SQS / SNS Messages

S3 Event Object Created

CloudWatch Schedule / Event

λ Lambda Function Your Code Auto-scales 0→∞

DynamoDB NoSQL DB

S3 Bucket File Storage

SQS / SNS Queue / Notify

External API 3rd Party

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

Diagram

Client (Browser /App) HTTPS

API Gateway • Authentication • Rate Limiting • ...

λ Lambda Function • Business Logic • Data Proce...

Dynamo DB NoSQL Table

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 vs Warm Start
❄ COLD START
First invocation / after ~15 min idle
Download
Code Package
50-200ms
Initialize
Runtime
50-200ms
Init Code
Imports & Setup
100-500ms
Handler
Your Function
varies
TOTAL
400ms-5s+
⚡ WARM START
Container reuse (warm Lambda)
Download
SKIPPED
Initialize
SKIPPED
Init Code
SKIPPED
Handler
Only!
TOTAL
~1ms
Performance Comparison
400ms — 5000ms+
~1ms

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