Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 18 — Cache — ElastiCache, Memorystore, Redis

Đã đọc 0/15 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn đang vận hành một sàn thương mại điện tử vào ngày sale 12/12. Lượng truy cập tăng gấp 20 lần bình thường, hàng chục nghìn người cùng lúc mở trang chi tiết sản phẩm "iPhone giảm giá". Nếu mỗi lượt xem đều bắt cơ sở dữ liệu (database) truy vấn lại từ đầu, chỉ sau vài phút database của bạn sẽ nghẽn, thời gian phản hồi vọt lên vài giây, và khách hàng bỏ giỏ hàng hàng loạt.

Đây chính là bài toán mà cache (bộ nhớ đệm) sinh ra để giải quyết. Trong toàn bộ chuỗi bài về lưu trữ và cơ sở dữ liệu trên cloud, bài này tập trung riêng vào một lớp hạ tầng cực kỳ đặc biệt: lớp cache in-memory được quản lý (managed cache), với ba đại diện tiêu biểu là Amazon ElastiCache, Google Cloud Memorystore và engine phổ biến nhất đứng sau chúng — Redis.

Cache không phải là thứ "làm cho vui". Với các hệ thống đọc nhiều (read-heavy), cache thường là ranh giới giữa một sản phẩm chạy mượt và một sản phẩm liên tục sập. Là kỹ sư hạ tầng, bạn bắt buộc phải hiểu rõ khi nào cần cache, dùng nó thế nào cho đúng, và những cái bẫy chết người khi triển khai. Bài này sẽ trang bị cho bạn đúng những điều đó.

Khái niệm cốt lõi

Cache là gì và vì sao nó nhanh

Cache là một lớp lưu trữ tạm thời, giữ những dữ liệu hay được truy cập ở nơi có thể lấy ra cực nhanh. Điểm mấu chốt: cache thường lưu dữ liệu trong bộ nhớ RAM (in-memory), thay vì trên đĩa như database truyền thống.

Con số nói lên tất cả. Đọc một giá trị từ RAM mất khoảng dưới 1 mili-giây (ms). Trong khi đó, một truy vấn database có join, có index scan, có kết nối qua mạng thường tốn từ 10 đến 100 ms, thậm chí hơn khi tải cao. Chênh lệch 10–100 lần này, khi nhân với hàng triệu request mỗi ngày, tạo ra khác biệt khổng lồ về trải nghiệm người dùng và chi phí hạ tầng.

Ba lý do chính để dùng cache

1. Giảm tải cho database (giảm DB load). Đây là lý do quan trọng nhất với các workload đọc nhiều. Thay vì mỗi request đều "đánh" thẳng vào database, phần lớn request đọc được phục vụ từ cache. Database chỉ phải xử lý phần còn lại — thường là các thao tác ghi và những lần đọc "trượt cache" (cache miss). Một tỷ lệ cache hit 90% nghĩa là database của bạn chỉ còn phải gánh 10% lượng đọc.

2. Giảm độ trễ (giảm latency). Như đã nói, in-memory ~1 ms so với DB ~10–100 ms. Với những trang cần phản hồi tức thời — bảng xếp hạng game, giá sản phẩm, số lượng tồn kho — cache giúp trang tải gần như ngay lập tức.

3. Lưu session cho tầng web stateless. Trong kiến trúc web hiện đại, bạn chạy nhiều server web giống hệt nhau đằng sau load balancer. Các server này nên stateless (không giữ trạng thái riêng), để request của một người dùng có thể rơi vào bất kỳ server nào. Vậy thông tin đăng nhập, giỏ hàng tạm, token phiên lưu ở đâu? Câu trả lời phổ biến: lưu vào một cache chung như Redis. Mọi server web đều đọc/ghi session vào cùng một Redis, nên người dùng đăng nhập ở server A vẫn được nhận diện khi request tiếp theo rơi vào server B.

Redis — engine cache phổ biến nhất

Redis (Remote Dictionary Server) là một kho dữ liệu in-memory mã nguồn mở. Nó không chỉ lưu chuỗi key-value đơn giản mà còn hỗ trợ nhiều cấu trúc dữ liệu phong phú: list, set, sorted set (dùng làm bảng xếp hạng cực tốt), hash, bitmap, và cả pub/sub. Chính sự linh hoạt này khiến Redis thống trị thị trường cache. Một engine tương tự thường được nhắc kèm là Memcached — đơn giản hơn, chỉ key-value thuần, nhẹ và nhanh cho cache đơn giản, nhưng thiếu các tính năng nâng cao và khả năng bền vững (persistence) của Redis.

Managed cache — ElastiCache và Memorystore

Bạn hoàn toàn có thể tự cài Redis lên một VM và tự vận hành. Nhưng khi đó bạn phải tự lo: cài đặt, vá lỗi bảo mật, sao lưu, failover khi node chết, mở rộng cụm... Đó là lý do các nhà cung cấp cloud tung ra managed cache:

  • Amazon ElastiCache: dịch vụ cache được quản lý của AWS, hỗ trợ cả Redis (nay gọi là ElastiCache for Valkey/Redis OSS) và Memcached. AWS lo phần vá lỗi, sao lưu, giám sát, và có chế độ cluster để mở rộng.
  • Google Cloud Memorystore: tương đương của Google, hỗ trợ Redis, Valkey và Memcached, tích hợp sâu với VPC và IAM của GCP.
  • Bên Azure có Azure Cache for Redis đóng vai trò tương tự (dù không nằm trong tiêu đề bài, bạn nên biết để đối chiếu khi làm multi-cloud).
Điểm chung: bạn đánh đổi một chút quyền kiểm soát và chi phí cao hơn tự host, để lấy về sự an tâm vận hành. Với hầu hết đội ngũ tại Việt Nam, đánh đổi này gần như luôn xứng đáng.

Các mẫu (pattern) đưa dữ liệu vào cache

Hiểu pattern là hiểu bản chất dùng cache đúng cách:

  • Cache-aside (lazy loading): Phổ biến nhất. Ứng dụng đọc cache trước; nếu trượt (miss) thì đọc database, rồi ghi kết quả vào cache cho lần sau. Ưu điểm: chỉ cache thứ thật sự được dùng. Nhược: lần đọc đầu luôn chậm, và dễ gặp dữ liệu cũ nếu không quản lý tốt.
  • Write-through: Mỗi khi ghi vào database thì ghi luôn vào cache. Cache luôn mới, nhưng mọi thao tác ghi chậm hơn một chút.
  • TTL (Time To Live): Gắn thời hạn sống cho mỗi key. Sau TTL, key tự hết hạn và bị xóa. Đây là công cụ chính để tránh dữ liệu cache cũ tồn tại vĩnh viễn.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT ngày sale (bối cảnh Việt Nam)

Một công ty thương mại điện tử tại TP.HCM, tạm gọi là ShopViet, chạy trên AWS. Ngày thường trang chi tiết sản phẩm nhận khoảng 500 request/giây, database RDS PostgreSQL xử lý thoải mái. Nhưng ngày sale 12/12, lưu lượng nhảy lên 9.000 request/giây. Ban đầu chưa có cache, mỗi request đọc thông tin sản phẩm (giá, tồn kho, mô tả) đều truy vấn thẳng RDS. Kết quả: CPU của RDS chạm 100%, thời gian phản hồi từ 40 ms leo lên 3–4 giây, tỷ lệ bỏ giỏ hàng tăng vọt.

Đội kỹ sư triển khai ElastiCache for Redis với pattern cache-aside cho dữ liệu sản phẩm, đặt TTL 60 giây. Sau khi vận hành, tỷ lệ cache hit đạt 94%. Nghĩa là RDS chỉ còn nhận khoảng 540 request/giây thay vì 9.000. Thời gian phản hồi trung bình giảm về 12 ms. Chi phí thêm cho một cụm ElastiCache 2 node cỡ cache.r6g.large chỉ vài trăm đô mỗi tháng — rẻ hơn nhiều so với việc nâng cấp RDS lên bậc khổng lồ.

Bài học: Với workload đọc nhiều và dữ liệu ít thay đổi (thông tin sản phẩm cập nhật vài lần/ngày), cache-aside với TTL ngắn là "vũ khí" hiệu quả và rẻ nhất để chống nghẽn database.

Ví dụ 2 — Session cho web tier stateless (startup fintech Đông Nam Á)

Một startup ví điện tử tại Singapore mở rộng ra khu vực, chạy 12 server web đằng sau một Application Load Balancer trên GCP. Ban đầu họ lưu session người dùng trong bộ nhớ của từng server. Vấn đề xuất hiện ngay: khi load balancer phân phối request của cùng một người dùng sang server khác, người dùng bị "văng" ra khỏi phiên đăng nhập giữa chừng — cực kỳ nguy hiểm với một app tài chính.

Giải pháp: chuyển toàn bộ session sang Cloud Memorystore for Redis. Mọi server web đọc/ghi session vào một Redis chung, mỗi session gắn TTL 30 phút để tự hết hạn khi người dùng không hoạt động. Nhờ đó tầng web trở nên thực sự stateless: họ có thể tự do thêm/bớt server, autoscale theo tải, hay thay thế server hỏng mà người dùng không hề bị đăng xuất. Khi một node Redis chính gặp sự cố, Memorystore ở tier Standard tự động failover sang replica trong vài chục giây.

Bài học: Cache không chỉ để tăng tốc đọc dữ liệu. Nó còn là "bộ nhớ chung" giúp cả một tầng web trở nên stateless và co giãn tự do — điều kiện tiên quyết cho hệ thống mở rộng được.

Ví dụ 3 — Bảng xếp hạng game realtime

Một studio game mobile tại Việt Nam có tính năng bảng xếp hạng (leaderboard) cập nhật liên tục cho hàng triệu người chơi. Nếu tính top 100 bằng câu lệnh ORDER BY score trên database mỗi lần người chơi mở bảng xếp hạng, database sẽ chết vì phải sắp xếp hàng triệu dòng liên tục.

Họ dùng cấu trúc sorted set của Redis. Mỗi khi người chơi ghi điểm, hệ thống gọi ZADD cập nhật điểm vào sorted set. Lấy top 100 chỉ cần một lệnh ZREVRANGE chạy trong dưới 1 ms, bất kể có bao nhiêu người chơi. Database gốc chỉ dùng để lưu bền điểm số định kỳ, không phải phục vụ mỗi lượt xem bảng xếp hạng.

Bài học: Redis không chỉ là key-value. Các cấu trúc dữ liệu chuyên biệt như sorted set giải quyết trọn vẹn những bài toán mà database quan hệ làm rất tệ. Chọn đúng cấu trúc dữ liệu chính là chọn đúng công cụ.

Hướng dẫn từng bước

Dưới đây là quy trình thực tế để đưa một cache-aside dựa trên Redis vào một dịch vụ đọc nhiều:

  • Xác định dữ liệu đáng cache. Ưu tiên dữ liệu được đọc nhiều, thay đổi ít, và tốn kém khi truy vấn (nhiều join, tính toán nặng). Ví dụ: hồ sơ sản phẩm, cấu hình, kết quả tổng hợp. Tránh cache dữ liệu thay đổi từng giây mà phải luôn chính xác tuyệt đối như số dư tài khoản ngân hàng.
  • Chọn managed cache và cấu hình cụm. Trên AWS tạo một cụm ElastiCache for Redis; trên GCP tạo một instance Memorystore for Redis. Đặt cache trong cùng VPC với ứng dụng, chọn tier có replica (Standard/Multi-AZ) cho môi trường production để có failover.
  • Thiết kế key rõ ràng. Dùng quy ước đặt tên nhất quán, ví dụ product:12345 hay session:user:abc. Key rõ ràng giúp gỡ lỗi và tránh đụng độ.
  • Cài đặt logic cache-aside. Khi cần dữ liệu: đọc cache trước. Nếu có (hit) thì trả về ngay. Nếu không (miss) thì đọc database, ghi kết quả vào cache kèm TTL, rồi trả về.
  • Chọn TTL hợp lý. TTL ngắn (30–60 giây) cho dữ liệu cần tươi; dài hơn (vài phút đến vài giờ) cho dữ liệu ít đổi. TTL là lưới an toàn chống dữ liệu cũ.
  • Xử lý cập nhật (invalidation). Khi dữ liệu gốc thay đổi, hãy xóa hoặc ghi đè key tương ứng trong cache để tránh phục vụ dữ liệu cũ.
  • Giám sát các chỉ số then chốt. Theo dõi cache hit ratio, độ trễ, mức dùng bộ nhớ, số lần eviction (key bị đẩy ra do hết RAM) qua CloudWatch hoặc Cloud Monitoring. Hit ratio thấp bất thường là dấu hiệu chiến lược cache có vấn đề.

Lỗi thường gặp & mẹo

Cache stampede (bầy đàn cache). Khi một key nóng hết hạn cùng lúc, hàng nghìn request đồng loạt trượt cache và cùng đánh vào database — gây sập ngay chính lúc bạn cần cache nhất. Mẹo: dùng khóa (lock) để chỉ một request được phép nạp lại cache, hoặc thêm chút ngẫu nhiên (jitter) vào TTL để các key không hết hạn cùng lúc.

Dữ liệu cũ (stale data). Quên xóa cache khi dữ liệu gốc đổi khiến người dùng thấy giá cũ, tồn kho sai. Luôn có chiến lược invalidation rõ ràng và TTL làm lưới an toàn.

Cache toàn bộ mà không nghĩ. Không phải mọi thứ đều nên cache. Dữ liệu thay đổi liên tục hoặc chỉ đọc một lần khi cache vào chỉ tốn RAM mà chẳng lợi ích.

Coi cache là nguồn dữ liệu bền. Cache in-memory có thể mất dữ liệu khi node restart. Đừng bao giờ lưu dữ liệu quan trọng chỉ trong cache mà không có nguồn bền phía sau. Redis có persistence nhưng đó là dự phòng, không phải để thay database.

Không xử lý cache-miss và lỗi kết nối. Nếu Redis chết mà ứng dụng không có đường lui về database, cả hệ thống sập theo. Luôn thiết kế để cache miss hoặc cache lỗi vẫn đọc được từ database (degrade gracefully).

Mẹo bảo mật. Bật in-transit encryption (TLS) và AUTH cho cache, đặt cache trong subnet riêng, chỉ cho phép ứng dụng truy cập qua security group/firewall. Đừng bao giờ để Redis mở ra internet — đây là lỗi bị khai thác rất phổ biến.

Bài tập thực hành

  • Phân tích workload. Chọn một ứng dụng bạn biết (hoặc giả định một blog có 100.000 lượt đọc/ngày). Liệt kê 3 loại dữ liệu nên cache và 2 loại không nên cache. Giải thích lý do dựa trên tần suất đọc và mức độ thay đổi.
  • Thiết kế key và TTL. Với sàn TMĐT, hãy đề xuất quy ước đặt tên key cho: thông tin sản phẩm, giỏ hàng người dùng, và top 10 sản phẩm bán chạy. Gán TTL phù hợp cho mỗi loại và biện luận.
  • Vẽ luồng cache-aside. Vẽ sơ đồ (hoặc mô tả bằng lời) đầy đủ luồng đọc theo pattern cache-aside, bao gồm cả nhánh hit và miss, và bước ghi lại vào cache.
  • Tình huống sự cố. Giả sử cache hit ratio đột nhiên tụt từ 92% xuống 40% và database bắt đầu quá tải. Liệt kê ít nhất 3 nguyên nhân khả dĩ và cách bạn điều tra từng nguyên nhân.
  • (Nâng cao) Thử nghiệm thực tế. Nếu có tài khoản cloud, tạo một instance Memorystore hoặc ElastiCache nhỏ nhất, kết nối bằng redis-cli, thử các lệnh SET, GET, EXPIRE, và ZADD/ZREVRANGE để cảm nhận trực tiếp tốc độ và cách sorted set hoạt động.

Tóm tắt

Cache là lớp hạ tầng in-memory giúp giảm tải database, giảm độ trễ từ hàng chục ms xuống dưới 1 ms, và lưu session cho tầng web stateless. Redis là engine phổ biến nhất nhờ hỗ trợ nhiều cấu trúc dữ liệu, còn ElastiCache (AWS) và Memorystore (GCP) là các dịch vụ managed giúp bạn khỏi phải tự vận hành cụm cache. Pattern nền tảng là cache-aside kết hợp TTL để cân bằng giữa tốc độ và độ tươi của dữ liệu.

Điều quan trọng nhất cần nhớ: cache là một sự đánh đổi có chủ đích giữa tốc độ và độ chính xác. Dùng đúng — cho dữ liệu đọc nhiều, đổi ít, có chiến lược invalidation và giám sát rõ ràng — cache sẽ cứu hệ thống của bạn trong những ngày cao điểm. Dùng sai — cache mù quáng, quên invalidation, coi cache là nguồn bền — nó sẽ trở thành nguồn gây lỗi khó hiểu nhất. Ở các bài sau, bạn sẽ thấy cache thường xuyên xuất hiện như một mảnh ghép trong các kiến trúc lớn hơn; hãy nắm chắc nền tảng này ngay từ bây giờ.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi