Vấn đề với bộ nhớ của serverless - Deno Deploy

Vấn đề với bộ nhớ của serverless - Deno Deploy

Vấn đề

Hà Nội đã bước vào mùa thu, thời tiết không còn nắng nóng khắc nghiệt như ngày hè nữa mà thay vào đó là những cơn mưa xen kẽ. Mưa tắc đường, ngập lụt, còn hại biết bao nhiêu người. Ghét cái tiết trời thế này thật!

Hơn nửa tháng nay cơ thể tôi khá "mềm mỏng", pha một chút uể oải và rã rời. Nó không muốn phải vận động nhiều, kéo theo đó là cả phong độ đi xuống. Nhiều hôm đi làm về chỉ muốn nằm lì một chỗ, hoặc ngủ luôn cũng được. Sáng mai thức dậy cơn đau đầu thi thoảng còn ghé đến thăm, năng suất làm việc của ngày hôm đó phải giảm đi một nửa 🫣

Cách đây không lâu, trong bài viết Đập đi xây lại có đề cập đến việc viết lại blog từ Nuxt sang Fresh để giảm thiểu mã JavaScript và tăng tốc độ tải trang. Bên cạnh đó là chuyển nhà từ Cloudflare Worker sang Deno Deploy đơn giản vì Cloudflare chưa hỗ trợ Fresh. Quá trình diễn ra suôn sẻ và nhanh chóng vì đã có kinh nghiệm triển khai dự án lên serverless từ trước đó.

Deno Deploy vẫn còn khá mới. Thi thoảng tôi nhận được báo cáo trang web bị sập từ các công cụ theo dõi. Mà điều này thì chưa từng xảy ra ở Cloudflare. Thiết nghĩ đã trên serverless rồi mà còn bị dính downtime thì quả là một điều tối kỵ. Nhưng không sao, nó không diễn ra quá thường xuyên và thường được khắc phục rất nhanh sau đó.

Gần đây, đã có một số độc giả nhắn tin trực tiếp cho tôi phản ánh về một số bài viết không thể truy cập vào được. Sau khi kiểm tra thì có vẻ như nó đã sử dụng hết bộ nhớ. Mỗi khi truy cập, màn hình logs xuất hiện một thông báo "Memory limit exceeded, terminated". Hmm… điều này trước đến nay cũng chưa từng xảy ra ở Cloudflare.

Memory limit exceeded

Như các bạn biết, serverless có cách phân bổ bộ nhớ không giống với máy chủ truyền thống. Chúng ta không thể can thiệp được vào việc cấp phát tài nguyên mà chỉ được phân bổ một lượng nhất định từ nhà cung cấp. Ví dụ mỗi một request đến thì chỉ được xử lý trong 512Kb bộ nhớ cộng với 50ms xử lý của CPU chẳng hạn. Nếu vượt quá, bạn phải trả thêm tiền để nâng cấp hoặc phải nhận thông báo lỗi giống như tôi.

Vậy thì vấn đề nằm ở logic, có thể mình đang lấy quá nhiều dữ liệu hoặc tạo ra quá nhiều biến khiến cho bộ nhớ sử dụng bị tăng lên. Nhưng có một điều lạ là không phải bài viết nào cũng bị lỗi này. Sẽ có bài bị bài không bị và điểm chung của những bài bị lỗi là nội dung thường chứa nhiều block code của markdown.

Sau khi xem xét lại mã, tôi nghĩ rằng vấn đề nằm ở hàm convertMarkdownToHTML. Hàm này có chức năng chuyển đổi văn bản markdown thành HTML để cho trình duyệt hiển thị. Các bài viết đang được để ở văn bản markdown, và với mỗi lần đọc bài viết, convertMarkdownToHTML lại được gọi.

Bên trong convertMarkdownToHTML còn có thư viện showdown-highlight để áp dụng cú pháp "tô sáng" vào những nơi có sử dụng block code. Thay vì những dòng chữ đơn điệu màu đen thì khi áp dụng highlight vào, đoạn code sẽ trở nên có màu sắc hơn. showdown-highlight áp thêm một số đoạn mã html kèm các thẻ css để thêm màu sắc. Vì thế tôi nghĩ vấn đề nằm ở đây, block code càng nhiều thì lượng ký tự thêm vào càng nhiều. Nếu cắt bớt các đoạn block code này hoặc giảm thiểu cú pháp tô sáng xuống thì nhiều khả năng sẽ giảm được dung lượng bộ nhớ.

