Nếu một view Django tốn nhiều query hoặc tính toán nặng mà dữ liệu chỉ đổi vài lần mỗi ngày, hãy bọc nó bằng @cache_page(60 * 15). Django sẽ lưu toàn bộ response vào cache và trả lại y nguyên trong 15 phút, thay vì chạy lại view mỗi request. Cách này phù hợp cho trang danh sách, trang thống kê hay trang chi tiết ít thay đổi — nhưng không nên dùng trần cho trang chứa dữ liệu riêng của từng người dùng.
Vấn đề
Hãy tưởng tượng trang thống kê của bạn mỗi lần mở lại chạy một loạt query tổng hợp, rồi render cùng một bảng số liệu cho mọi khách truy cập. Lượt xem càng đông, database càng chịu tải cho những kết quả gần như giống hệt nhau:
# views.py — chưa cache: mỗi request chạy lại toàn bộfrom django.shortcuts import renderfrom django.db.models import Sumfrom .models import Orderdef thong_ke(request): don_hang = Order.objects.filter(trang_thai="hoan_thanh") context = {"tong_doanh_thu": don_hang.aggregate(tong=Sum("tong_tien"))["tong"], "so_don": don_hang.count()} return render(request, "thong_ke.html", context)
Vấn đề không nằm ở câu query mà ở chỗ nó bị lặp lại vô ích: dữ liệu thống kê theo ngày vài phút sau vẫn đúng, nhưng server vẫn tính lại từ đầu cho từng người xem.
Giải pháp
Chỉ cần thêm một decorator, Django sẽ tự lo phần còn lại:
# views.py — chỉ tính một lần mỗi 15 phútfrom django.views.decorators.cache import cache_pagefrom django.shortcuts import renderfrom django.db.models import Sumfrom .models import Order@cache_page(60 * 15)def thong_ke(request): don_hang = Order.objects.filter(trang_thai="hoan_thanh") context = {"tong_doanh_thu": don_hang.aggregate(tong=Sum("tong_tien"))["tong"], "so_don": don_hang.count()} return render(request, "thong_ke.html", context)
Trước đó, bạn cần khai báo một cache backend trong settings.py. Khi phát triển, bộ nhớ đệm trong RAM là đủ; lên production hãy chuyển sang Redis:
# settings.py — môi trường devCACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
Nếu không muốn sửa view, bạn có thể bọc ngay trong urls.py — cách này tiện khi view nằm trong app của bên thứ ba:
# urls.pyfrom django.urls import pathfrom django.views.decorators.cache import cache_pagefrom . import viewsurlpatterns = [path("thong-ke/", cache_page(60 * 15)(views.thong_ke), name="thong_ke")]
Có ba điều cần nhớ. Thứ nhất, cache_page phân biệt theo URL đầy đủ, kèm cả query string, nên ?page=2 là một bản cache riêng — phân trang vẫn hoạt động đúng. Thứ hai, cache không tự biết dữ liệu đã đổi: trong 15 phút, đơn hàng mới vẫn chưa hiện lên trang. Vì vậy hãy chọn timeout theo nhịp đổi của dữ liệu, hoặc đổi key_prefix để “xả” cache cũ. Thứ ba, trang cá nhân hóa theo cookie phải thêm @vary_on_cookie, và thứ tự decorator là bắt buộc: @cache_page ở trên, @vary_on_cookie ở dưới. Đảo ngược thứ tự, người dùng sau có thể nhận đúng trang đã cache của người trước — lỗi này mình đã kiểm chứng trực tiếp trước khi viết bài.
# Thứ tự ĐÚNG: cache_page ở trên, vary_on_cookie ở dướifrom django.views.decorators.cache import cache_pagefrom django.views.decorators.vary import vary_on_cookie@cache_page(60 * 15)@vary_on_cookiedef trang_ca_nhan(request): return render(request, "trang_ca_nhan.html", {"user": request.user})
Áp dụng vào dự án của bạn
Hãy bắt đầu từ trang chậm nhất mà dữ liệu ít đổi: trang danh sách sản phẩm, bảng thống kê. Đo bằng django-debug-toolbar trước và sau khi gắn decorator để thấy số query giảm về 0 ở các request trúng cache. Đặt timeout 5–15 phút cho lần thử đầu tiên, theo dõi một ngày rồi mới nới. Và đừng cache trang giỏ hàng hay dashboard cá nhân — những nơi mỗi người phải thấy dữ liệu của riêng mình thì cache sai còn tệ hơn không cache.
Bài viết liên quan
- Django: cache kết quả truy vấn bằng cache framework trong 5 phút — khỏi truy vấn lại mỗi request
- Django: lấy hàng loạt object theo danh sách ID bằng in_bulk() trong 5 phút
- Django: thống kê theo nhóm bằng values() + annotate() — GROUP BY trong 1 query
Câu hỏi thường gặp
cache_page khác gì cache.get/cache.set?
cache_page cache nguyên cả response theo URL, không cần viết logic đọc/ghi cache. Còn cache.get/cache.set linh hoạt hơn: cache từng mảnh dữ liệu, từng queryset, dùng được ở bất cứ đâu.
Nên đặt timeout bao lâu?
Theo nhịp đổi của dữ liệu. Dữ liệu đổi theo ngày thì 15–60 phút là hợp lý; dữ liệu đổi mỗi vài phút thì chỉ nên cache 1–5 phút, hoặc đừng cache mà tối ưu query trước.
Vì sao lưu dữ liệu mới mà trang vẫn hiện nội dung cũ?
Vì cache chưa hết hạn. Cách xử lý: chờ hết timeout, đặt timeout ngắn hơn, hoặc đổi key_prefix để cache cũ bị bỏ qua ngay.
Có cache được trang yêu cầu đăng nhập không?
Được, nhưng phải gắn @vary_on_cookie đúng thứ tự như mục Giải pháp để mỗi người dùng có bản cache riêng. Nếu không chắc chắn, an toàn nhất là không cache trang cá nhân.
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
- Tài liệu Django — Django’s cache framework: https://docs.djangoproject.com/en/5.2/topics/cache/
