Cache

Nguồn: Scalable system design patterns
Caching cải thiện thời gian tải trang và có thể giảm tải cho server và database của bạn. Trong mô hình này, dispatcher trước tiên sẽ tra xem request đã từng được thực hiện chưa và cố tìm kết quả trước đó để trả về, nhằm tránh phải thực thi lại.
Database thường được lợi khi các thao tác đọc và ghi được phân bố đều trên các partition của nó. Các mục phổ biến có thể làm lệch phân bố, gây ra nút thắt cổ chai. Đặt một cache phía trước database có thể giúp hấp thụ tải không đều và các đợt tăng đột biến traffic.
Client caching
Cache có thể nằm ở phía client (OS hoặc trình duyệt), phía server, hoặc trong một lớp cache riêng.
CDN caching
CDN được xem là một loại cache.
Web server caching
Reverse proxy và các cache như Varnish có thể phục vụ trực tiếp nội dung tĩnh và động. Web server cũng có thể cache request, trả về response mà không cần liên hệ application server.
Database caching
Database của bạn thường đã có sẵn một mức caching nhất định trong cấu hình mặc định, được tối ưu cho use case chung. Tinh chỉnh các thiết lập này theo mẫu sử dụng cụ thể có thể cải thiện thêm performance.
Application caching
Các in-memory cache như Memcached và Redis là key-value store nằm giữa ứng dụng và nơi lưu trữ dữ liệu của bạn. Vì dữ liệu nằm trong RAM nên nhanh hơn nhiều so với các database thông thường lưu dữ liệu trên đĩa. RAM hạn chế hơn đĩa, nên các thuật toán cache invalidation như least recently used (LRU) có thể giúp loại bỏ các entry 'cold' và giữ dữ liệu 'hot' trong RAM.
Redis có thêm các tính năng sau:
- Tuỳ chọn persistence
- Cấu trúc dữ liệu dựng sẵn như sorted set và list
Có nhiều mức bạn có thể cache, thuộc hai nhóm chung: database query và object:
- Mức row
- Mức query
- Object đã hoàn chỉnh có thể serialize
- HTML đã render đầy đủ
Nhìn chung, bạn nên tránh cache dựa trên file, vì nó khiến việc clone và auto-scaling khó hơn.
Caching ở mức database query
Mỗi khi truy vấn database, hãy hash query làm key và lưu kết quả vào cache. Cách này gặp vấn đề về hết hạn:
- Khó xoá một kết quả đã cache với các query phức tạp
- Nếu một phần dữ liệu thay đổi, chẳng hạn một ô trong bảng, bạn cần xoá mọi query đã cache có thể chứa ô bị thay đổi đó
Caching ở mức object
Hãy nhìn dữ liệu như một object, tương tự cách bạn làm trong code ứng dụng. Để ứng dụng tập hợp dataset từ database thành một instance của class hoặc một/nhiều cấu trúc dữ liệu:
- Xoá object khỏi cache nếu dữ liệu nền tảng của nó đã thay đổi
- Cho phép xử lý bất đồng bộ: các worker tập hợp object bằng cách lấy object mới nhất trong cache
Gợi ý những thứ nên cache:
- User session
- Trang web đã render đầy đủ
- Activity stream
- Dữ liệu user graph
Khi nào cập nhật cache
Vì chỉ lưu được một lượng dữ liệu giới hạn trong cache, bạn cần xác định chiến lược cập nhật cache nào phù hợp nhất với use case của mình.
Cache-aside

Nguồn: From cache to in-memory data grid
Ứng dụng chịu trách nhiệm đọc và ghi từ nơi lưu trữ. Cache không tương tác trực tiếp với nơi lưu trữ. Ứng dụng thực hiện như sau:
- Tìm entry trong cache, kết quả là cache miss
- Nạp entry từ database
- Thêm entry vào cache
- Trả về entry
def get_user(self, user_id):
user = cache.get("user.{0}", user_id)
if user is None:
user = db.query("SELECT * FROM users WHERE user_id = {0}", user_id)
if user is not None:
key = "user.{0}".format(user_id)
cache.set(key, json.dumps(user))
return userMemcached thường được dùng theo cách này.
Các lần đọc tiếp theo với dữ liệu đã thêm vào cache sẽ nhanh. Cache-aside còn được gọi là lazy loading. Chỉ dữ liệu được yêu cầu mới được cache, tránh làm đầy cache bằng dữ liệu không ai yêu cầu.
Nhược điểm: cache-aside
- Mỗi cache miss tốn ba lượt đi lại, có thể gây ra độ trễ đáng chú ý.
- Dữ liệu có thể bị cũ (stale) nếu được cập nhật trong database. Vấn đề này được giảm nhẹ bằng cách đặt time-to-live (TTL) để buộc cập nhật entry trong cache, hoặc bằng cách dùng write-through.
- Khi một node gặp sự cố, nó được thay bằng một node mới còn trống, làm tăng latency.
Write-through

Nguồn: Scalability, availability, stability, patterns
Ứng dụng dùng cache làm nơi lưu trữ dữ liệu chính, đọc và ghi dữ liệu vào đó, còn cache chịu trách nhiệm đọc và ghi xuống database:
- Ứng dụng thêm/cập nhật entry trong cache
- Cache ghi entry vào data store một cách đồng bộ
- Trả về
Code ứng dụng:
set_user(12345, {"foo":"bar"})Code cache:
def set_user(user_id, values):
user = db.query("UPDATE Users WHERE id = {0}", user_id, values)
cache.set(user_id, user)Write-through là thao tác chậm xét tổng thể do có thao tác ghi, nhưng các lần đọc tiếp theo đối với dữ liệu vừa ghi thì nhanh. Người dùng thường chấp nhận độ trễ khi cập nhật dữ liệu hơn là khi đọc dữ liệu. Dữ liệu trong cache không bị cũ.
Nhược điểm: write through
- Khi một node mới được tạo do sự cố hoặc do scale, node mới sẽ không cache entry nào cho đến khi entry đó được cập nhật trong database. Kết hợp cache-aside với write through có thể giảm nhẹ vấn đề này.
- Phần lớn dữ liệu được ghi có thể không bao giờ được đọc, điều này có thể giảm thiểu bằng TTL.
Write-behind (write-back)

Nguồn: Scalability, availability, stability, patterns
Với write-behind, ứng dụng thực hiện như sau:
- Thêm/cập nhật entry trong cache
- Ghi entry vào data store một cách bất đồng bộ, cải thiện performance ghi
Nhược điểm: write-behind
- Có thể mất dữ liệu nếu cache ngừng hoạt động trước khi nội dung của nó được ghi vào data store.
- Triển khai write-behind phức tạp hơn so với cache-aside hoặc write-through.
Refresh-ahead

Nguồn: From cache to in-memory data grid
Bạn có thể cấu hình cache tự động làm mới bất kỳ entry nào vừa được truy cập gần đây trước khi nó hết hạn.
Refresh-ahead có thể giảm latency so với read-through nếu cache dự đoán chính xác những mục nào có khả năng được cần đến trong tương lai.
Nhược điểm: refresh-ahead
- Nếu không dự đoán chính xác những mục nào có khả năng được cần đến trong tương lai, performance có thể thấp hơn so với khi không dùng refresh-ahead.
Nhược điểm: cache
- Cần duy trì tính nhất quán giữa cache và nguồn dữ liệu gốc như database thông qua cache invalidation.
- Cache invalidation là bài toán khó, có thêm độ phức tạp liên quan đến việc khi nào cần cập nhật cache.
- Cần thay đổi ứng dụng, chẳng hạn thêm Redis hoặc memcached.