AWS Lambda & Serverless: Từ Prototype Đến Production
Danh Mục Bài Viết
- 1. Serverless Là Gì Và Tại Sao AWS Lambda Lại Phổ Biến Đến Vậy?
- 2. Cold Start — Kẻ Thù Số Một Của Lambda Production
- 3. Từng Bước Đưa Lambda Từ Prototype Lên Production
- 4. Cấu trúc code đúng cho Lambda
- 5. Quản lý Environment Variables đúng cách
- 6. Error Handling và Dead Letter Queue
- 7. Observability — Biết Chuyện Gì Đang Xảy Ra
- 8. Quản lý Concurrency — Tránh "Noisy Neighbor"
- 9. Những Lỗi Mình Thấy Nhiều Nhất Khi Review Lambda Code
- 10. FAQ — Những Câu Hỏi Hay Gặp
- 11. Điều Mình Muốn Nói Thẳng
Có một câu hỏi mình hay nghe từ các bạn mới tiếp cận AWS Lambda: "Deploy lên rồi, sao vẫn chưa xong?" Câu trả lời thường nằm ở chỗ: prototype và production là hai thế giới khác nhau hoàn toàn. Bài này mình sẽ đi thẳng vào những điểm khác biệt đó — cùng với cách xử lý để Lambda của bạn chạy ổn định trên môi trường thực.
Serverless Là Gì Và Tại Sao AWS Lambda Lại Phổ Biến Đến Vậy?
Serverless không có nghĩa là "không có server" — mà là bạn không cần quản lý server. AWS Lambda cho phép bạn chạy code mà không cần provision, scale hay patch bất kỳ máy chủ nào. Bạn chỉ trả tiền cho thời gian code chạy thực tế, tính theo millisecond.
Với prototype, điều này cực kỳ hấp dẫn: viết một function Python hoặc Node.js, bấm Deploy, gắn API Gateway vào — xong. Nhưng khi traffic thật sự đến, hoặc khi hệ thống phức tạp hơn với nhiều function liên kết nhau, một loạt vấn đề mới bắt đầu xuất hiện mà lúc viết demo bạn không thể thấy được.
Lambda được thiết kế theo mô hình event-driven: mỗi lần có sự kiện (HTTP request, SQS message, S3 upload, CloudWatch schedule...), một instance Lambda được khởi động để xử lý. Đây là điểm mạnh, nhưng cũng là nguồn gốc của phần lớn vấn đề production.
Cold Start — Kẻ Thù Số Một Của Lambda Production
Cold start xảy ra khi Lambda cần khởi tạo một execution environment mới — tức là instance chưa tồn tại hoặc đã bị AWS thu hồi sau thời gian không dùng. Thời gian này có thể dao động từ vài trăm millisecond đến hơn 1 giây, tùy vào runtime và kích thước deployment package.
Trong prototype, bạn test lác đác nên ít gặp cold start. Nhưng khi production có traffic không đều — ví dụ ban đêm không có request, sáng sớm đột ngột có người dùng — cold start xuất hiện ngay và người dùng đầu tiên sẽ thấy latency cao bất thường.
Một số cách xử lý thực tế:
- Provisioned Concurrency: Giữ một số lượng instance Lambda luôn "warm". Tốn thêm chi phí nhưng loại bỏ hoàn toàn cold start cho những API quan trọng.
- Giảm kích thước deployment package: Package nhỏ hơn → init nhanh hơn. Tránh import thư viện không cần thiết. Với Python, dùng Lambda Layers để tách dependency ra khỏi code chính.
- Chọn runtime phù hợp: Node.js và Python có cold start nhanh hơn Java hay .NET đáng kể. Nếu đang dùng Java Spring Boot cho Lambda — hãy cân nhắc lại kiến trúc.
- Tăng Memory: Memory cao hơn đồng nghĩa CPU cao hơn, init nhanh hơn. Đôi khi tăng từ 128MB lên 512MB giảm cold start xuống 40%.
Mẹo thực tế: Dùng CloudWatch để monitor metric
InitDuration— đây là thời gian cold start. Nếu con số này cao và thường xuyên, Provisioned Concurrency là đáng đầu tư hơn bạn nghĩ.
Từng Bước Đưa Lambda Từ Prototype Lên Production
Dưới đây là checklist mình dùng mỗi lần đưa một Lambda function vào production thực sự:
1. Cấu trúc code đúng cho Lambda
Code Lambda production khác prototype ở chỗ: bạn phải tách rõ initialization code ra khỏi handler. Những gì chạy một lần (kết nối database, load config, khởi tạo SDK client) phải nằm ngoài handler để được tái dùng giữa các invocations.
# ĐÚNG — connection pool được tái dùng
import boto3
import psycopg2
# Init một lần, dùng nhiều lần
db_conn = None
s3_client = boto3.client('s3')
def get_db():
global db_conn
if db_conn is None or db_conn.closed:
db_conn = psycopg2.connect(
host=os.environ['DB_HOST'],
database=os.environ['DB_NAME'],
user=os.environ['DB_USER'],
password=os.environ['DB_PASS']
)
return db_conn
def lambda_handler(event, context):
conn = get_db()
# xử lý logic ở đây
return {'statusCode': 200, 'body': 'OK'}
Ngược lại, nếu bạn mở connection trong handler, mỗi invocation sẽ tốn thêm 100-300ms cho việc kết nối — cộng dồn lại với traffic cao là thảm họa.
2. Quản lý Environment Variables đúng cách
Prototype thường hardcode config hoặc dùng .env file. Production thì không. Dùng AWS Systems Manager Parameter Store hoặc Secrets Manager để quản lý secret. Lambda đọc từ environment variable, còn giá trị thực lấy từ SSM khi khởi động.
import boto3
import os
def get_secret(param_name):
ssm = boto3.client('ssm', region_name='ap-northeast-1')
response = ssm.get_parameter(Name=param_name, WithDecryption=True)
return response['Parameter']['Value']
# Cache lại để tránh gọi SSM mỗi invocation
DB_PASSWORD = get_secret('/myapp/prod/db-password')
3. Error Handling và Dead Letter Queue
Lambda retry mặc định 2 lần khi gặp lỗi (với async invocation). Nếu không xử lý đúng, bạn sẽ xử lý cùng một message 3 lần mà không biết. Cấu hình Dead Letter Queue (DLQ) với SQS để catch các message thất bại:
# serverless.yml hoặc SAM template
functions:
processOrder:
handler: handler.process_order
events:
- sqs:
arn: !GetAtt OrderQueue.Arn
batchSize: 10
functionResponseType: ReportBatchItemFailures
destinations:
onFailure: arn:aws:sqs:region:account:dead-letter-queue
ReportBatchItemFailures đặc biệt quan trọng khi dùng SQS trigger — nó cho phép Lambda báo cáo từng item thất bại trong batch thay vì retry cả batch.
4. Observability — Biết Chuyện Gì Đang Xảy Ra
Production mà không có observability tốt thì debug như mò kim đáy bể. Tối thiểu cần:
- Structured logging: Log JSON thay vì plain text để CloudWatch Insights có thể query được.
- AWS X-Ray: Enable tracing để thấy toàn bộ request flow qua các services.
- Custom metrics: Dùng
aws-embedded-metricsđể push metric tùy chỉnh lên CloudWatch mà không tốn thêm chi phí API call.
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Structured log — dễ query với CloudWatch Insights
logger.info(json.dumps({
'event': 'order_processed',
'order_id': event.get('order_id'),
'duration_ms': 142,
'status': 'success'
}))
5. Quản lý Concurrency — Tránh "Noisy Neighbor"
Mặc định, tất cả Lambda function trong account dùng chung một concurrency pool (1000 concurrent executions theo mặc định ở hầu hết region). Nếu một function bị spike traffic và dùng hết pool, các function khác sẽ bị throttle.
Giải pháp: Set Reserved Concurrency cho từng function quan trọng. Function A quan trọng thì reserve 200, function B ít critical hơn thì để unreserved. Điều này đảm bảo A luôn có chỗ chạy, còn B dùng phần còn lại.
Những Lỗi Mình Thấy Nhiều Nhất Khi Review Lambda Code
Lỗi 1: Timeout quá ngắn hoặc quá dài. Default timeout là 3 giây — quá ngắn cho nhiều use case thực tế. Nhưng set 15 phút cho một API endpoint cũng là vấn đề vì API Gateway chỉ cho phép tối đa 29 giây. Phải match timeout của Lambda với timeout của caller.
Lỗi 2: Không giới hạn memory hợp lý. Chạy với 128MB tiết kiệm tiền nhưng chậm. Chạy với 3008MB cho một function đơn giản là lãng phí. Dùng AWS Lambda Power Tuning để tìm điểm tối ưu giữa chi phí và hiệu năng.
Lỗi 3: Kết nối database không dùng connection pooling. RDS Proxy được sinh ra để giải quyết vấn đề này — nó pool connection ở layer proxy, không phải trong Lambda. Với Aurora Serverless đặc biệt cần thiết.
Lỗi 4: Package quá lớn. Một deployment package 200MB sẽ làm cold start chậm kinh khủng. Dùng Lambda Layers cho dependency, loại bỏ test file và tài liệu, dùng --no-dev khi cài production dependency.
Lỗi 5: Không xử lý idempotency. Lambda có thể được invoke nhiều hơn một lần cho cùng một event (at-least-once delivery). Code production phải idempotent — xử lý cùng một request nhiều lần phải cho kết quả giống nhau, không gây side effect trùng lặp.
FAQ — Những Câu Hỏi Hay Gặp
Q: Lambda hay EC2/ECS — khi nào nên dùng cái nào?A: Lambda phù hợp cho workload event-driven, spiky, execution time ngắn (dưới vài phút). EC2/ECS phù hợp hơn khi cần long-running process, cần fine-grained control về networking/OS, hoặc có steady traffic cao khiến cost Lambda vượt cost container. Cũng tránh Lambda cho WebSocket connection dài hoặc background job chạy hàng giờ.
Q: Dùng Serverless Framework hay AWS SAM hay CDK?A: Cả ba đều tốt, tùy team. Serverless Framework có ecosystem plugin phong phú và dễ onboard. SAM là official AWS tool, tốt nếu team đã quen CloudFormation. CDK linh hoạt nhất và type-safe (TypeScript/Python), nhưng learning curve cao hơn. Mình đang dùng CDK cho dự án mới vì nó dễ reuse và test hơn YAML template.
Q: Chi phí Lambda có thực sự rẻ hơn EC2 không?A: Phụ thuộc traffic pattern. Với spiky/unpredictable traffic, Lambda thường rẻ hơn nhiều vì bạn không trả tiền cho idle time. Nhưng nếu function chạy gần như liên tục 24/7, một EC2 t3.micro hoặc container có thể rẻ hơn. Điểm breakeven thường khoảng 60-70% utilization — tức là nếu Lambda của bạn chạy hơn 60% thời gian, hãy tính đến container.
Điều Mình Muốn Nói Thẳng
Serverless với AWS Lambda thực sự là công cụ mạnh — nhưng chỉ khi bạn hiểu rõ nó hoạt động như thế nào bên dưới. Prototype chạy được không có nghĩa là production sẽ ổn. Cold start, concurrency, error handling, observability — đây là những thứ phân biệt một Lambda function "chạy được" với một Lambda function "đáng tin cậy".
Nếu bạn đang chuẩn bị đưa Lambda vào production lần đầu, hãy bắt đầu với Provisioned Concurrency cho những API critical, cấu hình DLQ cho async function, và bật X-Ray ngay từ đầu. Ba thứ đó giải quyết được 80% vấn đề phổ biến nhất.
Có gì cần hỏi thêm về kiến trúc serverless hoặc kinh nghiệm thực tế với Lambda, cứ để lại comment nhé — mình thường reply trong ngày.

