1. Caching là gì?

Caching là kỹ thuật lưu tạm dữ liệu đã được tính toán hoặc truy xuất trước đó để có thể sử dụng lại nhanh hơn trong những lần truy cập tiếp theo. Giả sử một API lấy thông tin sản phẩm từ PostgreSQL. Mỗi request đều truy vấn database, chuyển đổi dữ liệu rồi trả về client. Nếu sản phẩm đó được hàng nghìn người xem trong thời gian ngắn, hệ thống có thể phải thực hiện hàng nghìn truy vấn tương tự. Thay vì truy vấn database mỗi lần, ứng dụng có thể lưu kết quả vào cache. Những request tiếp theo đọc dữ liệu từ cache cho đến khi dữ liệu hết hạn hoặc bị vô hiệu hóa.

2. Redis là gì?

Redis là một hệ thống lưu trữ dữ liệu trong bộ nhớ, hỗ trợ nhiều cấu trúc dữ liệu và các thao tác có độ trễ thấp. Redis thường được sử dụng cho:

  • Caching dữ liệu từ database.
  • Lưu session đăng nhập.
  • Rate limiting.
  • Đếm lượt truy cập.
  • Leaderboard và bảng xếp hạng.
  • Distributed locking trong những trường hợp phù hợp.
  • Pub/Sub và Redis Streams.
  • Lưu trạng thái tạm thời của các tác vụ.

Redis có thể giảm số lần truy vấn database và giảm độ trễ khi đọc dữ liệu đã được cache. Tuy nhiên, cache thêm một tầng vận hành và không tự giải quyết mọi vấn đề về hiệu năng.

Redis có phải database không?

Có. Redis là một hệ thống lưu trữ dữ liệu thực sự, không chỉ là thư viện cache.

Redis có thể được cấu hình để lưu dữ liệu xuống ổ đĩa thông qua các cơ chế persistence. Tuy vậy, khi sử dụng Redis làm cache, ứng dụng thường phải chấp nhận rằng dữ liệu cache có thể bị xóa, hết hạn hoặc mất sau sự cố tùy cấu hình.

3. Vì sao cần Caching?

Giảm độ trễ

Truy vấn database thường cần xử lý SQL, đọc index hoặc dữ liệu, sử dụng connection pool và truyền kết quả qua mạng.

Với dữ liệu đã có trong Redis, ứng dụng có thể bỏ qua phần lớn công việc đó và lấy kết quả trực tiếp từ cache.

Tuy nhiên, tốc độ thực tế phụ thuộc vào network, kích thước dữ liệu, serialization, tải hệ thống và cách triển khai. Không nên mặc định mọi thao tác Redis đều nhanh hơn mọi truy vấn database.

Giảm tải database

Giả sử một endpoint nhận 10.000 request trong một phút. Nếu phần lớn request đọc cùng một danh sách danh mục sản phẩm, cache có thể giúp tránh thực hiện lại cùng truy vấn trên database.

Điều này có thể giảm:

  • Số truy vấn đọc.
  • Sử dụng CPU trên database.
  • Áp lực lên connection pool.
  • Chi phí đọc dữ liệu.
  • Khả năng database bị quá tải khi traffic tăng đột biến.

Tăng khả năng phục vụ traffic

Khi database là điểm nghẽn, cache có thể giúp backend phục vụ nhiều request hơn mà không phải tăng tài nguyên database tương ứng.

Nhưng cache chỉ có lợi rõ rệt khi dữ liệu được truy cập lặp lại và cache hit rate đủ cao. Nếu mỗi request truy cập một dữ liệu hoàn toàn khác nhau, cache có thể làm tăng độ phức tạp mà không tiết kiệm được nhiều truy vấn.

4. TTL – Time To Live

TTL (Time To Live) là khoảng thời gian một key được phép tồn tại trước khi Redis xem nó là hết hạn.

Ví dụ:

SET product:101 '{"name":"Keyboard"}' EX 60

Key product:101 được đặt TTL 60 giây.

TTL giúp giới hạn thời gian dữ liệu cache có thể trở nên cũ. Khi TTL hết hạn, key không còn được xem là dữ liệu hợp lệ cho các lần truy cập tiếp theo.

TTL không bảo đảm dữ liệu luôn mới

Giả sử sản phẩm có giá 100. Backend cache giá trong 5 phút. Một quản trị viên đổi giá thành 120 trong database ngay sau khi cache được ghi.

Trong khoảng thời gian còn lại, ứng dụng có thể trả về giá 100 nếu chưa có cơ chế invalidation.

Vì vậy, TTL chỉ giới hạn thời gian tồn tại của cache entry; nó không tự bảo đảm dữ liệu luôn đồng bộ với database.

5. Cache-Aside Pattern – Mô hình cache phổ biến nhất

