Engineering Notes

Tối ưu N+1 Query trong Optimizely CMS: Case Study Thực Tế

By Ginbok2 min read

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:

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.

#optimizely#backend#performance
← Back to Articles
Tối ưu N+1 Query trong Optimizely CMS: Case Study Thực Tế - Ginbok