AWS Lambda 冷啟動優化策略

AWS Lambda Cold Start Optimization Strategies

冷啟動概述

AWS Lambda 冷啟動(Cold Start)是指當 Lambda 函數在一段時間內沒有被調用,或當需要擴展新的執行環境時,AWS 需要初始化新的執行環境來處理請求的過程。這個初始化過程會導致額外的延遲,對於需要低延遲回應的應用程式來說是一個重要的效能考量。

冷啟動的典型延遲時間範圍:

  • Node.js / Python:100ms - 500ms
  • Java / .NET:500ms - 5000ms
  • 容器映像:可能更長

冷啟動原因分析

冷啟動發生在以下情況:

  1. 首次調用:函數第一次被調用時
  2. 閒置超時:執行環境閒置一段時間後被回收(通常 5-15 分鐘)
  3. 擴展事件:並發請求超過現有執行環境數量
  4. 程式碼更新:部署新版本的函數程式碼
  5. 設定變更:修改記憶體、超時等設定

冷啟動過程包含:

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────────────────────┐
│  冷啟動階段                                                  │
├─────────────────────────────────────────────────────────────┤
│  1. 下載程式碼套件                                           │
│  2. 啟動執行環境                                             │
│  3. 初始化執行時期(Runtime)                                │
│  4. 執行初始化程式碼(Handler 外的程式碼)                    │
│  5. 執行 Handler 函數                                        │
└─────────────────────────────────────────────────────────────┘

測量冷啟動時間

使用 CloudWatch Logs Insights

1
2
3
4
5
6
filter @type = "REPORT"
| stats avg(@initDuration) as avg_init,
        max(@initDuration) as max_init,
        min(@initDuration) as min_init,
        count(*) as invocations
| sort invocations desc

使用 AWS CLI 查詢

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 取得函數的調用統計
aws cloudwatch get-metric-statistics \
    --namespace AWS/Lambda \
    --metric-name Duration \
    --dimensions Name=FunctionName,Value=my-function \
    --start-time 2024-04-13T00:00:00Z \
    --end-time 2024-04-14T00:00:00Z \
    --period 3600 \
    --statistics Average Maximum

# 查詢冷啟動相關日誌
aws logs filter-log-events \
    --log-group-name /aws/lambda/my-function \
    --filter-pattern "Init Duration"

使用 X-Ray 追蹤

在 Lambda 設定中啟用 X-Ray 追蹤:

1
2
3
aws lambda update-function-configuration \
    --function-name my-function \
    --tracing-config Mode=Active

程式語言選擇影響

不同程式語言對冷啟動時間有顯著影響:

程式語言平均冷啟動時間建議使用場景
Python100-300msAPI Gateway、資料處理
Node.js100-300msWeb API、事件處理
Go50-150ms高效能、低延遲需求
Java500-3000ms企業應用、Spring Boot
.NET400-1500ms企業應用、C# 專案

輕量級執行時期範例(Python)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 避免在 handler 外進行不必要的初始化
import json

# 僅導入必要的模組
from boto3 import client

# 在 handler 外初始化客戶端(會被重用)
dynamodb = client('dynamodb')

def lambda_handler(event, context):
    response = dynamodb.get_item(
        TableName='my-table',
        Key={'id': {'S': event['id']}}
    )
    return {
        'statusCode': 200,
        'body': json.dumps(response.get('Item', {}))
    }

減少程式碼套件大小

最小化依賴套件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 使用 pip 僅安裝必要套件
pip install --target ./package boto3 --no-deps

# 移除不必要的檔案
find ./package -type d -name "__pycache__" -exec rm -rf {} +
find ./package -type f -name "*.pyc" -delete
find ./package -type f -name "*.pyo" -delete

# 建立部署套件
cd package && zip -r9 ../deployment.zip .
cd .. && zip -g deployment.zip lambda_function.py

使用 Lambda Layers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 建立 Layer
mkdir -p python/lib/python3.11/site-packages
pip install requests -t python/lib/python3.11/site-packages
zip -r layer.zip python

# 發布 Layer
aws lambda publish-layer-version \
    --layer-name my-dependencies \
    --zip-file fileb://layer.zip \
    --compatible-runtimes python3.11

# 將 Layer 附加到函數
aws lambda update-function-configuration \
    --function-name my-function \
    --layers arn:aws:lambda:us-east-1:123456789012:layer:my-dependencies:1

Provisioned Concurrency

Provisioned Concurrency 可以預先初始化執行環境,消除冷啟動:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 設定 Provisioned Concurrency
aws lambda put-provisioned-concurrency-config \
    --function-name my-function \
    --qualifier prod \
    --provisioned-concurrent-executions 10

# 查詢設定狀態
aws lambda get-provisioned-concurrency-config \
    --function-name my-function \
    --qualifier prod

