Caching Frontend với Websocket và LocalStorage
Chào các bạn, hôm nay chúng ta sẽ đả động đến một thứ rất quen tai: caching. Caching là vấn đề muôn thủa và có lẽ là một trong số những việc đầu tiên cần làm khi tối ưu một hệ thống phần mềm nào đó. Mục tiêu chung của caching là giảm tối đa thời gian cần thiết để lấy dữ liệu, bằng cách lưu lại data ...
Chào các bạn, hôm nay chúng ta sẽ đả động đến một thứ rất quen tai: caching. Caching là vấn đề muôn thủa và có lẽ là một trong số những việc đầu tiên cần làm khi tối ưu một hệ thống phần mềm nào đó. Mục tiêu chung của caching là giảm tối đa thời gian cần thiết để lấy dữ liệu, bằng cách lưu lại data của các lần lấy trước đó (tránh lặp đi lặp lại việc lấy dữ liệu nhiều lần). Trong bài viết này, mình xin giới thiệu một cách cache dữ liệu ở phía browser (trình duyệt) kết hợp giữa websocket và localStorage.
(Bài viết này học hỏi từ bài viết tại bloghoctap của anh Võ Duy Tuấn, các bạn có thể tham khảo tại đây)
Thông thường chúng ta có hai nơi để cache dữ liệu: Backend và Frontend
Backend
Cách phổ biến nhất ở phía backend là sử dụng các in-memory database có tốc độ truy xuất đọc ghi cao như Memcached, Redis. Dữ liệu chỉ cần phải query lần đầu tiên trước khi được lưu vào bộ nhớ Cache. Ngoài ra định nghĩa cache trên HTTP Header, nén Gzip từ phía server cũng là các phương pháp hiệu quả và thường được nhắc đến nhằm giảm lưu lượng truyền tải dữ liệu. Tuy nhiên, caching phía backend thôi là không đủ.
Frontend
Mục tiêu khi caching phía frontend là giảm tối đa số lượng request cần gọi lên server để truy vấn dữ liệu. Với dữ liệu đã được cache phía backend vào Redis chẳng hạn, client vẫn cần khởi tạo một HTTP Request lên server, server phải gọi redis, lấy dữ liệu, trả về để client hiển thị => Nếu dữ liệu đã được lưu sẵn ở client thì tốc độ có thể tối ưu lên rất nhiều. Hiện nay, localStorage ở các trình duyệt đã khá phát triển và có thể lưu dữ liệu rất linh hoạt, nên việc lưu dữ liệu tại browser đã trở nên tiện lợi hơn rất nhiều.
Vấn đề
Tuy nhiên dữ liệu phía Frontend cần được cập nhật thường xuyên từ backend. Chưa kể đến với sự phát triển mạnh mẽ của các thư viện, framework frontend như Vue.js, React.js, các trang web thường rất hiếm khi reload lại trang (Single Page App). Để giải quyết việc cập nhật dữ liệu tại client, chúng ta sẽ sử dụng thêm Websocket để giải quyết.
Websocket có rất nhiều lựa chọn hiện nay, ví dụ như socket.io, Redis pub/sub ... hoặc các dịch vụ third party như Pusher. Trong bài viết này, mình sử dụng Opensource Poxa làm công cụ dựng một server websocket, thay thế cho Pusher. Chi tiết về Poxa, các bạn có thể theo dõi bài viết của mình tại đây.
Sau khi đã có websocket, mình sẽ sử dụng api localStorage để lưu trữ dữ liệu. Đối với các dữ liệu phức tạp, các bạn có thể sử dụng TaffyDB là một in-memory lưu trữ và tương tác dữ liệu gọn nhẹ ở browser.
Luồng
- Dữ liệu trên backend sẽ được lưu bao gồm data và version. Mỗi khi data thay đổi, version sẽ được đánh tăng lên một đơn vị. Ví dụ ban đầu khi mới khởi tạo version sẽ là 0, sau đó sẽ là 1, 2, 3 .... ứng với mỗi lần thay đổi. Các version này có thể lưu vào Redis cache.
- Lần đầu tiên khi lấy dữ liệu về, phía frontend sẽ lưu trữ lại version dữ liệu. Ví dụ với data là "kipalog", version sẽ là 0. Client sẽ subscribe một channel websocket để cập nhật dữ liệu.
- Khi phát sinh thay đổi dữ liệu, bên backend bắn qua websocket để client cập nhật lại dữ liệu và version.
- Khi reload lại trang, ứng dụng gọi API để lấy version của data. Nếu version không đổi thì sẽ không cần gọi API lấy dữ liệu. Nhờ đó, thay vì truy vấn lại data mỗi lần tải trang, mình có thể truy xuất thông qua version (đã được cache từ trước), hạn chế được việc truy vấn vào CSDL. Có một điểm tối ưu thêm là tận dụng presence-channel của Poxa (Pusher) để nhận biết user có đang online hệ thống hay không: Nếu user đang online hệ thống, ta thậm chí có thể không cần gọi API lấy version dữ liệu (Vì dữ liệu đã được cập nhật thường xuyên qua websocket). Khi user leave hệ thống thì mới cần gọi lại vào lần request sau.
Về cơ bản cách caching này giúp việc lưu trữ data tại browser linh hoạt hơn khi bổ sung websocket, có thể giảm số request để gọi lấy dữ liệu, đồng thời dữ liệu có thể được cập nhật realtime ngay khi có thay đổi.
Cám ơn các bạn đã đọc bài viết, mình sẽ sớm bổ sung demo và source code để minh họa cho luồng xử lý. Nếu có gì các bạn hãy comment để mình biết và cải thiện nhé.