Trong nhiều năm, “Serverless” đã được bán ra như là tương lai của phát triển phần mềm. Ý tưởng ban đầu thật hấp dẫn: không cần quản lý máy chủ vật lý hay máy ảo, chỉ cần viết code cho một hàm (function), triển khai nó, và để nền tảng tự động mở rộng (scale) khi cần thiết. AWS Lambda là người tiên phong, và sau đó hàng loạt các nhà cung cấp khác đã nối bước, từ Vercel, Cloudflare Workers cho đến Deno Deploy.
Lời hứa về sự đơn giản và hiệu quả chi phí đã tạo nên một làn sóng hào hứng trong cộng đồng phát triển. Tuy nhiên, nhìn lại bức tranh hiện tại, có vẻ như “giấc mơ serverless” như ban đầu đang dần lộ ra những vết nứt, đặc biệt khi áp dụng cho các ứng dụng phức tạp và có “trạng thái” (stateful).
Mục lục
Dấu Hiệu Từ Thị Trường: Deno Rút Lui Khỏi Mạng Lưới Toàn Cầu
Một trong những tín hiệu rõ ràng nhất cho thấy serverless không phải là viên đạn bạc cho mọi vấn đề đến từ Deno. Tuần trước, Deno đã âm thầm thông báo cắt giảm số lượng vùng (regions) mà Deno Deploy hoạt động, từ 35 xuống chỉ còn 6. Điều đáng ngạc nhiên là, theo báo cáo, hiệu suất thực tế lại được cải thiện.
Sự điều chỉnh này không có nghĩa là serverless đã chết, nhưng nó là một minh chứng cho thấy việc cố gắng “kéo căng” Functions-as-a-Service (FaaS) để biến nó thành một nền tảng ứng dụng đa năng dường như không hiệu quả như kỳ vọng. FaaS phát huy thế mạnh ở đâu và gặp hạn chế gì khi xây dựng các sản phẩm thực tế?
Lời Hứa Ban Đầu Của Serverless
Triết lý cơ bản của serverless rất đơn giản và mạnh mẽ:
- Không quản lý máy chủ: Nhà phát triển tập trung vào code thay vì cấu hình hạ tầng.
- Thanh toán theo mức sử dụng: Chỉ trả tiền cho thời gian code chạy.
- Tự động mở rộng: Nền tảng xử lý việc scale up/down dựa trên nhu cầu traffic.
Với những lợi ích này, serverless (đặc biệt là FaaS) rất phù hợp cho các trường hợp sử dụng đặc thù như:
- Webhooks xử lý sự kiện từ hệ thống khác.
- Các tác vụ theo lịch trình (scheduled tasks).
- Các công việc chạy nền (background jobs).
- Các API nhỏ, đơn giản (microservices).
- Xử lý lưu lượng truy cập đột biến (bursty traffic).
Tuy nhiên, khi bắt tay vào xây dựng các sản phẩm phức tạp hơn, những hạn chế của mô hình này nhanh chóng bộc lộ.
Ứng Dụng Thực Tế Không Chỉ Là “Không Trạng Thái” (Stateless)
Đa số các ứng dụng trong thế giới thực đều có những yêu cầu vượt ra ngoài mô hình FaaS thuần túy:
- Tương tác với Cơ sở dữ liệu: Hầu hết ứng dụng cần lưu trữ và truy xuất dữ liệu.
- Độ trễ thấp và ổn định: Người dùng mong đợi phản hồi nhanh chóng và nhất quán.
- Quản lý phiên & xác thực: Cần duy trì trạng thái người dùng qua các request.
- Các quy trình dài hạn hoặc đa bước: Logic nghiệp vụ phức tạp thường không thể gói gọn trong một hàm ngắn ngủi.
Cố gắng ép buộc các yêu cầu này vào một hàm không trạng thái, có thể được kích hoạt ở bất kỳ vùng địa lý nào, dẫn đến nhiều vấn đề:
Cold Starts: Thời gian chờ đợi khi hàm được khởi tạo lần đầu sau một thời gian không hoạt động.
Độ trễ không ổn định: Phụ thuộc vào vị trí của người dùng, vị trí của hàm được kích hoạt, và vị trí của cơ sở dữ liệu.
Giải pháp vòng vèo: Phải tìm cách lưu trữ trạng thái bên ngoài (ví dụ: Redis, S3) hoặc áp dụng các mẫu thiết kế phức tạp.
Ứng dụng của bạn không chỉ đơn thuần là một webhook nhận dữ liệu rồi xử lý. Bạn cần kiểm soát, bạn cần duy trì trạng thái, và bạn cần những thứ mà serverless thuần túy không làm tốt một cách tự nhiên.
Ví dụ về một hàm FaaS đơn giản:
exports.handler = async (event) => {
// Hàm này chỉ xử lý một request đơn giản, không cần state
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
So với một kịch bản cần truy cập DB và quản lý session (khó khăn hơn nhiều trong FaaS thuần):
// Kịch bản phức tạp hơn: cần kết nối DB, kiểm tra session...
// Việc quản lý kết nối DB và session state trong môi trường stateless FaaS là một thách thức lớn
exports.handler = async (event) => {
// Kết nối DB (có thể bị chậm, quản lý pool kết nối khó)
// const dbClient = await connectToDatabase();
// Lấy thông tin session (cần external storage)
// const session = await getSession(event.headers.cookie);
// Xử lý nghiệp vụ dựa trên state và dữ liệu DB
// ... logic phức tạp ...
// Đóng kết nối DB (quan trọng nhưng dễ quên trong FaaS ngắn hạn)
// await dbClient.close();
const response = {
statusCode: 200,
body: JSON.stringify('Processed request with state and DB access'),
};
return response;
};
Sự Điều Chỉnh Của Các Nền Tảng: Trở Về Với Những Điều Cơ Bản?
Việc Deno giảm số lượng vùng là một minh chứng rõ ràng cho vấn đề này. Họ thu hẹp phạm vi vì nhận ra rằng điện toán biên (edge compute) không thực sự giúp ích cho đa số trường hợp sử dụng. Hầu hết ứng dụng đều cần gọi đến cơ sở dữ liệu, vốn thường chỉ nằm cố định ở một hoặc một vài vùng.
Việc kích hoạt hàm ở một vùng “lạnh” (cold region) xa cơ sở dữ liệu gây ra độ trễ cao. Đôi khi, chuyển hướng request đến một vùng “ấm” (warm region) thậm chí còn xa hơn về mặt địa lý nhưng gần cơ sở dữ liệu lại nhanh hơn.
Do đó, Deno đang dịch chuyển. Thay vì cố gắng trở thành một nền tảng FaaS có mặt ở khắp mọi nơi cùng lúc, họ đang hướng tới một nền tảng lưu trữ ứng dụng hoàn chỉnh hơn. Họ đang bổ sung các tính năng như:
- Pinning theo vùng (Region pinning): Gắn ứng dụng vào một vùng cụ thể.
- Lưu trữ KV (Key-Value storage).
- Quản lý trạng thái và tính toán kết hợp (phong cách Durable Objects của Cloudflare).
- Tác vụ chạy nền (Background tasks).
- Các quy trình con (Subprocesses).
- Các quy trình build (Build pipelines).
Nói cách khác, họ đang dần tái tạo lại những gì mà máy chủ và container đã cung cấp trong nhiều năm qua.
Đây Không Phải Lỗi Của Serverless, Mà Là Lỗi Sử Dụng Sai Cách
Cần làm rõ: serverless không hề tệ. Vấn đề nằm ở việc sử dụng nó không đúng mục đích.
Trong các trường hợp stateless, xử lý logic gần người dùng, hoặc chỉ cần một request upstream duy nhất (không truy cập DB phức tạp), serverless vẫn là một công cụ tuyệt vời. Nó nhẹ, nhanh và có thể scale về 0 (không tốn chi phí khi không chạy). Đây là nơi serverless phát huy thế mạnh.
Tuy nhiên, ngay khi bạn bắt đầu thêm vào các lớp phức tạp như quản lý trạng thái, tác vụ nền, hoặc yêu cầu nhận biết vùng địa lý, bạn không còn chỉ viết một hàm nữa — bạn đang xây dựng một ứng dụng đầy đủ. Và ngay khi bạn cố gắng xây dựng một ứng dụng trên mô hình FaaS thuần túy, bạn sẽ gặp phải những giới hạn đáng kể.
Các Nền Tảng Serverless Đang Âm Thầm Tái Tạo Lại Máy Chủ
Xu hướng này không chỉ xảy ra với Deno:
- Cloudflare đã có KV, R2 (lưu trữ đối tượng), Durable Objects (quản lý trạng thái và tính toán), và cơ sở dữ liệu riêng (D1).
- Vercel gần đây đã giới thiệu Fluid Compute, cho phép nhiều request được xử lý trong cùng một instance Lambda để giảm thiểu overhead của cold start.
Tất cả các nền tảng này đều đang xây dựng lại những chức năng cốt lõi đã làm cho máy chủ và container trở nên hữu ích. Nhưng giờ đây, chúng được ẩn sau các lớp công cụ độc quyền của từng nhà cung cấp.
Cái Bẫy Khóa Chặt (Vendor Lock-In)
Một vấn đề khác nảy sinh từ xu hướng này là sự phụ thuộc vào nhà cung cấp (vendor lock-in). Các nền tảng serverless đang giới thiệu ngày càng nhiều dịch vụ độc quyền chỉ hoạt động tốt (hoặc chỉ hoạt động) trên hệ sinh thái của họ:
- KV, Durable Objects, Queues của Cloudflare.
- Các dịch vụ lưu trữ và tính toán có trạng thái sắp ra mắt của Deno.
- Các tính năng edge và Fluid Compute của Vercel.
Hầu hết các dịch vụ này đều là độc quyền (proprietary) hoặc “open core” ở mức tốt nhất. Bạn không thể tự cài đặt và vận hành chúng (self-host). Ngay cả khi chúng là mã nguồn mở về mặt kỹ thuật, chúng vẫn dựa vào quy mô hạ tầng mà chỉ các nền tảng này mới có thể cung cấp.
FaaS ban đầu được cho là sẽ giải phóng bạn khỏi việc quản lý hạ tầng. Thay vào đó, bạn giờ đây lại bị ràng buộc chặt chẽ vào cách mà một nhà cung cấp cụ thể định nghĩa về cách ứng dụng nên hoạt động.
Đa Số Ứng Dụng Không Cần Mạng Lưới Toàn Cầu Ngay Từ Đầu
Một sự thật thường bị bỏ qua là: phần lớn các ứng dụng hoàn toàn có thể hoạt động tốt chỉ trong một vùng địa lý duy nhất.
Việc ép buộc phân phối toàn cầu ngay từ đầu thêm vào sự phức tạp mà không mang lại nhiều lợi ích tương xứng. Bạn đột nhiên phải đối mặt với các vấn đề về nhất quán dữ liệu (consistency), sao chép (replication), đánh đổi về độ trễ (latency trade-offs), và các thách thức từ Định lý CAP mà không thực sự cần thiết.
Chúng ta đã chứng kiến những nỗ lực làm cho cơ sở dữ liệu hoạt động liền mạch trên nhiều vùng, nhưng hầu hết đều có những góc khuất. Giao dịch (transactions), chuyển đổi dự phòng (failover), và nhất quán dữ liệu trở nên khó khăn hơn ngay khi bạn vượt ra khỏi một vùng đơn lẻ.
Trừ khi bạn thực sự cần phân phối toàn cầu (ví dụ: ứng dụng cần độ trễ cực thấp cho người dùng khắp nơi), bạn nên bắt đầu và mở rộng trong một vùng duy nhất trước khi nghĩ đến việc scale out ra toàn cầu — nếu thực sự cần thiết. Hầu hết các ứng dụng không bao giờ đạt đến điểm đó.
Serverless Có Những Giới Hạn Cứng
Có những giới hạn cố hữu của mô hình FaaS mà không bộc lộ cho đến khi ứng dụng của bạn phát triển:
- Giới hạn thời gian thực thi: Mỗi hàm chỉ được chạy trong một khoảng thời gian nhất định (ví dụ: vài giây đến vài phút), thay đổi tùy nền tảng.
- Giới hạn bộ nhớ: Lượng RAM tối đa cho mỗi lần thực thi request.
- Các vấn đề về đồng thời: Việc chia sẻ trạng thái hoặc tài nguyên giữa các lần gọi hàm khác nhau rất phức tạp và tiềm ẩn lỗi.
- Cold Starts: Vẫn là vấn đề ảnh hưởng đến hiệu suất của request đầu tiên sau một thời gian không hoạt động.
- Giới hạn kích thước hàm và quirks khi triển khai: Codebase lớn có thể gặp vấn đề.
- Khả năng tương thích Node.js không đầy đủ: Đặc biệt là trên các nền tảng như Cloudflare Workers, một số package Node.js có thể không hoạt động hoặc yêu cầu polyfills.
Ví dụ, trên Cloudflare Workers, bạn có thể import một package trong môi trường phát triển, triển khai nó, và phát hiện ra rằng nó âm thầm lỗi hoặc ném ra các lỗi khó hiểu. Khi đó, bạn phải tìm kiếm polyfills hoặc viết lại logic chỉ để làm cho nó hoạt động. Những điểm ma sát này cộng dồn lại. Điều ban đầu là “quản lý ít hơn” nhanh chóng biến thành “debug khó hơn”.
Kết Luận: Serverless Là Công Cụ Chuyên Dụng, Không Phải Nền Tảng
Serverless chưa chết. Nó chỉ đang co lại về đúng vai trò mà lẽ ra nó phải luôn là: một **công cụ chuyên dụng**.
Hãy sử dụng nó cho:
- Các API nhanh, đơn giản.
- Các tác vụ chạy nền (background jobs).
- Các tác vụ theo lịch trình (scheduled tasks).
- Điện toán biên không trạng thái (stateless edge compute) xử lý logic gần người dùng.
Nhưng đối với các sản phẩm đầy đủ, phức tạp và có trạng thái? Hãy tuân thủ những gì đã được chứng minh là hiệu quả:
- Container (Docker, Kubernetes).
- Máy chủ ứng dụng truyền thống.
- Thiết lập trong một vùng địa lý duy nhất.
- Hạ tầng dự đoán được và dễ kiểm soát.
Việc cố gắng ép buộc mọi workload vào mô hình serverless chỉ dẫn đến sự phức tạp không cần thiết, nợ kỹ thuật (tech debt), và một mớ hỗn độn các dịch vụ độc quyền khóa bạn chặt vào một nhà cung cấp.
Một Điều Cuối Cùng
Khi xây dựng các sản phẩm thực tế, việc lựa chọn kiến trúc phù hợp là cực kỳ quan trọng. Serverless, khi được sử dụng đúng cách, là một bổ sung mạnh mẽ cho bộ công cụ của nhà phát triển. Nó tuyệt vời cho các logic không trạng thái ở biên, các tác vụ tính toán nhanh, những thứ cần mở rộng theo lưu lượng đột biến.
Nhưng đối với các thành phần cốt lõi của sản phẩm, những phần cần quản lý trạng thái, tương tác liên tục với cơ sở dữ liệu, và yêu cầu hiệu suất ổn định, các giải pháp truyền thống như máy chủ và container vẫn thường là lựa chọn tối ưu hơn.
Serverless là một công cụ. Không phải là nền tảng cho mọi thứ.
Hãy sử dụng nó một cách thông minh.