Tại sao Aggregation Pipeline của bạn bị lỗi
Bạn sẽ gặp phải rào cản này nếu cố gắng sử dụng toán tử $search ở bất kỳ vị trí nào ngoại trừ vị trí bắt đầu của mảng aggregation. Không giống như các toán tử tiêu chuẩn, Atlas Search yêu cầu driver phải chuyển giao truy vấn cho một công cụ chuyên dụng ngay lập tức.
PlanExecutor error during aggregation :: caused by :: $search is only allowed as the first stage in the pipeline
Lý do kỹ thuật: mongot so với mongod
Bên dưới hệ thống, MongoDB Atlas chạy hai tiến trình riêng biệt. Engine mongod tiêu chuẩn xử lý các truy vấn thông thường, trong khi một tiến trình có tên là mongot quản lý Atlas Search dựa trên Lucene.
Khi bạn kích hoạt một aggregation, MongoDB cần biết ngay lập tức liệu nó có cần sự tham gia của tiến trình mongot hay không. Nếu bạn đặt stage $match hoặc $project lên trước, engine mongod sẽ tự bắt đầu xử lý dữ liệu. Đến khi nó gặp $search, đã quá muộn để chuyển giao thao tác cho search engine, dẫn đến việc pipeline bị lỗi.
Sai lầm phổ biến
Các lập trình viên thường cố gắng thu hẹp dữ liệu bằng stage $match trước khi thực hiện tìm kiếm. Mặc dù điều này có vẻ hợp lý về mặt hiệu năng, nhưng đây lại là nguyên nhân chính gây ra lỗi này. Hãy xem ví dụ bị lỗi sau đây:
db.products.aggregate([
{ $match: { status: "active" } }, // ❌ Lỗi! Stage này không thể đứng trước $search
{
$search: {
index: "default",
text: {
query: "mechanical keyboard",
path: "name"
}
}
}
]);
Giải pháp 1: Thay đổi thứ tự các Stage
Cách nhanh nhất để khắc phục là di chuyển $search lên đầu mảng. Mọi thao tác lọc, giới hạn hoặc sắp xếp phải diễn ra sau đó.
db.products.aggregate([
{
$search: {
index: "default",
text: {
query: "mechanical keyboard",
path: "name"
}
}
},
{ $match: { status: "active" } } // ✅ Cách này hoạt động
]);
Lưu ý về hiệu năng: Mặc dù cách này khắc phục được lỗi, nhưng không phải lúc nào cũng hiệu quả. Nếu bạn có 10 triệu document nhưng chỉ có 1.000 cái ở trạng thái "active", MongoDB vẫn phải tìm kiếm trên toàn bộ index trước khi stage $match có thể loại bỏ những document không hoạt động.
Giải pháp 2: Sử dụng toán tử Compound (Cách tốt nhất)
Để giữ cho các truy vấn nhanh, hãy sử dụng toán tử compound. Điều này cho phép bạn kết hợp các thuật ngữ tìm kiếm và các bộ lọc của mình vào một thao tác duy nhất nằm bên trong engine mongot. Trong một collection có 5 triệu document, phương pháp này có thể giảm thời gian thực thi từ vài giây xuống dưới 100ms.
db.products.aggregate([
{
$search: {
index: "default",
compound: {
must: [{
text: {
query: "mechanical keyboard",
path: "name"
}
}],
filter: [{
text: {
query: "active",
path: "status"
}
}]
}
}
}
]);
Dưới đây là phân tích chi tiết tại sao cách này hoạt động tốt hơn:
- must: Đây là tiêu chí tìm kiếm của bạn. Nó đóng góp vào điểm mức độ liên quan (relevance score).
- filter: Hoạt động chính xác như một
$match. Nó bao gồm hoặc loại trừ các document mà không ảnh hưởng đến thứ hạng tìm kiếm của bạn.
Giải pháp 3: Cẩn thận với Mongoose Middleware
Nếu mã của bạn trông có vẻ đúng nhưng vẫn gặp lỗi, hãy kiểm tra các Mongoose plugin. Các plugin toàn cục cho tính năng "xóa mềm" (soft deletes) thường tự động chèn một stage match { deleted: false } vào đầu mỗi truy vấn. Stage ẩn này sẽ làm hỏng Atlas Search của bạn.
Để tránh điều này, bạn phải thiết lập pipeline của mình một cách thủ công và đảm bảo $search nằm ở vị trí 0:
const pipeline = [
{ $search: { /* cấu hình của bạn */ } },
{ $limit: 10 }
];
// Sử dụng base model để tránh sự can thiệp của middleware nếu cần thiết
await Product.aggregate(pipeline);
Cách kiểm tra kết quả
Chạy truy vấn của bạn trong MongoDB Compass Aggregation Pipeline Builder. Compass cung cấp bản xem trước thời gian thực của từng stage. Nếu stage đầu tiên không phải là $search, bản xem trước sẽ hiển thị ngay lỗi PlanExecutor. Ngoài ra, hãy thử thêm .explain("executionStats") vào truy vấn của bạn. Bạn sẽ thấy stage SEARCH ở đầu cây thực thi thay vì COLLSCAN hoặc IXSCAN.
Những điểm chính cần lưu ý
- Vị trí:
$searchgiống như một "ngôi sao"; nó luôn phải đứng đầu tiên. - Lọc: Sử dụng
compoundvàfilterbên trong stage search để đạt tốc độ tối đa. - Lưu trữ: Hãy nhớ rằng
$searchchỉ hoạt động trên MongoDB Atlas. Nếu bạn đang sử dụng phiên bản community cục bộ, bạn phải sử dụng$text(toán tử này cũng có các quy tắc vị trí cụ thể).

