Nội dung
- Caching?
- Caching levels
- Lý do cần caching?
- Caching Strategies
- Một số policies loại bỏ cache
- Thiết kế caching
- Caching với REDIS
- Kết luận
Caching?
Caching là quá trình backup bản sao data (truy cập thường xuyên) của database trong cache. Cache là nơi lưu trữ dữ liệu tạm thời với tốc độ truy cập nhanh hơn, caching cải thiện được latency, giảm tải cho server và database, giúp trải nghiệm người dùng tốt hơn, ...
Caching levels
Có 2 kiểu caching chính: client-side caching và server-side caching.
- Client-side caching
- Browser caching
- ...
- Server-side caching
- Edge caching / CDN
- Web Server caching
- Site Caching (Page caching)
- Application caching (API caching)
- Database caching
- ...
Client-side caching
Có nhiều dạng client-side caching khác nhau nhưng phổ biến là Browser caching.
Browser caching: Trình duyệt sẽ cache các nội dung tĩnh dựa trên HTTP headers, những truy cập sau này dữ liệu sẽ được lấy từ cache (local) thay vì tải lại từ server, dưới đây là một số HTTP response headers dùng để kiểm soát cache.
Expires: Chỉ ra thời gian cache hết hạn, sau khảng thời gian đó resource sẽ được tải lại từ server.
Expires: Thu, 03 Dec 2020 23:02:37 GMT
Cache-Control: chứa các chỉ thị (directives) cho cache.
Cache-Control: max-age=3600
Last-Modified: Cho biết thời điểm resource được thay đổi lần cuối cùng, server trả về resource kèm tham số Last-Modified
trong response header, ở lần request tiếp theo client gửi request chứa tham số này lên server, nếu nội dung đã thay đổi server sẽ trả về resource mới, ngược lại trả về status 304 Not Modified
Last-Modified: Mon, 07 Dec 2020 16:21:06 GMT
ETag (entity tag): Là một chuỗi định danh cho resource do server trả về, client sẽ gửi chuỗi này lên server cho lần request tiếp theo, nếu nội dung resource đã được thay đổi, nó sẽ được tải lại, ngược lại server sẽ trả về status 304 Not Modified
ETag: "abcd1234567n34jv"
Browser caching diễn ra tại trình duyệt phía người dùng, chúng ta không kiểm soát được, nhưng có thể kiểm soát được ở Server-side.
Server-side caching
CDN (Content Delivery Network): được sử dụng để cache dữ liệu tĩnh (HTML, CSS, JavaScript, image, video, ...) trong các distributed server theo vị trí địa lý để giúp truyền dữ liệu nhanh hơn đến phía người dùng.
Web Server caching: Web Server (Apache, NGINX, ...) sẽ cache lại các requests sau đó trả về response mà không cần phải tương tác trực tiếp với app.
Site caching: Tạm thời cache lại nội dung như: Web page, images, media content khi web được load lần đầu tiên, những lần sau đó nội dung này sẽ không phải tải lại.
Application caching: Cache được đặt ở giữa application và datastores/databases, các dữ liệu cache này về cơ bản sẽ lưu trữ dưới dạng key: value
trong memory (RAM) nên sẽ nhanh hơn so với việc lấy trực tiếp từ Database (Disk).
Có hai cách cache với Application caching:
-
Database Queries Caching:
- Cách này được sử dụng phổ biến nhất, bất cứ khi nào thực hiện query, chúng ta sẽ lưu lại một hashed version kết quả query ở cache dưới dạng
key: value
, mỗi khi thực hiện query thì sẽ kiểm tra xem cache đã tồn tại hay chưa, nếu có sẽ trả về data được cache, nếu chưa thì sẽ lưu lại để sử dụng cho những request sau này.
Vấn đề gây khó khăn ở đây là cách vô hiệu hoá cache cũ khi thực hiện các queries phức tạp hay hay khi có thay đổi nhỏ như một row table trong database thì cũng sẽ phải cập nhật lại toàn bộ dữ liệu đã cache.
- Cách này được sử dụng phổ biến nhất, bất cứ khi nào thực hiện query, chúng ta sẽ lưu lại một hashed version kết quả query ở cache dưới dạng
- Objects Caching:
- Data cần cache ở đây là một Object, khi có sự thay đổi chúng ta có thể loại bỏ object dễ dàng.
Ví dụ như: user sessions, data rendering ...
- Data cần cache ở đây là một Object, khi có sự thay đổi chúng ta có thể loại bỏ object dễ dàng.
Database caching: Mỗi database sẽ có một thuật toán riêng để tối ưu hoá caching, và cache phụ thuộc vào tuỳ trường hợp cụ thể.
Ngoài ra còn nhiều kiểu cache khác, trong bài viết này, chúng ta sẽ tìm hiểu về Application caching (API caching).
Lý do cần caching?
Khi đưa API vào sử dụng thực tế, sẽ có những lúc mà số lượng requests đồng thời (concurrent requests) tại một thời điểm tăng lên, khi đó chúng ta sẽ phải đối mặt với một số vấn đề.
- Database sẽ tốn nhiều thời gian hơn để phản hồi.
- CPU tăng đột biến trong 1 thời điểm.
- Cạn kiệt nguồn tài nguyên: CPU, Memory, ...
- Thời gian phản hồi của Server không nhất quán vì phụ thuộc vào số lượng concurrent requests.
Thường thì sẽ có một số cách giải quyết như sau:
Horizontal Scaling (Scaling out) - Nâng cao hiệu suất server: Dùng server cấu hình cao để nâng cao tốc độ xử lý.
Vertical Scaling (Scaling up) - Tăng số lượng server: Tăng số lượng server để tăng khả năng xử lý song song.
Đối với Vertical Scalability: Bằng cách nâng cấp resources (CPU, RAM, HDD, SSD) trên một machine/server sẽ làm tăng khả năng xử lí được nhiều requests hơn, nhưng việc nâng cấp cũng có giới hạn nhất định.
Trong hầu hết các trường hợp, Horizontal scaling có thể giải quyết những vấn đề này. Horizontal scaling tăng số lượng servers để xử lý được nhiều traffics hơn. Tuy nhiên, sẽ có lúc nào đó số lượng requests đạt đến đỉnh điểm mà database sẽ không thể xử lý hết được chúng.
Đối với API caching, có nhiều cách tối ưu hóa để giải quyết các vấn đề đó. Ví dụ như pagination để giảm số lượng records từ database, cache lại dữ liệu đối với những requests giống nhau, database sharding,...
Một số ví dụ khác về các trường hợp cần cache:
- Blog có số lượng truy cập nhiều tại một thời điểm.
- Một service cần cập nhật dữ liệu realtime.
- Dữ liệu có tần suất sử dụng nhiều (ví dụ: User data, Session).
- Dữ liệu query trả về luôn giống nhau (ví dụ: search).
Caching Strategies
Caching là một chủ đề rộng lớn, có rất nhiều cách thực hiện khác nhau, những cách tạo và lưu trữ cache phụ thuộc vào dữ liệu và các kiểu truy cập, Trong phần này, chúng ta sẽ tìm hiểu một số cách cache phổ biến và những ưu nhược điểm của chúng.
Cache lifetime and expiry
Cache sử dụng TTL (Time To Live)
Đây là cách được sử dụng phổ biến nhất, khi dữ liệu thường xuyên được cập nhật và chúng ta muốn cache tự động hết hạn sau một khoảng thời gian.
Response headers sẽ trả về sẽ chứa Cache-Control
vớimax-age
là thời gian (giây) cache hết hạn.
Cache-Control: max-age=86400
Cache không sử dụng TTL
Được sử dụng trong trường hợp không cần cập nhật dữ liệu thường xuyên, các web tĩnh như blog thường sử dụng cách này.
Cache aside (lazy loading)
Cache-aside sẽ cập nhật cache một cách bất đồng bộ thông qua Application.
Khi một request thực hiện lấy dữ liệu, đầu tiên, Application kiểm tra xem dữ liệu có tồn tại trong cache hay chưa. Nếu đã tồn tại thì nó sẽ lấy từ cache (cache-hit) và trả về response. Ngược lại, sẽ lấy từ database sau đó trả về response và đồng thời lưu dữ liệu vào cache.
Các requests tương tự sau này sẽ lấy dữ liệu trực tiếp từ cache.
Cache-aside dễ thực hiện nhưng khó vô hiệu hoá cache cũ. Bất cứ khi nào dữ liệu trong database được cập nhật, chúng ta cần kiểm tra và cập nhật cache mới, việc này rất khó và tốn kém trong trường hợp sử dụng nhiều database để cập nhật dữ liệu (ví dụ: dữ liệu cập nhật từ các cửa hàng).
Read through cache
Read through cache tương tự như cache-aside, sự khác biệt duy nhất là dữ liệu sẽ luôn được lấy từ cache.
Đầu tiên, App kiểm tra xem dữ liệu có tồn tại trong cache hay chưa. Nếu có, lấy từ cache và gửi trả về response . Nếu không, cache sẽ được cập nhật từ database, sau đó sẽ gửi trả về response.
Read through cache gặp cùng vấn đề với cache-aside, nếu database được cập nhật từ nhiều nguồn, dữ liệu cache sẽ không được cập nhật mới nhất.
Cache-aside và Read through cache chủ yếu được sử dụng trên các web có lượng truy cập (read) nhiều. Vấn đề vô hiệu cache có thể được giải quyết bằng cách sử dụng Write through cache.
Write through cache
Dữ liệu mới sẽ được cập nhật vào cache và sau đó vào database/datastore một cách đồng bộ (synchronously).
Write through cache chỉ giải quyết vấn đề ghi (write) dữ liệu như add, update một đối tượng trong cache, cần kết hợp với read through cache để đảm bảo cache luôn là mới nhất.
Write through cache sẽ không làm mất dữ liệu cache trong một số trường hợp như: crash, power failure, ... Tuy nhiên sẽ làm tăng write-latency.
Write behind cache (Write back)
Application sẽ ghi dữ liệu vào cache và trả về response, sau đó sẽ lưu vào database sau (asynchronously) phụ thuộc vào số lượng requests.
Cách này giống như write through cache, nhưng không xác nhận rằng liệu dữ liệu đã được lưu hay không.
Đầu tiên, dữ liệu sẽ được ghi vào cache, sau đó những dữ liệu này sẽ được đẩy vào hàng đợi (Queue) để database xử lí sau. Bằng cách này, database có thể xử lý được số lượng record lớn, và không ảnh hưởng đến performance.
Tuy nhiên dữ liệu có thể bị mất mát trong quá trình lưu do lỗi, hay cache bị hỏng trước khi lưu vào database, do đó cần có thêm bản sao cache để dự phòng.
Write behind cache nên được kết hợp với read through cache để giải đảm bảo cache luôn được cập nhật mới nhất. Hầu hết relational database có hỗ trợ write behind và read through cache.
Refresh ahead cache
Refresh ahead cache được sử dụng để refresh dữ liệu trước khi cache hết hạn.
Cách này được sử dụng nhiều trên các real-time websites, ví dụ như: live sports, stock market. Dữ liệu sẽ đọc từ cache, cache sẽ refresh để cập nhật dữ liệu mới nhất từ database trước khi hết hạn.
Một số policies loại bỏ cache
Dưới đây là một số policies loại bỏ cache phổ biến:
- First In First Out (FIFO): Loại bỏ dữ liệu cache được truy cập đầu tiên, không quan tâm đến tần suất hoặc số lần đã được truy cập trước đó.
- Last In First Out (LIFO): Xoá dữ liệu cache được truy cập gần đây nhất, không quan tâm đến tần suất hoặc số lần đã được truy cập trước đó.
- Least Recently Used (LRU): Loại bỏ các dữ liệu cache ít được sử dụng gần đây nhất.
- Most Recently Used (MRU): Trái ngược với LRU, loại bỏ các dữ liệu cache được sử dụng gần đây nhất trước tiên.
- Least Frequently Used (LFU): Đếm tần suất truy cập của một item, cái ít được sử dụng nhất sẽ bị xoá.
- Random Replacement (RR): Chọn ngẫu nhiên và xoá một item để giải phóng không gian khi cần thiết.
Thiết kế caching
Trước khi đi vào thiết kế caching, chúng ta sẽ cần quan tâm đến một số vấn đề như sau:
- Liệt kê các requests cần cache (dựa theo tần suất, ...), có thể sử dụng JMeter
- Dung lượng data cần để cache ?
- Kiểu data cần cache ?
- Xác định số lượng QPS (Queries per second) của system ?
- Sử dụng caching strategies nào ?
- Áp dụng policies nào để loại bỏ cache ?
- Latency có quan trọng hay không ?
- Trường hợp cache bị miss hay die, DB chưa được cập nhật, dữ liệu trả về sẽ không chính xác ?
Cách đặt caching key
Đây là yếu tố cực kì quan trọng trong việc thết kế caching, vì liên quan đến việc vô hiệu hoá cache sau này. Việc đặt key dựa vào thông tin cần filter, càng nhiều filters phức tạp thì việc xử lý dynamic key sẽ trở nên khó hơn.
Giả sử có request như sau:
GET /contracts/1
GET /contracts?contract_code=abc-xyz
Caching key:
Caching:contracts/1
Caching:contracts/contract_code:abc-xyz
Để xử lí relational caching data, tham khảo thêm tại đây
Caching với REDIS
REDIS (Remote Dictionary Server) - Là cơ sở dữ liệu NoSQL, Redis hỗ trợ các kiểu dữ liệu như: strings, hashes, lists, sets, sorted, dữ liệu sẽ được lưu trữ dưới dạng key - value
trên RAM với tốc độ truy cập cực nhanh, ngoài ra có thể lưu trữ trên Disk để khôi phục dữ liệu khi gặp sự cố, có hỗ trợ Queue (hàng đợi),...
Kết luận
Caching là một chủ đề rộng lớn, và có rất nhiều cách triển khai, trong đó vô hiệu hoá cache và đặt caching key là hai vấn đề khó nhất khi thiết kế caching. Áp dụng caching sẽ giúp cải thiện trải nghiệm người dùng và góp phần giảm chi phí , tài nguyên đáng kể.
References
https://blog.logrocket.com/caching-strategies-to-speed-up-your-api/ https://dzone.com/articles/introducing-amp-assimilating-caching-quick-read-fo https://redis.io/topics/client-side-caching https://aws.amazon.com/caching/