
上一篇幫你選好架構了,這篇聊 Serverless 的「但是」——Cold Start 有多痛、隱藏成本在哪、還有實務上最常見的混合部署怎麼搞。
先講結論
- Cold Start 的痛不在「有」,在於「不可預測」。Go/Rust < 100ms 幾乎無感,Java 可以飆到 5 秒
- Lambda 最貴的不是 Lambda 本身,是 API Gateway 和 NAT Gateway
- 生產環境不要全 Serverless 或全 Server——混合部署才是正解
Cold Start:到底有多慢?
Cold Start 發生在 function 閒置一段時間後,平台回收了執行環境,下一個請求進來要重新配置。延遲來自四個部分:
- 容器配置:50-200ms
- Runtime 初始化:10-100ms
- 程式碼 + 相依套件載入:50ms 到好幾秒
- VPC 網路介面配置(如果有開 VPC):額外 1-10 秒
不同語言的差距很大:
| Runtime | Cold Start 延遲 |
|---|---|
| Go / Rust | < 100ms(幾乎無感) |
| Python / Node.js | 100-500ms |
| Java / .NET | 500ms - 5s(JVM 啟動 + class loading) |
你有沒有遇過 API 偶爾回應超慢,其他時候又很快?八成就是 Cold Start。用戶不會管你的技術架構,他們只知道「這網站有時候很慢」。
怎麼解?
Provisioned Concurrency(Lambda) — 預先保持 warm 實例,完全消除 Cold Start。但你要為預留的實例持續付費,等於把「按次計費」的優勢打了折扣。
Keep-warm 定時觸發 — 每幾分鐘用 CloudWatch Event 發一個空請求。便宜但不能保證流量暴增時所有實例都是 warm 的。
Cloud Run min-instances — 設 min-instances: 1,確保始終有 warm container。成本介於 Provisioned Concurrency 和完全冷啟動之間。
選輕量 runtime — 如果 Cold Start 真的是痛點,考慮從 Java 換到 Go。或者接受命運。
Lambda 的隱藏成本:比 Lambda 本身還貴的東西
我第一次看到 Lambda 帳單的時候很開心——35。NAT Gateway——12。
| 隱藏成本項目 | 費用 | 備註 |
|---|---|---|
| API Gateway | $3.50/百萬次 | 往往比 Lambda 本身貴 |
| NAT Gateway | 0.045/GB | Lambda 在 VPC 裡存取外部服務就要付 |
| CloudWatch Logs | 按儲存量 | function 一多,log 量暴增 |
Lambda 本身按次計費很便宜,但周邊服務的費用可能把你的成本優勢完全吃掉。在決定用 Serverless 之前,把整個架構的成本都算進去,不要只看 Lambda pricing page 就覺得「太便宜了」。
混合部署:生產環境的正確答案
很少有團隊全部用一種架構。最常見的做法:核心 API 跑在 Server/CaaS,背景任務用 Serverless。
使用者 → ALB → Core API (ECS Fargate)
↓ publish event
SQS → Background Worker (Lambda) → 圖片縮放、寄 email
S3 → File Processor (Lambda) → 影片轉檔、CSV 匯入
CloudWatch → Scheduled Tasks (Lambda) → 報表、清理、同步
原則很簡單:
- 使用者直接互動的 API → Server/CaaS(低延遲、穩定)
- 非同步、可容忍延遲的任務 → Serverless(省錢)
- Cron job → Serverless(不需要 24 小時跑一台機器只為了每天執行 5 分鐘)
- 用 Message Queue(SQS/Pub/Sub)解耦核心 API 和背景處理
實戰範例:Lambda + SAM Template
# template.yaml — SAM 部署範本
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs20.x
MemorySize: 256
Timeout: 10
Environment:
Variables:
USERS_TABLE: !Ref UsersTable
Resources:
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: handler.handler
CodeUri: ./src
Events:
GetUser:
Type: Api
Properties:
Path: /users/{id}
Method: GET
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 2
Policies:
- DynamoDBReadPolicy:
TableName: !Ref UsersTable
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: users
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH注意 ProvisionedConcurrentExecutions: 2——這代表始終有 2 個 warm 實例等著,Cold Start 問題直接消失,但帳單會多一筆。
實戰範例:Cloud Run 部署
#!/bin/bash
PROJECT_ID="my-project"
SERVICE_NAME="user-api"
REGION="asia-east1"
IMAGE="gcr.io/${PROJECT_ID}/${SERVICE_NAME}"
docker build -t "${IMAGE}:latest" .
docker push "${IMAGE}:latest"
gcloud run deploy "${SERVICE_NAME}" \
--image "${IMAGE}:latest" \
--region "${REGION}" \
--platform managed \
--allow-unauthenticated \
--memory 512Mi \
--cpu 1 \
--min-instances 1 \
--max-instances 10 \
--concurrency 80 \
--timeout 300
SERVICE_URL=$(gcloud run services describe "${SERVICE_NAME}" \
--region "${REGION}" --format "value(status.url)")
echo "Deployed: ${SERVICE_URL}"--min-instances 1 確保至少一個 container 常駐,避免 Cold Start。--concurrency 80 表示每個 container 同時處理 80 個請求——Cloud Run 的 concurrency 模型和 Lambda(一個 instance 一個請求)不同,這點要注意。
容易忽略的坑
Lambda 記憶體和 CPU 是綁定的。 128MB 只給很少的 CPU,1,769MB 才等於一個 vCPU。如果你的 function 是 CPU 密集型(加解密、壓縮),增加記憶體反而可能降低總成本,因為執行時間大幅縮短。
Timeout + 重試 = 風暴。 Lambda 非同步呼叫預設重試 2 次。如果下游服務超時導致失敗,重試會讓下游壓力更大。設好 timeout + Dead Letter Queue,別讓重試變成 DDoS。
並發限制。 Lambda 每個 Region 預設 1,000 並發。一個吃流量的 function 可能擠掉同帳號其他 function 的額度。用 Reserved Concurrency 保護關鍵 function。
本地開發體驗。 SAM Local、Serverless Offline 都沒辦法完全模擬雲端環境(IAM、Event Source Mapping)。別太相信本地測試的結果,搭配一個真實的測試環境驗證。
延伸閱讀
Serverless 最大的謊言不是「不需要伺服器」,而是「不需要理解伺服器」。