Sau khi áp dụng với 1-2 bài thì lỗi không còn xuất hiện nữa. Tôi càng tin suy đoán của mình là đúng. Nhưng chưa kịp thở phào nhẹ nhõm thì sau khi mở Google Console, thật bất ngờ khi thấy số lượng bài viết gặp gặp lỗi nhiều hơn rất nhiều so với con số mình biết. Nếu tiếp tục áp dụng cách trên thì bài viết sẽ dần bị mất "chất". Vì loại bỏ block code đồng nghĩa với chất lượng bài viết bị giảm đi đáng kể, viết về lập trình mà không "show code" thì nói ai tin!?

Hmm, chẵng lẽ việc lấy dữ liệu từ database ra thôi mà đã hết bộ nhớ rồi sao?

Tôi thậm chí còn xem lại xem việc dữ liệu lấy dữ liệu từ database có làm tăng kích thước bộ nhớ không. Vì ngoài hàm convertMarkdownToHTML ra thì vẫn còn nhiều đoạn mã lấy bài viết gợi ý, lấy bình luận, cùng nhiều biến được tạo ra để xử lý logic… Nếu nhiều như vậy thì có lẽ nào phải ngồi cân đo đong đếm từng biến để xem chúng đang chiếm bao nhiêu bộ nhớ sao?

Một bước ngoặt lớn khi tôi phát hiện ra một bài viết rất ít chữ cũng gặp phải tình trạng tương tự. Điều đó cho thấy vấn đề không phụ thuộc vào độ dài ngắn của nội dung bài viết mà có thể là do một đoạn mã nào đó đang sử dụng bộ nhớ nhiều hơn. Như vậy, phải có cách nào đó soi được quá trình cấp phát bộ nhớ trong của Deno thì mới xác định được vấn đề nằm ở đâu.

Dev Tools trong trình duyệt có một công cụ giúp chúng ta theo dõi được lượng bộ nhớ đã được phân bổ. Mở Dev Tools, vào "Memory" và nhìn vào tuỳ chọn "Allocation sampling", nó cho phép chúng ta quan sát được bộ nhớ được cấp phát trong quá trình chạy. Thật may mắn vì Deno vẫn sử dụng V8 nên bật debug thì sẽ sử dụng được tính năng này.

Dev Tools Memory

Tôi tìm cách bật debug trong Deno. Thật đơn giản, chỉ cần thêm cờ --inspect vào trước lệnh run thì quá trình debug rất giống với Node.js.

Tại đây, sau khi soi Chart thì đúng là hàm convertMarkdownToHTML đang sử dụng rất nhiều bộ nhớ, lên đến 86% tổng bộ nhớ trong phiên xử lý này. Khi so sánh với bài viết không gặp lỗi, thì con số chỉ ở mức hơn 20% một chút. Tiếp tục soi vào bên trong convertMarkdownToHTML thì hoá ra thư viện showdown cần nhiều bộ nhớ để parse markdown sang HTML. Cuối cùng tôi có thể kết luận rằng bài viết nào sử dụng thẻ markdown càng phức tạp thì khả năng lỗi càng cao.

Phân tích hàm parse markdown

Phân tích hàm parse markdown 2

Đến đây nhiều người nghĩ nên đổi sang thư viện khác. Nhưng tôi thì ngược lại, một phần vì đã mất công cấu hình thư viện này parse một số đoạn mã phức tạp theo cách mà mình muốn. Nếu đổi sang thư viện khác thì phải tìm cách parse lại nội dung giống như thế, tốn thời gian, cả chưa kể rủi ro về việc phá vỡ parse mã trong các bài viết khác.

Vậy cách khả thi nhất vẫn là gỡ service parse markdown hiện tại ra, không gọi hàm convertMarkdownToHTML mỗi khi render bài viết nữa. Thay vào đó là tạo thêm một cột lưu lại nội dung dạng HTML trong mỗi bài viết.

Chỉ với vài dòng code, tôi tách được hàm convertMarkdownToHTML sang một project sử dụng hono.dev và triển khai nó lên Cloudflare Worker. Sau đó thêm logic ở API tạo/cập nhật bài viết, chuyển hàm convertMarkdownToHTML thành một cuộc gọi API sang service mới, lưu lại nội dung HTML mới. Đồng thời bỏ hàm convertMarkdownToHTML cũ đi.

Mọi thứ đã hoạt động trở lại!

Tổng kết

Qua vấn đề này mới thấy tài nguyên trong serverless là hữu hạn, không được sử dụng thoả thích như trong máy chủ. Cần phải cẩn trọng trong khâu xử lý dữ liệu, mọi thứ cần phải được tối ưu nhất có thể nếu không muốn gặp phải phiền toái trong quá trình vận hành.

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (0)