Kịch bản: Khi dữ liệu Production làm hỏng các truy vấn của bạnĐiều này xảy ra với cả những người giỏi nhất. Ứng dụng của bạn chạy hoàn hảo trong môi trường staging với 1.200 bản ghi giả lập, nhưng ngay khi bạn triển khai lên production, mọi thứ bắt đầu đình trệ. Gần đây tôi đã gặp phải vấn đề này khi xây dựng một dashboard nhật ký hoạt động (activity log). Ứng dụng hoạt động tốt trong nhiều tuần cho đến khi bộ sưu tập nhật ký đạt khoảng 45.000 tài liệu, lúc đó dashboard bắt đầu báo lỗi 500.
Vấn đề bắt nguồn từ một lệnh gọi .find().sort({ createdAt: -1 }) tiêu chuẩn. Vì trường createdAt không được đánh chỉ mục (index), MongoDB đã cố gắng đưa mọi tài liệu khớp vào RAM để sắp xếp chúng ngay lập tức. Khi tập dữ liệu tạm thời đó đạt đến ngưỡng 32MB, engine đã dừng tiến trình để bảo vệ máy chủ.
Tại sao lại có giới hạn 32MBMongoDB không hề gây khó dễ; nó đang bảo vệ hệ thống. Theo mặc định, engine cấp phát chính xác 33.554.432 bytes (32MB) cho các thao tác sắp xếp trong bộ nhớ (in-memory sort). Rào chắn an toàn này ngăn một truy vấn chưa được tối ưu ngốn hết bộ nhớ hệ thống hiện có và làm treo toàn bộ instance mongod.
Khi một truy vấn yêu cầu sắp xếp mà không thể thực hiện bằng index, MongoDB sẽ thực hiện một "Blocking Sort". Nó phải giữ toàn bộ kết quả trong bộ nhớ trước khi có thể trả về tài liệu đầu tiên. Nếu các tài liệu của bạn có kích thước lớn—ví dụ: 500KB mỗi bản ghi—bạn chỉ cần khoảng 65 bản ghi là đã chạm đến giới hạn này.
Executor error during find command: Operation failed because the sort operation used more than the maximum 33554432 bytes of RAM.
Bước 1: Kiểm tra truy vấn với explain()Đừng đoán xem điểm nghẽn nằm ở đâu. Hãy sử dụng phương thức .explain("executionStats") trong shell của bạn hoặc MongoDB Compass để xem điều gì đang diễn ra bên dưới.
db.activity_logs.find({ status: "active" }).sort({ createdAt: -1 }).explain("executionStats")
Kiểm tra phần winningPlan. Nếu bạn thấy giai đoạn SORT, MongoDB đang sắp xếp dữ liệu thủ công trong RAM. Bạn muốn thấy IXSCAN (Index Scan). Một truy vấn hiệu quả sẽ sử dụng index để truy xuất các tài liệu đã được sắp xếp sẵn theo đúng thứ tự, bỏ qua hoàn toàn giới hạn 32MB.
Giải pháp 1: Tạo Index mục tiêu (Khuyên dùng)Index là cách khắc phục hiệu quả nhất. Khi bạn đánh index cho trường sắp xếp, MongoDB sẽ lưu trữ dữ liệu trong cấu trúc cây đã được sắp xếp trước. Điều này chuyển đổi một tác vụ nặng nề cho CPU/RAM thành một thao tác đọc con trỏ đơn giản.
// Tạo index giảm dần để sắp xếp cái mới nhất lên đầu
db.activity_logs.createIndex({ createdAt: -1 })
Nếu bạn lọc theo một trường và sắp xếp theo một trường khác, hãy tuân theo quy tắc ESR (Equality, Sort, Range). Đây là tiêu chuẩn vàng cho các index hỗn hợp (compound indexes). Đặt các trường bạn khớp chính xác lên trước, sau đó đến trường sắp xếp, và cuối cùng là bất kỳ bộ lọc phạm vi nào (như $gt hoặc $lt).
// Tối ưu cho: .find({ status: "active" }).sort({ createdAt: -1 })
db.activity_logs.createIndex({ status: 1, createdAt: -1 })
Giải pháp 2: Sử dụng allowDiskUse cho các phép tổng hợp nặngĐôi khi bạn không thể đánh index cho mọi tổ hợp có thể, đặc biệt là với các dashboard báo cáo động. Đối với các pipeline aggregation, bạn có thể chỉ thị cho MongoDB sử dụng ổ cứng làm bộ nhớ RAM "tràn".
db.activity_logs.aggregate([
{ $match: { status: "active" } },
{ $sort: { createdAt: -1 } }
], { allowDiskUse: true })
Mặc dù điều này ngăn việc bị lỗi do giới hạn 32MB, nhưng nó đi kèm với sự đánh đổi về hiệu suất. Việc di chuyển dữ liệu đến và đi từ đĩa chậm hơn đáng kể so với RAM. Hãy sử dụng cách này cho các tác vụ quản trị ngầm, không phải cho các tính năng người dùng có lưu lượng truy cập cao.
Giải pháp 3: Điều chỉnh giới hạn bộ nhớ toàn cụcNếu máy chủ của bạn có 64GB RAM và bạn cảm thấy giới hạn 32MB quá khắt khe, bạn có thể tăng nó lên. Tuy nhiên, hãy thận trọng. Việc tăng giới hạn này trên toàn cục có nghĩa là mọi truy vấn được viết kém giờ đây đều có thể tiêu thụ nhiều tài nguyên hơn.
Để tăng giới hạn lên 64MB (67.108.864 bytes) ngay lập tức:
db.adminCommand({ setParameter: 1, internalQueryExecMaxBlockingSortBytes: 67108864 })
Để thay đổi này duy trì sau khi khởi động lại máy chủ, hãy cập nhật tệp mongod.conf của bạn:
setParameter:
internalQueryExecMaxBlockingSortBytes: 67108864

