Ngôn ngữ hiển thị:
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 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 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ế:
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ĩ.
Dưới đây là checklist mình dùng mỗi lần đưa một Lambda function vào production thực sự:
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.
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')
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.
Production mà không có observability tốt thì debug như mò kim đáy bể. Tối thiểu cần:
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'
}))
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.
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.
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.
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.