Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là kiến trúc sư của một startup fintech ở TP.HCM. Sếp giao nhiệm vụ: xây một tính năng gửi email xác nhận mỗi khi khách hàng nạp tiền. Lượng giao dịch mỗi ngày chỉ vài trăm, nhưng vào ngày lễ có thể tăng vọt lên hàng chục nghìn. Nếu bạn dựng một server chạy 24/7 để làm việc này, bạn sẽ trả tiền cho một cỗ máy ngồi không suốt 23 tiếng mỗi ngày, rồi lại quá tải đúng lúc cao điểm. Đây chính là bài toán mà Serverless sinh ra để giải quyết.
Serverless — cụ thể là các nền tảng Function-as-a-Service (FaaS) như AWS Lambda, Google Cloud Functions, Azure Functions — cho phép bạn viết một đoạn code, đẩy lên cloud, và nhà cung cấp lo hết phần còn lại: máy chủ ở đâu, bao nhiêu instance, khi nào scale. Bạn chỉ trả tiền cho đúng số mili-giây code của bạn thực sự chạy.
Nhưng đừng để chữ "serverless" đánh lừa. Vẫn có server — chỉ là bạn không phải quản lý chúng. Và như mọi quyết định kiến trúc, serverless đi kèm những trade-off rất thật. Bài này giúp bạn hiểu bản chất, biết khi nào serverless là lựa chọn thông minh và khi nào nó là cái bẫy đắt đỏ. Là người ra quyết định kiến trúc, đây là một trong những công cụ bạn phải nắm vững để cân nhắc bên cạnh container (Bài 46) hay VM truyền thống.
Khái niệm cốt lõi
Serverless thực chất là gì
Serverless là mô hình thực thi trong đó nhà cung cấp cloud quản lý toàn bộ hạ tầng chạy code, tự động cấp phát và thu hồi tài nguyên theo nhu cầu thực tế. Có hai nhánh chính:
- FaaS (Function-as-a-Service): bạn viết những hàm nhỏ, độc lập, được kích hoạt bởi sự kiện (event). Ví dụ: AWS Lambda, Google Cloud Functions, Azure Functions, Cloudflare Workers.
- BaaS (Backend-as-a-Service): các dịch vụ backend được quản lý hoàn toàn như database, authentication, storage. Ví dụ: Firebase, Supabase, DynamoDB.
Mô hình thực thi theo sự kiện
Điểm mấu chốt của FaaS là event-driven. Một function không "chạy sẵn" chờ request. Nó chỉ được đánh thức khi có một trigger:
- HTTP request qua API Gateway
- Một file mới upload lên S3 hoặc Cloud Storage
- Một message vào hàng đợi (SQS, Pub/Sub)
- Một sự kiện theo lịch (cron)
- Một bản ghi thay đổi trong database (DynamoDB Streams)
Bốn ưu điểm lớn
Không quản lý server. Bạn không cần vá lỗi bảo mật OS, không cấu hình load balancer, không lo ổ đĩa đầy. Đội ngũ nhỏ có thể tập trung 100% vào code nghiệp vụ. Với một startup 3-4 kỹ sư, đây là sự khác biệt giữa việc ra tính năng nhanh và việc chết chìm trong DevOps.
Trả tiền theo lần thực thi (pay-per-execution). Bạn trả theo số lần gọi và thời gian chạy (tính bằng GB-giây). Nếu function không chạy, bạn trả 0 đồng. Điều này lý tưởng cho workload không đều — lúc rảnh, lúc dồn dập.
Tự scale từ 0 đến vô cực. Không có request thì chạy 0 instance. Có 10.000 request đồng thời thì cloud tự tạo 10.000 môi trường thực thi. Bạn không phải cấu hình auto-scaling group, không phải đoán trước capacity.
Vòng lặp phát triển nhanh. Deploy một function chỉ mất vài giây. Không cần build image, không cần rolling update cả cụm. Thử nghiệm, sửa, deploy lại rất nhanh — hợp với giai đoạn tìm product-market fit.
Bốn nhược điểm phải cân nhắc
Cold start. Đây là nhược điểm nổi tiếng nhất. Khi function lâu không chạy, môi trường thực thi bị thu hồi. Request đầu tiên sau đó phải chờ cloud khởi tạo lại môi trường, nạp runtime, nạp code, kết nối dependency — có thể mất từ vài trăm mili-giây đến vài giây. Với Java hay .NET, cold start nặng hơn hẳn Node.js hay Python. Nếu ứng dụng của bạn nhạy cảm về độ trễ (như trang thanh toán), cold start có thể làm hỏng trải nghiệm.
Giới hạn thời gian chạy. AWS Lambda giới hạn tối đa 15 phút cho một lần thực thi. Cloud Functions cũng có giới hạn tương tự. Các tác vụ chạy dài — encode video 2 tiếng, train model ML — hoàn toàn không hợp với FaaS.
Vendor lock-in. Code Lambda gắn chặt với hệ sinh thái AWS (event format, IAM, API Gateway). Chuyển sang Google hay Azure không đơn giản. Bạn đánh đổi sự tự do lấy sự tiện lợi.
Khó debug và observe. Vì function ephemeral (sớm nở tối tàn) và phân tán, việc trace một request đi qua nhiều function trở nên phức tạp. Bạn phụ thuộc vào công cụ như CloudWatch, X-Ray, và phải chủ động thiết kế observability (liên hệ Bài 43).
Chi phí có thể đảo chiều
Điều nhiều người bỏ qua: serverless rẻ khi lưu lượng thấp hoặc không đều, nhưng đắt hơn hẳn khi lưu lượng cao và ổn định. Ở một ngưỡng nào đó, một cụm container chạy liên tục sẽ rẻ hơn nhiều so với trả tiền theo từng lần gọi. Đây là trade-off kinh tế cốt lõi bạn phải tính.
Tình huống thực tế
Ví dụ 1 — Startup giao đồ ăn xử lý ảnh món ăn
Một startup giao đồ ăn giả định tên NgonNgon ở Hà Nội cho phép nhà hàng tự upload ảnh món ăn. Mỗi ảnh cần được resize thành 3 kích cỡ (thumbnail, medium, full) và nén lại. Lưu lượng upload rất thất thường: buổi sáng nhà hàng cập nhật menu dồn dập, ban đêm gần như không có ai.
Đội kỹ thuật ban đầu dựng một server EC2 chạy service resize 24/7, tốn khoảng 70 USD/tháng cho một instance đủ mạnh, mà phần lớn thời gian CPU ngồi không. Họ chuyển sang Lambda: mỗi khi có ảnh lên S3, một Lambda tự động kích hoạt để resize. Kết quả: với khoảng 40.000 ảnh mỗi tháng, mỗi function chạy ~2 giây, chi phí Lambda rơi vào khoảng 4-5 USD/tháng, cộng thêm phần S3.
Bài học: workload theo sự kiện, thất thường, mỗi tác vụ ngắn và độc lập — đây là "sân nhà" hoàn hảo của serverless. Cold start ~500ms không quan trọng vì đây là xử lý nền bất đồng bộ, người dùng không đứng chờ.
Ví dụ 2 — Sàn thương mại điện tử và cái bẫy chi phí
Một sàn TMĐT khu vực Đông Nam Á (giả định tên ChoLon) quyết định xây toàn bộ API sản phẩm trên Lambda vì "nghe nói serverless rẻ và không phải quản server". Ban đầu ổn. Nhưng khi lưu lượng đạt mức ổn định khoảng 300 triệu request mỗi tháng, hóa đơn Lambda cộng API Gateway leo lên hơn 12.000 USD/tháng.
Đội kiến trúc tính lại: cũng lượng traffic đó, nếu chạy trên một cụm Kubernetes với vài node được reserve, chi phí chỉ khoảng 3.500-4.000 USD/tháng. Họ migrate các API "nóng" (traffic cao, ổn định) sang container, giữ Lambda cho các tác vụ nền và webhook thất thường. Hóa đơn giảm gần 60%.
Bài học: serverless không phải lúc nào cũng rẻ. Ở lưu lượng cao và đều, mô hình pay-per-execution trở thành gánh nặng. Quyết định đúng là kiến trúc lai (hybrid): dùng serverless cho workload thất thường, dùng container cho workload nóng và ổn định.
Ví dụ 3 — Ngân hàng số và bài toán cold start
Một ngân hàng số giả định tên VietPay muốn dùng Lambda cho API xác thực OTP ở luồng thanh toán. Khi test, họ phát hiện vào những khung giờ ít giao dịch (2-4 giờ sáng), request đầu tiên bị cold start mất tới 2,8 giây vì họ viết bằng Java Spring. Với luồng thanh toán, độ trễ này là không chấp nhận được — khách hàng tưởng giao dịch treo.
Họ xử lý bằng hai cách: (1) bật Provisioned Concurrency để luôn giữ sẵn một số instance ấm, và (2) viết lại function bằng Node.js để giảm thời gian khởi động. Cold start giảm xuống dưới 300ms. Tuy nhiên Provisioned Concurrency nghĩa là bạn trả tiền cho instance chờ sẵn — mất đi một phần lợi thế "trả tiền theo thực thi".
Bài học: với luồng nhạy cảm độ trễ, cold start là rủi ro thật. Có công cụ để giảm thiểu, nhưng chúng làm giảm chính lợi ích kinh tế của serverless. Phải cân nhắc kỹ trước khi đặt serverless vào critical path.
Hướng dẫn từng bước
Khi đứng trước một quyết định "có nên dùng serverless không", hãy đi qua các bước sau:
Bước 1 — Phân loại workload. Trả lời: Lưu lượng có thất thường không? Mỗi tác vụ có chạy dưới 15 phút không? Có được kích hoạt theo sự kiện không? Nếu cả ba đều "có", serverless là ứng viên mạnh.
Bước 2 — Đánh giá độ nhạy cảm về độ trễ. Function này có nằm trong critical path mà người dùng đứng chờ không? Nếu có, hãy tính đến cold start ngay từ đầu và cân nhắc runtime nhẹ (Node.js, Python, Go) thay vì Java/.NET.
Bước 3 — Ước lượng chi phí ở quy mô mục tiêu. Đừng chỉ tính chi phí hiện tại. Hãy tính hóa đơn ở mức traffic gấp 10, gấp 100 lần. Tìm "điểm hòa vốn" nơi container trở nên rẻ hơn. Đây là bước nhiều đội bỏ quên và trả giá đắt sau này.
Bước 4 — Kiểm tra ràng buộc kỹ thuật. Function có cần kết nối lâu dài (WebSocket, connection pool tới database)? FaaS xử lý connection pool kém vì mỗi instance là độc lập — dễ làm cạn kiệt kết nối database. Cân nhắc dùng proxy như RDS Proxy nếu bắt buộc.
Bước 5 — Đánh giá vendor lock-in. Bạn có chấp nhận gắn với một cloud không? Nếu cần đa cloud, cân nhắc Cloudflare Workers hoặc viết logic core tách biệt khỏi handler đặc thù của nhà cung cấp (áp dụng nguyên tắc Ports & Adapters ở Bài 14).
Bước 6 — Thiết kế observability từ đầu. Đặt structured logging, distributed tracing, và metrics ngay từ function đầu tiên. Đừng để đến khi sự cố mới nhận ra không trace nổi request.
Bước 7 — Ghi lại quyết định bằng ADR. Viết một Architecture Decision Record nêu rõ context, các lựa chọn (serverless vs container vs VM), và lý do chọn — như bạn đã học ở Bài 4.
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng serverless cho mọi thứ. "Serverless-first" một cách mù quáng dẫn đến hóa đơn phình to và kiến trúc rối. Serverless là một công cụ, không phải tôn giáo. Dùng đúng chỗ.
Lỗi 2 — Bỏ qua cold start cho đến production. Nhiều đội chỉ phát hiện cold start khi khách hàng phàn nàn. Hãy test độ trễ ở điều kiện function nguội ngay từ giai đoạn phát triển.
Lỗi 3 — Nhồi function quá to. Viết một Lambda 2000 dòng làm đủ thứ là phản mô hình. Function nên nhỏ, làm một việc. Nhưng cũng đừng chẻ quá nhỏ đến mức có hàng trăm function rời rạc không ai quản nổi.
Lỗi 4 — Quên giới hạn kết nối database. 5.000 Lambda đồng thời có thể mở 5.000 kết nối và giết chết database của bạn. Luôn có chiến lược quản lý connection.
Lỗi 5 — Không quản lý dependency và kích thước package. Package càng lớn, cold start càng lâu. Chỉ đóng gói thứ cần thiết, dùng layer để chia sẻ thư viện chung.
Mẹo: Với runtime nhạy cảm cold start, ưu tiên Node.js, Python, Go. Dùng Provisioned Concurrency (hoặc min instances trên Cloud Functions) cho các function critical. Đặt timeout và memory hợp lý — trên Lambda, tăng memory cũng tăng CPU, đôi khi function chạy nhanh hơn và rẻ hơn dù bạn cấp nhiều memory hơn.
Mẹo: Giữ logic nghiệp vụ tách biệt khỏi handler. Handler chỉ nên là lớp mỏng nhận event, gọi logic core. Cách này giúp bạn test dễ và giảm lock-in.
Bài tập thực hành
Bài tập 1 — Phân loại workload. Với mỗi workload sau, hãy quyết định serverless hay container, và giải thích trong 2-3 câu: a) API tìm kiếm sản phẩm, 500 triệu request/tháng, traffic ổn định cả ngày. b) Gửi thông báo push khi có đơn hàng mới, khoảng 20.000 lần/ngày, rải rác. c) Xử lý báo cáo cuối tháng, chạy 40 phút mỗi lần, mỗi tháng một lần. d) Webhook nhận callback thanh toán từ đối tác, thất thường, cần phản hồi nhanh.
Bài tập 2 — Tính điểm hòa vốn. Giả sử một function chạy trung bình 200ms, cấp 512MB memory. Giá Lambda ~0,0000166667 USD/GB-giây cộng 0,20 USD/triệu request. Hãy ước lượng chi phí ở 1 triệu, 50 triệu và 500 triệu request/tháng. So sánh với một container 2 vCPU/4GB chạy liên tục giá ~60 USD/tháng (giả định xử lý được tải đó). Ở mức nào container bắt đầu rẻ hơn?
Bài tập 3 — Viết mini-ADR. Viết một ADR ngắn (khoảng 200 từ) cho quyết định: "Dùng serverless hay container cho service gửi email giao dịch của một sàn TMĐT vừa và nhỏ tại Việt Nam". Nêu context, các lựa chọn cân nhắc, quyết định, và hệ quả.
Bài tập 4 — Chống cold start. Liệt kê 3 kỹ thuật giảm cold start cho một function xác thực nằm trong luồng đăng nhập, và giải thích trade-off chi phí của mỗi kỹ thuật.
Tóm tắt
Serverless — cụ thể là FaaS như Lambda và Cloud Functions — là mô hình thực thi theo sự kiện, trong đó nhà cung cấp quản lý toàn bộ hạ tầng và bạn chỉ trả tiền cho đúng thời gian code chạy. Bốn ưu điểm lớn: không phải quản lý server, trả tiền theo lần thực thi, tự scale từ 0 đến vô cực, và vòng lặp phát triển nhanh. Bốn nhược điểm phải cân: cold start, giới hạn thời gian chạy, vendor lock-in, và độ khó trong debug/observe.
Bài học cốt lõi cho người ra quyết định kiến trúc: serverless không phải lúc nào cũng rẻ và cũng không phải mặc định cho mọi thứ. Nó tỏa sáng với workload thất thường, theo sự kiện, tác vụ ngắn và bất đồng bộ. Nó trở thành gánh nặng chi phí ở lưu lượng cao ổn định, và là rủi ro trong các luồng nhạy cảm độ trễ vì cold start. Kiến trúc thông minh thường là lai (hybrid): serverless cho phần thất thường, container cho phần nóng và ổn định.
Hãy luôn phân loại workload, ước lượng chi phí ở quy mô mục tiêu, kiểm tra ràng buộc về độ trễ và kết nối, rồi ghi lại quyết định bằng một ADR. Khi bạn chọn serverless vì đúng lý do — chứ không vì trào lưu — nó là một trong những công cụ mạnh nhất trong bộ công cụ kiến trúc của bạn.