# 刪除 Provisioned Concurrency
aws lambda delete-provisioned-concurrency-config \
    --function-name my-function \
    --qualifier prod

使用 Application Auto Scaling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 註冊可擴展目標
aws application-autoscaling register-scalable-target \
    --service-namespace lambda \
    --resource-id function:my-function:prod \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --min-capacity 5 \
    --max-capacity 100

# 設定擴展策略
aws application-autoscaling put-scaling-policy \
    --service-namespace lambda \
    --resource-id function:my-function:prod \
    --scalable-dimension lambda:function:ProvisionedConcurrency \
    --policy-name my-scaling-policy \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{
        "TargetValue": 0.7,
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
        }
    }'

Lambda SnapStart(Java)

SnapStart 是專為 Java 執行時期設計的功能,可大幅減少冷啟動時間:

1
2
3
4
5
6
7
8
# 啟用 SnapStart
aws lambda update-function-configuration \
    --function-name my-java-function \
    --snap-start ApplyOn=PublishedVersions

# 發布新版本以套用 SnapStart
aws lambda publish-version \
    --function-name my-java-function

Java 程式碼最佳化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.crac.Core;
import org.crac.Resource;

public class Handler implements RequestHandler<Map<String, Object>, String>, Resource {

    private final DynamoDbClient dynamoDb;

    public Handler() {
        // 初始化時建立客戶端
        this.dynamoDb = DynamoDbClient.builder()
            .region(Region.US_EAST_1)
            .build();

        // 註冊 CRaC 資源以處理快照還原
        Core.getGlobalContext().register(this);
    }

    @Override
    public void beforeCheckpoint(org.crac.Context<? extends Resource> context) {
        // 快照前的清理工作
    }

    @Override
    public void afterRestore(org.crac.Context<? extends Resource> context) {
        // 還原後的初始化工作
    }

    @Override
    public String handleRequest(Map<String, Object> event, Context context) {
        // 處理請求
        return "Success";
    }
}

連線重用技巧

保持連線存活(Python)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import boto3
from botocore.config import Config

# 設定連線重用
config = Config(
    connect_timeout=5,
    read_timeout=30,
    retries={'max_attempts': 3},
    tcp_keepalive=True
)

# 在 handler 外建立客戶端
dynamodb = boto3.resource('dynamodb', config=config)
table = dynamodb.Table('my-table')

def lambda_handler(event, context):
    # 重用已建立的連線
    response = table.get_item(Key={'id': event['id']})
    return response.get('Item')

HTTP Keep-Alive(Node.js)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const https = require('https');
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');

// 建立保持連線的 Agent
const agent = new https.Agent({
    keepAlive: true,
    maxSockets: 50
});

// 在 handler 外初始化客戶端
const client = new DynamoDBClient({
    requestHandler: {
        httpAgent: agent
    }
});

exports.handler = async (event) => {
    // 使用重用的連線
    const result = await client.send(new GetItemCommand({
        TableName: 'my-table',
        Key: { id: { S: event.id } }
    }));
    return result.Item;
};

最佳實踐

1. 記憶體配置優化

較高的記憶體配置會獲得更多 CPU 資源,可能減少冷啟動時間:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 測試不同記憶體配置的效能
for mem in 128 256 512 1024 2048; do
    aws lambda update-function-configuration \
        --function-name my-function \
        --memory-size $mem

    # 執行測試調用
    aws lambda invoke \
        --function-name my-function \
        --payload '{}' \
        response.json
done

2. 預熱策略

使用 CloudWatch Events 定期調用函數:

1
2
3
4
5
6
7
8
9
# 建立預熱規則
aws events put-rule \
    --name lambda-warmup \
    --schedule-expression "rate(5 minutes)"

# 新增 Lambda 作為目標
aws events put-targets \
    --rule lambda-warmup \
    --targets Id=1,Arn=arn:aws:lambda:us-east-1:123456789012:function:my-function

3. 架構建議

  • 將大型函數拆分為多個小型函數
  • 使用非同步調用減少延遲影響
  • 考慮使用 Step Functions 編排工作流程
  • 對於極低延遲需求,考慮使用 EC2 或 ECS

4. 監控儀表板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 建立 CloudWatch 儀表板
aws cloudwatch put-dashboard \
    --dashboard-name LambdaColdStartMonitor \
    --dashboard-body '{
        "widgets": [
            {
                "type": "metric",
                "properties": {
                    "metrics": [
                        ["AWS/Lambda", "Duration", "FunctionName", "my-function"],
                        [".", "ConcurrentExecutions", ".", "."]
                    ],
                    "period": 60,
                    "title": "Lambda Performance"
                }
            }
        ]
    }'

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy