Đi đến nội dung chính
VI
Trang chủ / Bài viết / Cache ba tầng an toàn cho SaaS nhiều khách hàng
15 thg 1, 2026 · Z-SOFT Admin · 10 phút đọc

Cache ba tầng an toàn cho SaaS nhiều khách hàng

Cách kết hợp trình duyệt, CDN và Redis để tăng tốc hệ thống mà vẫn giữ dữ liệu của từng khách hàng tách biệt.

Xem tất cả bài viết

Ý chính: Dữ liệu công khai có thể được lưu gần người dùng. Dữ liệu riêng phải luôn gắn với đúng khách hàng và có quy tắc làm mới rõ ràng.

Thuật ngữ trong bài: Tenant: một khách hàng dùng nền tảng; cache: bản sao dữ liệu để đọc nhanh; invalidation: làm cho bản cache cũ hết hiệu lực.

Bài toán thực tế

Thêm Redis chưa đủ để có một hệ thống cache an toàn. Khi một khóa hết hạn, hàng loạt yêu cầu có thể cùng truy vấn cơ sở dữ liệu. Nếu cấu hình cache sai, dữ liệu của khách hàng này còn có thể xuất hiện trong phản hồi dành cho khách hàng khác.

Cache không chỉ là một tối ưu hiệu năng; đó là một bản sao của dữ liệu được đặt ở nơi khác. Trước khi thêm một tầng cache, đội ngũ phải trả lời rõ bản sao này thuộc khách hàng nào, được phép cũ trong bao lâu và được làm mới bằng cơ chế nào.

Trong SaaS nhiều khách hàng, một phản hồi nhanh nhưng sai ranh giới dữ liệu là lỗi nghiêm trọng hơn nhiều so với một phản hồi chậm. Vì vậy, khóa cache và quy tắc CDN phải được kiểm thử với ít nhất hai khách hàng ngay từ đầu.

Thiết kế và triển khai

  1. Phân loại phản hồi là công khai hay riêng tư trước khi quyết định tầng nào được phép cache.
  2. Dùng single-flight để khi một khóa hết hạn chỉ có một yêu cầu dựng lại dữ liệu, các yêu cầu còn lại chờ hoặc nhận bản cũ trong giới hạn cho phép.
  3. Đưa mã khách hàng và phiên bản dữ liệu vào khóa cache; tăng phiên bản mỗi khi dữ liệu liên quan thay đổi.

Mã minh họa: Đọc cache theo khách hàng với stale-while-revalidate

key = `catalog:${tenantId}:v${catalogVersion}:${queryHash}`
cached = await redis.get(key)
if (cached && cached.age < freshFor) return cached.value

lock = await redis.set(`${key}:lock`, requestId, { NX: true, PX: 3000 })
if (!lock && cached && cached.age < staleFor) return cached.value

value = await db.catalog.findMany({ where: { tenantId } })
await redis.set(key, encode(value), { EX: staleFor })
return value

Nếu dùng khóa để chống dựng lại đồng thời, mỗi lần giữ khóa cần có mã chủ sở hữu và chỉ chính chủ mới được nhả khóa.

Rủi ro cần tính trước

  • Thiếu mã khách hàng tạo ra một khóa dùng chung cho toàn hệ thống.
  • Giao dịch cơ sở dữ liệu đã hoàn tất nhưng bước làm mới cache thất bại.
  • Redis ngừng hoạt động và toàn bộ lưu lượng bất ngờ dồn xuống cơ sở dữ liệu.

Nên theo dõi gì?

Tín hiệuĐiều tín hiệu cho biết
Tỷ lệ đọc trúng cache theo endpoint và khách hàngPhát hiện endpoint không phù hợp để cache hoặc một khách hàng đang tạo tải bất thường.
Số lần dựng lại và thời gian chờ khóaTăng đột biến thường cho thấy nhiều yêu cầu đang cùng dựng lại một khóa vừa hết hạn.
Số truy vấn cơ sở dữ liệu trên mỗi yêu cầuXác nhận cache thật sự loại bỏ công việc phía sau thay vì chỉ chuyển chi phí sang nơi khác.
Số phản hồi dùng dữ liệu cũ và độ trễ làm mớiĐo cái giá về tính nhất quán đang được đánh đổi để lấy tốc độ đọc.

Đưa vào production từng bước

Nên bắt đầu với một endpoint đọc nhiều nhưng ít rủi ro. Chạy cache ở chế độ đối chiếu, theo dõi tỷ lệ đọc trúng, độ cũ của dữ liệu, độ trễ Redis và số truy vấn cơ sở dữ liệu; chỉ trả dữ liệu từ cache khi kết quả đã ổn định.

Điều cần nhớ

Cache tốt không chỉ làm hệ thống nhanh hơn. Nó còn cho phép đội ngũ nhìn rõ dữ liệu thuộc về ai, có hiệu lực bao lâu và được làm mới khi nào.

HỢP TÁC CÙNG Z-SOFT

Mỗi quyết định công nghệ hôm nay đều ảnh hưởng đến nhiều năm sau.

Trao đổi với đội ngũ Z-SOFT để đánh giá hiện trạng, xác định mục tiêu và lựa chọn kiến trúc phù hợp với định hướng phát triển của doanh nghiệp.

Đặt lịch tư vấn