Chi phí tiềm ẩn của các ContentArea lồng nhau
Trong Optimizely CMS, tính linh hoạt của ContentArea cho phép các biên tập viên tạo ra cấu trúc nội dung lồng nhau phức tạp. Tuy nhiên, điều này thường dẫn đến vấn đề N+1 query. Chúng tôi đã xử lý một trang Store nơi việc tải một trang duy nhất thực hiện tới ~181 truy vấn database. Nguyên nhân là do vòng lặp lồng nhau gọi _contentLoader.Get<T>() cho từng item riêng lẻ.
Anti-Pattern: Tải dữ liệu đơn lẻ
Mã nguồn ban đầu tải nội dung từng bước một trong các vòng lặp lồng nhau. Điều này tạo ra nút thắt cổ chai về hiệu năng vì mỗi lần gọi hàm là một lần truy vấn vào cơ sở dữ liệu.
// ❌ Anti-pattern gây ra N+1
foreach (var item in storePage.ContentArea.Items)
{
var content = _contentLoader.Get<IContent>(item.ContentLink);
if (content is LayoutRowBlock row)
{
foreach (var rowItem in row.ContentArea.Items)
{
var child = _contentLoader.Get<IContent>(rowItem.ContentLink); // ❌ Truy vấn cho mỗi con
}
}
}
Giải pháp: Tải theo lô với GetItems()
Chiến lược để loại bỏ N+1 rất đơn giản: Thu thập tất cả reference trước, tải một lần duy nhất, sau đó xử lý trong bộ nhớ. Hàm IContentLoader.GetItems() của Optimizely được thiết kế chính xác cho mục đích này.
Triển khai tối ưu
// ✅ 1. Thu thập tất cả links
var contentLinks = storePage.ContentArea.Items
.Where(i => i?.ContentLink != null)
.Select(i => i.ContentLink)
.ToList();
// ✅ 2. Truy vấn batch duy nhất
var allContent = _contentLoader.GetItems(contentLinks, CultureInfo.CurrentUICulture);
// ✅ 3. Lặp trong bộ nhớ
foreach (var content in allContent)
{
if (content is LayoutRowBlock row)
{
var childLinks = row.ContentArea.Items.Select(x => x.ContentLink);
var rowChildren = _contentLoader.GetItems(childLinks, CultureInfo.CurrentUICulture);
// Xử lý dữ liệu...
}
}
Kết quả đạt được
Việc chuyển sang xử lý theo lô đã mang lại những cải thiện rõ rệt:
- Truy vấn Database: Giảm từ ~181 xuống còn ~11.
- Thời gian phản hồi: Giảm từ hơn 800ms xuống dưới 150ms.
- Khả năng mở rộng: Hiệu năng trang không còn giảm dần khi số lượng sản phẩm tăng lên.
Bài học kinh nghiệm
Hãy luôn kiểm tra hiệu năng (profile) trang CMS của bạn. Nếu bạn thấy các câu lệnh SELECT lặp lại liên tục vào bảng tblContent, đó chính là dấu hiệu của N+1. Hãy nhớ: dùng Get<T> cho một item và GetItems cho một danh sách.