AWS Lambda SnapStart 效能優化

AWS Lambda SnapStart Performance Optimization

概述

AWS Lambda SnapStart 是一項效能優化功能,專為解決 Java 等需要較長冷啟動時間的執行環境所設計。透過在部署時預先初始化執行環境並建立快照,SnapStart 可以大幅減少函數的啟動延遲,讓冷啟動時間從數秒降低至數百毫秒。

傳統的 Lambda 冷啟動流程包含:下載程式碼、啟動執行環境、初始化應用程式。對於 Java 應用程式來說,JVM 啟動和類別載入通常需要數秒時間。SnapStart 透過快照機制跳過這些耗時的初始化步驟,顯著提升效能。

運作原理

SnapStart 的核心機制如下:

  1. 發布階段:當您發布新版本時,Lambda 會執行初始化程式碼
  2. 快照建立:Lambda 將初始化完成的記憶體和磁碟狀態建立快照(Firecracker microVM snapshot)
  3. 快照加密:快照使用 AWS 管理的金鑰加密並快取
  4. 還原階段:當函數被呼叫時,Lambda 從快照還原執行環境,而非重新初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│                    傳統冷啟動流程                            │
├─────────────────────────────────────────────────────────────┤
│  下載程式碼 → 啟動 JVM → 類別載入 → 初始化 → 處理請求        │
│     ~500ms      ~1s        ~1s       ~500ms     執行時間     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                  SnapStart 啟動流程                          │
├─────────────────────────────────────────────────────────────┤
│            還原快照 → 處理請求                               │
│             ~200ms      執行時間                             │
└─────────────────────────────────────────────────────────────┘

支援的執行環境

目前 SnapStart 支援以下執行環境:

執行環境支援版本
JavaJava 11, Java 17, Java 21
PythonPython 3.12+
.NET.NET 8+

注意:SnapStart 目前僅支援 x86_64 架構,不支援 arm64 (Graviton2)。

啟用 SnapStart

使用 AWS CLI 啟用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 更新函數配置以啟用 SnapStart
aws lambda update-function-configuration \
    --function-name my-java-function \
    --snap-start ApplyOn=PublishedVersions

# 發布新版本以觸發快照建立
aws lambda publish-version \
    --function-name my-java-function \
    --description "Enable SnapStart"

# 確認 SnapStart 狀態
aws lambda get-function-configuration \
    --function-name my-java-function \
    --query 'SnapStart'

使用 AWS SAM 範本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  MyJavaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: my-java-function
      Handler: com.example.Handler::handleRequest
      Runtime: java17
      CodeUri: target/my-function.jar
      MemorySize: 512
      Timeout: 30
      SnapStart:
        ApplyOn: PublishedVersions

使用 Terraform

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
resource "aws_lambda_function" "java_function" {
  function_name = "my-java-function"
  handler       = "com.example.Handler::handleRequest"
  runtime       = "java17"
  memory_size   = 512
  timeout       = 30

  snap_start {
    apply_on = "PublishedVersions"
  }

  filename         = "my-function.jar"
  source_code_hash = filebase64sha256("my-function.jar")
  role             = aws_iam_role.lambda_role.arn
}

效能提升數據

根據 AWS 官方數據和實測結果,SnapStart 可帶來顯著的效能提升:

場景未啟用 SnapStart啟用 SnapStart改善幅度
純 Java 應用~3000ms~200ms93%
Spring Boot 應用~6000ms~300ms95%
Quarkus 應用~1500ms~150ms90%
Micronaut 應用~2000ms~180ms91%

Hook 機制

SnapStart 提供 Runtime Hooks 讓開發者在快照建立前後執行自訂邏輯:

Java CRaC API 範例

 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
36
37
38
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;

public class MyHandler implements RequestHandler<String, String>, Resource {

    private Connection dbConnection;

    public MyHandler() {
        // 註冊為 CRaC 資源
        Core.getGlobalContext().register(this);
        // 初始化資料庫連線
        this.dbConnection = createConnection();
    }

    @Override
    public void beforeCheckpoint(Context<? extends Resource> context) {
        // 快照建立前:關閉連線、釋放資源
        if (dbConnection != null) {
            dbConnection.close();
            dbConnection = null;
        }
        System.out.println("準備建立快照,已關閉資料庫連線");
    }

    @Override
    public void afterRestore(Context<? extends Resource> context) {
        // 快照還原後:重新建立連線
        this.dbConnection = createConnection();
        System.out.println("快照還原完成,已重新建立資料庫連線");
    }

    @Override
    public String handleRequest(String input, Context context) {
        // 處理請求
        return "Processed: " + input;
    }
}

Python 範例

 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
from snapshot_restore_py import register_before_snapshot, register_after_restore
import boto3

# 全域變數
s3_client = None

def init_s3_client():
    global s3_client
    s3_client = boto3.client('s3')
    print("S3 客戶端已初始化")

@register_before_snapshot
def before_snapshot():
    global s3_client
    s3_client = None
    print("快照前:清除 S3 客戶端")

@register_after_restore
def after_restore():
    init_s3_client()
    print("還原後:重新初始化 S3 客戶端")

# 初始化
init_s3_client()

def handler(event, context):
    # 使用 s3_client 處理請求
    buckets = s3_client.list_buckets()
    return {"bucket_count": len(buckets['Buckets'])}

唯一性考量

由於多個執行環境可能從同一快照還原,需要特別注意唯一性問題:

需要注意的情境

  • 隨機數生成:快照還原後,偽隨機數產生器的種子相同
  • UUID 產生:需確保每次呼叫產生唯一值
  • 暫存檔案:檔案名稱可能重複
  • 網路連線:socket 和連線狀態需重新建立

解決方案

 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
import java.security.SecureRandom;
import java.util.UUID;

public class UniqueIdGenerator implements Resource {

    private SecureRandom secureRandom;

    public UniqueIdGenerator() {
        Core.getGlobalContext().register(this);
        this.secureRandom = new SecureRandom();
    }

    @Override
    public void afterRestore(Context<? extends Resource> context) {
        // 還原後重新設定隨機種子
        this.secureRandom = new SecureRandom();
        // 使用系統熵源確保唯一性
        secureRandom.nextBytes(new byte[32]);
    }

    public String generateUniqueId() {
        // 結合時間戳記和隨機數確保唯一性
        return UUID.randomUUID().toString() + "-" + System.nanoTime();
    }
}

限制與注意事項

使用 SnapStart 時需注意以下限制:

  1. 版本要求:SnapStart 僅適用於已發布的版本,不適用於 $LATEST
  2. 佈建並行:不支援 Provisioned Concurrency
  3. 快照過期:14 天未使用的快照會被刪除,需重新建立
  4. 暫存目錄/tmp 目錄內容不會包含在快照中
  5. Ephemeral Storage:超過 512MB 的暫存儲存不支援
  6. VPC:支援 VPC 配置,但需額外注意網路連線重建
  7. 架構:僅支援 x86_64,不支援 arm64

不適合使用 SnapStart 的場景

  • 執行時間極短的函數(初始化開銷相對較大)
  • 需要 Provisioned Concurrency 的場景
  • 使用 arm64 架構的函數
  • 對冷啟動延遲不敏感的批次處理工作

最佳實踐

1. 初始化最佳化

將耗時的初始化邏輯放在建構子或靜態區塊中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class OptimizedHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    // 靜態初始化 - 會被快照捕捉
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private static final DynamoDbClient DYNAMO_CLIENT;

    static {
        DYNAMO_CLIENT = DynamoDbClient.builder()
            .region(Region.AP_NORTHEAST_1)
            .build();
    }

    @Override
    public APIGatewayProxyResponseEvent handleRequest(
            APIGatewayProxyRequestEvent event, Context context) {
        // 處理邏輯
    }
}

2. 監控與除錯

1
2
3
4
5
6
7
8
9
# 查看 SnapStart 相關指標
aws cloudwatch get-metric-statistics \
    --namespace AWS/Lambda \
    --metric-name SnapshotRestoreDuration \
    --dimensions Name=FunctionName,Value=my-java-function \
    --start-time 2025-02-15T00:00:00Z \
    --end-time 2025-02-16T00:00:00Z \
    --period 3600 \
    --statistics Average

3. 使用 Lambda Power Tuning

結合 AWS Lambda Power Tuning 工具找出最佳記憶體配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 部署 Power Tuning 並執行測試
aws lambda invoke \
    --function-name powerTuningFunction \
    --payload '{
        "lambdaARN": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-java-function:1",
        "powerValues": [256, 512, 1024, 2048],
        "num": 50,
        "payload": "{}"
    }' \
    output.json

參考資料

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