Cache-aside, còn gọi là lazy loading, là chiến lược phổ biến khi dùng Redis để cache dữ liệu từ database.

Nguyên tắc:

  1. Đọc Redis trước.
  2. Nếu có dữ liệu, trả về.
  3. Nếu không có, truy vấn database.
  4. Lưu kết quả vào Redis với TTL.
  5. Trả dữ liệu cho client.

Ưu điểm:

  • Chỉ cache dữ liệu thực sự được truy cập.
  • Không cần nạp toàn bộ database vào Redis.
  • Dễ tích hợp vào backend hiện có.
  • Cache có thể được xóa và tái tạo từ nguồn dữ liệu chính.

Nhược điểm:

  • Request đầu tiên sau khi cache miss phải truy vấn database.
  • Có thể phát sinh nhiều truy vấn đồng thời khi một key hết hạn.
  • Cần xử lý cache invalidation sau khi dữ liệu thay đổi.
  • Nếu cache và database không được đồng bộ đúng cách, có thể trả dữ liệu cũ.

6. Cache Stampede – Nhiều request cùng truy vấn database

Cache stampede xảy ra khi một cache entry phổ biến hết hạn hoặc bị xóa, khiến nhiều request đồng thời cache miss và cùng truy vấn database.

Ví dụ, 500 request cùng đọc product:101. Key hết hạn đúng lúc cả 500 request đến. Nếu mỗi request đều truy vấn database, cache có thể làm giảm tải bình thường nhưng lại tạo ra đột biến tải khi cache miss.

cách sử lý

Singleflight trong cùng một process: gom nhiều request cùng key để chỉ một request thực hiện truy vấn nguồn trong một thời điểm trên process đó. Các process khác vẫn có thể thực hiện truy vấn riêng.

Distributed lock: dùng Redis lock hoặc một cơ chế điều phối giữa nhiều instance để hạn chế việc cùng nạp một key. Cần thiết kế lease, timeout và cách xử lý lock hết hạn; không được giả định lock đơn giản luôn bảo đảm tính đúng đắn trong mọi sự cố.

TTL jitter: thêm một lượng ngẫu nhiên nhỏ vào TTL để nhiều key không đồng loạt hết hạn.

7. Tổng kết

Redis là hệ thống lưu trữ dữ liệu trong bộ nhớ với nhiều cấu trúc dữ liệu, còn caching là kỹ thuật tái sử dụng kết quả để giảm độ trễ và tải cho nguồn dữ liệu chính.

Bài viết khác

Redis & Caching là gì?

Redis & Caching là gì? Hướng dẫn sử dụng Redis trong Go 1. Caching là gì? Vì sao cần cache khi đã có database? Caching (bộ nhớ đệm) là kỹ thuật lưu tạm dữ liệu thường được truy cập để có thể lấy lại nhanh chóng khi cần, thay vì phải thực hiện lại toàn […]

Database Migration là gì?

1. Database Migration là gì? Database Migration là quá trình quản lý và áp dụng những thay đổi đối với cấu trúc hoặc dữ liệu trong cơ sở dữ liệu theo từng phiên bản của ứng dụng. Trong quá trình phát triển phần mềm, yêu cầu nghiệp vụ thường xuyên thay đổi. Một bảng có […]

Bun ORM là gì?

Bun ORM là gì? Hướng dẫn sử dụng Bun ORM với Go và PostgreSQL 1. Bun ORM là gì? Bun là một thư viện ORM dành cho ngôn ngữ Go, được thiết kế theo hướng SQL-first. Thay vì cố gắng che giấu hoàn toàn SQL phía sau các phương thức trừu tượng, Bun cho phép […]

Database Migration

1. Database Migration là gì? Database Migration là quá trình quản lý và áp dụng các thay đổi đối với cấu trúc hoặc dữ liệu trong database theo từng phiên bản có thể theo dõi và kiểm soát. Trong quá trình phát triển phần mềm, database hiếm khi giữ nguyên cấu trúc từ đầu đến […]

Saga Pattern

Saga Pattern là gì? Quản lý Transaction trong kiến trúc Microservices 1. Saga Pattern là gì? Saga Pattern là một mẫu thiết kế được sử dụng để quản lý các nghiệp vụ phân tán gồm nhiều bước xử lý, trong đó mỗi bước có thể thực hiện một local transaction tại một service hoặc Database […]

Two-Phase Locking (2PL)

Two-Phase Locking (2PL) là gì? Cơ chế khóa hai pha trong Database 1. Two-Phase Locking (2PL) là gì? Two-Phase Locking (2PL — khóa hai pha) là một giao thức quản lý khóa trong Database, quy định cách một transaction được phép lấy và giải phóng lock trong quá trình thực thi. Khi nhiều transaction cùng […]

Leave a Reply

Your email address will not be published. Required fields are marked *