本文測試數據與實作細節由同事提供。
AWS Lambda 計費看起來很簡單:Memory × Runtime = Compute(GB-s),用多少付多少。
但真正去調校過才會發現,這個公式裡藏著不少眉角。Memory 設太小,執行慢、費用不一定比較低;連線池沒配好,偶發的 EOF 會讓呼叫方一頭霧水;Secrets Manager Extension 失敗時,如果沒有 fallback,整個函式直接壞掉。
這篇文章記錄三個我們實際跑過的調校,每一個都有數字、有程式碼、有前因後果。
一、Memory 調校:花 2.6% 換 45% 的速度
Lambda 的計費邏輯
Lambda 的費用由兩個維度決定:
- Memory:分配給函式的記憶體大小
- Runtime:函式實際執行所花的時間
兩者相乘才是真正的計費單位 GB-s。
有一個常被忽略的細節是:Memory 越大,AWS 同時也會配置更多 CPU 資源,所以給更多記憶體不只是讓函式能存更多東西——它會直接讓函式跑得更快。
這代表存在一個最佳平衡點。Memory 調高讓 runtime 縮短,但超過某個臨界點後,省下的時間已無法抵銷增加的記憶體費用。
測試結果
以 10,500 requests/day 的流量為基準,對同一份程式碼測試三種 Memory 設定:
| Memory (MB) | Runtime (ms) | 每月 Compute (GB-s) |
|---|---|---|
| 128 | 1,672 | 66,749 |
| 240 | 915 | 68,491 |
| 512 | 484 | 77,289 |
以 128MB 為基準換算:
- 240MB:成本 +2.6%,runtime 縮短 45%
- 512MB:成本 +15.8%,runtime 縮短 71%
240MB 是最佳甜蜜點。
另外測試中發現 240MB 與 256MB 的 runtime 幾乎相同,代表在這個區間 CPU 資源沒有再增加,選 240MB 即可。
設定變更
1# Before
2resource "aws_lambda_function" "handler" {
3 memory_size = 128
4 timeout = 30
5}
6
7# After
8resource "aws_lambda_function" "handler" {
9 memory_size = 240
10 timeout = 10
11}
Timeout 同步從 30s 縮短為 10s,反映實際執行時間,避免函式異常時白白等待過久。
二、Secrets Manager 穩定性:加一條 Fallback 路徑
背景
AWS Secrets Manager 是用來集中管理敏感設定(API 金鑰、憑證等)的服務。
為了加速存取,AWS 提供了一個 Lambda Extension,它會在 Lambda 容器內部建立本地快取,避免每次都打 API,降低延遲並節省費用。
問題
偶發性地,從這個本地快取讀取設定時會失敗,導致函式無法正常啟動。
原本的邏輯只有單一路徑:
1// Before: 只有一條路,失敗就直接回傳 error
2secretResponse, err := extensionService.GetSecret(secretName)
3if err != nil {
4 return nil, fmt.Errorf("unable to get secret: %w", err)
5}
解法
加入 fallback 機制:Lambda Extension 失敗時,改走 AWS SDK 直接呼叫 Secrets Manager API。
1// After: Extension 失敗時,fallback 到 AWS SDK
2func getSecretWithFallback(ctx context.Context, ext SecretGetter, sdk SecretReader, secretName string) (string, error) {
3 secret, err := ext.GetSecret(secretName)
4 if err == nil {
5 return secret, nil
6 }
7
8 // 只有特定錯誤類型才值得 fallback
9 // 若是其他錯誤(例如 secret 不存在),fallback 也沒有意義
10 if !shouldFallback(err) {
11 return "", fmt.Errorf("extension failed: %w", err)
12 }
13
14 log.Printf("extension failed, falling back to SDK: %v", err)
15 return sdk.GetSecret(ctx, secretName)
16}
17
18func shouldFallback(err error) bool {
19 return errors.Is(err, ErrExtensionNotReady) ||
20 errors.Is(err, ErrExtensionRequest) ||
21 errors.Is(err, ErrExtensionServer)
22}
同時將 Lambda Extension layer 升級至最新版本,包含上游的穩定性修復。
注意 fallback 的條件要明確——不是所有錯誤都適合重試。Extension 尚未就緒、請求失敗、伺服器錯誤才值得 fallback;如果是 secret 根本不存在,fallback 到 SDK 也只是多打一次失敗的 API。
三、Connection Pool:EOF 錯誤的根因與修復
Connection Pool 是什麼
每次建立網路連線都有成本(TCP handshake、TLS 握手等)。Connection Pool 的概念是預先建立好一批連線並重複使用,避免每個請求都重新建立,藉此降低延遲。
問題
Connection Pool 裡的連線是有生命週期的。如果一條連線閒置太久,對端伺服器可能主動將它關閉,但 client 端不一定即時感知到。
當下一個請求拿到這條「已死」的連線去發送資料時,就會收到 EOF——代表連線已被對方切斷,沒有任何回應。
解法
兩個方向同時處理:
- 縮短 idle 連線的存活時間
將 idleConnTimeout 從 90 秒縮短為 5 秒,讓 pool 中的連線在被對端關閉之前就主動淘汰。
1// Before
2idleConnTimeout = 90 * time.Second
3
4// After
5idleConnTimeout = 5 * time.Second
- 偵測到死連線時自動重試
縮短 timeout 能降低機率,但無法完全消除競態條件——連線可能在剛被取出的瞬間被對端關閉。加入 EOF retry 作為第二道保險:
1const maxRetries = 1
2
3func (s *Service) SendRequest(ctx context.Context, payload string) (int, []byte, error) {
4 var lastErr error
5
6 for attempt := 0; attempt <= maxRetries; attempt++ {
7 req, _ := http.NewRequestWithContext(ctx, http.MethodPost, s.url, strings.NewReader(payload))
8
9 resp, err := s.client.Do(req)
10 if err != nil {
11 if isEOFError(err) && attempt < maxRetries {
12 lastErr = err
13 continue
14 }
15 return 0, nil, err
16 }
17 defer resp.Body.Close()
18
19 body, _ := io.ReadAll(resp.Body)
20 return resp.StatusCode, body, nil
21 }
22
23 return 0, nil, fmt.Errorf("failed after retries: %w", lastErr)
24}
25
26// EOF 有多種形式,需要一併處理
27func isEOFError(err error) bool {
28 if errors.Is(err, io.EOF) {
29 return true
30 }
31 return strings.Contains(err.Error(), "EOF")
32}
兩個方向互補:縮短 idle timeout 降低發生機率,retry 確保萬一發生時能自動恢復,對呼叫方完全透明。
總結
| 項目 | 變更 | 效益 |
|---|---|---|
| Memory 調整 | 128MB → 240MB,timeout 30s → 10s | Runtime -45%,成本僅增 2.6% |
| Secrets Manager | SDK fallback + Extension 升版 | 消除偶發性啟動失敗 |
| Connection Pooling | idleConnTimeout 90s → 5s,加入 EOF retry | 消除因 idle 連線被關閉造成的請求失敗 |
這三個調校沒有一個是大改動,但每一個解決的都是真實發生過的問題。Lambda 的「簡單」有時候是一種假象——底層的連線管理、Extension 機制、計費模型,還是需要真正理解才能用得好。

評論