Nhiều view Django chạy lại cùng một truy vấn ở mọi request dù dữ liệu hiếm khi thay đổi, khiến database chịu tải vô ích và trang chậm dần khi traffic tăng. Giải pháp là dùng cache framework có sẵn của Django: lưu kết quả truy vấn bằng cache.set() hoặc cache.get_or_set(), rồi lấy lại từ cache trong thời gian timeout thay vì hỏi database. Cách này phù hợp nhất với dữ liệu ít thay đổi như danh mục hay cấu hình chung.

Vấn đề

Hãy xem một view danh sách sản phẩm quen thuộc: mỗi lần người dùng mở trang, view lại lấy toàn bộ danh mục để vẽ menu, dù danh mục cả ngày không đổi lần nào.

def product_list(request):
categories = list(Category.objects.all()) # chạy lại ở mọi request
products = Product.objects.select_related("category")[:20]
return render(request, "shop/list.html", {
"categories": categories,
"products": products,
})

Vấn đề nằm ở số lượng: hàng nghìn request đồng nghĩa hàng nghìn lần truy vấn giống hệt nhau, và những truy vấn thật sự cần thiết phải xếp hàng chờ sau chúng.

Giải pháp

Django đã tích hợp sẵn cache framework: chỉ cần import cache từ django.core.cache là dùng ngay, không cần cài thêm thư viện. Khi lên production, bạn đổi backend sang Redis hoặc Memcached trong CACHES mà không phải sửa lại code dùng cache.

Cách viết rõ ràng nhất là kiểm tra cache trước, thiếu mới truy vấn rồi lưu lại:

from django.core.cache import cache
def get_categories():
categories = cache.get("categories:all")
if categories is None:
categories = list(Category.objects.all())
cache.set("categories:all", categories, timeout=300)
return categories

Nếu muốn gọn hơn, dùng get_or_set() — một dòng làm đúng việc trên: có trong cache thì trả về ngay, chưa có thì gọi hàm lấy dữ liệu rồi lưu lại với timeout 300 giây:

def get_categories():
return cache.get_or_set(
"categories:all",
lambda: list(Category.objects.all()),
timeout=300,
)

Cache không tự biết khi nào dữ liệu thay đổi, nên bạn phải chủ động xóa key cũ ở nơi dữ liệu được sửa, ví dụ sau khi tạo hoặc cập nhật danh mục:

cache.delete("categories:all")

Kết hợp timeout ngắn và delete() ngay khi dữ liệu đổi, bạn có một lớp cache nhanh mà không giữ dữ liệu cũ quá lâu.

Áp dụng vào dự án của bạn

Bắt đầu từ một chỗ duy nhất, đừng cache toàn bộ ứng dụng ngay: chọn dữ liệu đọc nhiều, viết ít như danh mục sidebar hay cấu hình hiển thị chung, đặt timeout khoảng 5 phút rồi đo lại bằng django-debug-toolbar để thấy số query giảm thực tế. Nhớ đặt tên key có quy ước rõ ràng, ví dụ categories:all, để sau này tìm và xóa không bị nhầm.

Bài viết liên quan

Câu hỏi thường gặp

Khi nào không nên cache kết quả truy vấn?

Không nên cache dữ liệu thay đổi liên tục như số dư tài khoản hay tồn kho theo thời gian thực, vì người dùng sẽ thấy số liệu cũ. Cũng tránh cache dữ liệu riêng tư dưới một key dùng chung — nếu bắt buộc, hãy đưa định danh người dùng vào key, ví dụ cart:user:42.

Cache lưu ở đâu, và LocMemCache có dùng được cho production không?

Vị trí lưu trữ do backend trong CACHES quyết định. LocMemCache lưu trong bộ nhớ từng tiến trình nên chỉ phù hợp cho phát triển và kiểm thử, vì mỗi worker có một bản cache riêng. Trên production, hãy dùng Redis hoặc Memcached để mọi worker đọc chung một cache.

Dữ liệu trong cache bị cũ thì xử lý thế nào?

Có hai cơ chế bổ sung cho nhau: timeout để key tự hết hạn, và cache.delete() ngay khi dữ liệu gốc thay đổi. Đặt timeout ngắn cho dữ liệu hay đổi, dài cho dữ liệu ổn định — đừng đặt cache vĩnh viễn.

get_or_set() khác gì viết tay get rồi set?

Về kết quả thì giống nhau, nhưng get_or_set() gọn hơn: bạn không phải viết lại mẫu kiểm tra is None ở khắp nơi, và code đọc đúng ý đồ hơn — “lấy từ cache, chưa có thì tính rồi lưu”.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo