EDIT - Hiệu suất truy vấn:
Như @NeilLunn đã chỉ ra trong nhận xét của mình, bạn không nên lọc tài liệu theo cách thủ công mà hãy sử dụng .find(...)
thay vào đó:
db.snapshots.find({
roundedDate: { $exists: true },
stream: { $exists: true },
sid: { $exists: false }
})
Ngoài ra, sử dụng .bulkWrite()
, có sẵn dưới dạng từ MongoDB 3.2
, sẽ hiệu quả hơn nhiều so với việc cập nhật riêng lẻ.
Có thể rằng, với điều đó, bạn có thể thực hiện truy vấn của mình trong vòng 10 phút tồn tại của con trỏ. Nếu nó vẫn mất nhiều hơn thế, con trỏ của bạn sẽ hết hạn và bạn sẽ gặp phải vấn đề tương tự, được giải thích bên dưới:
Chuyện gì đang xảy ra ở đây:
Error: getMore command failed
có thể là do thời gian chờ của con trỏ, có liên quan đến hai thuộc tính con trỏ:
-
Giới hạn thời gian chờ, theo mặc định là 10 phút. Từ tài liệu:
Theo mặc định, máy chủ sẽ tự động đóng con trỏ sau 10 phút không hoạt động hoặc nếu máy khách đã sử dụng hết con trỏ.
-
Kích thước lô, là 101 tài liệu hoặc 16 MB cho lô đầu tiên và 16 MB, bất kể số lượng tài liệu, cho các lô tiếp theo (kể từ MongoDB
3.4
). Từ tài liệu:find()
vàaggregate()
các hoạt động có kích thước lô ban đầu là 101 tài liệu theo mặc định. Các hoạt động getMore tiếp theo được thực hiện dựa trên con trỏ kết quả không có kích thước lô mặc định, vì vậy chúng chỉ bị giới hạn bởi kích thước thư 16 megabyte.
Có thể bạn đang sử dụng 101 tài liệu ban đầu đó và sau đó nhận được một lô 16 MB, đây là mức tối đa, với rất nhiều tài liệu hơn. Vì mất hơn 10 phút để xử lý chúng, con trỏ trên máy chủ hết thời gian chờ và khi bạn xử lý xong tài liệu trong lô thứ hai và yêu cầu tài liệu mới, con trỏ đã bị đóng:
Khi bạn lặp qua con trỏ và đến cuối lô được trả về, nếu có nhiều kết quả hơn, thì cursor.next () sẽ thực hiện thao tác getMore để truy xuất lô tiếp theo.
Các giải pháp khả thi:
Tôi thấy có 5 cách khả thi để giải quyết vấn đề này, 3 cách tốt với ưu nhược điểm và 2 cách không tốt:
-
👍 Giảm kích thước hàng loạt để giữ cho con trỏ hoạt động.
-
👍 Xóa thời gian chờ khỏi con trỏ.
-
👍 Thử lại khi con trỏ hết hạn.
-
👎 Truy vấn kết quả theo lô theo cách thủ công.
-
👎 Nhận tất cả các tài liệu trước khi con trỏ hết hạn.
Lưu ý rằng chúng không được đánh số theo bất kỳ tiêu chí cụ thể nào. Đọc qua chúng và quyết định cái nào phù hợp nhất cho trường hợp cụ thể của bạn.
1. 👍 Giảm kích thước hàng loạt để giữ cho con trỏ hoạt động
Một cách để giải quyết vấn đề đó là sử dụng cursor.bacthSize
để đặt kích thước lô trên con trỏ được trả về bởi find
của bạn để khớp với những truy vấn mà bạn có thể xử lý trong vòng 10 phút đó:
const cursor = db.collection.find()
.batchSize(NUMBER_OF_DOCUMENTS_IN_BATCH);
Tuy nhiên, hãy nhớ rằng việc đặt kích thước lô rất thận trọng (nhỏ) có thể sẽ hoạt động, nhưng cũng sẽ chậm hơn, vì bây giờ bạn cần phải truy cập vào máy chủ nhiều lần hơn.
Mặt khác, đặt nó thành một giá trị quá gần với số lượng tài liệu bạn có thể xử lý trong 10 phút có nghĩa là có thể xảy ra trường hợp một số lần lặp lại mất nhiều thời gian hơn một chút để xử lý vì bất kỳ lý do gì (các quy trình khác có thể tiêu tốn nhiều tài nguyên hơn) , con trỏ vẫn sẽ hết hạn và bạn sẽ gặp lại lỗi tương tự.
2. 👍 Xóa thời gian chờ khỏi con trỏ
Một tùy chọn khác là sử dụng cursor.noCursorTimeout để ngăn con trỏ hết thời gian:
const cursor = db.collection.find().noCursorTimeout();
Đây được coi là một phương pháp không tốt vì bạn cần phải đóng con trỏ theo cách thủ công hoặc sử dụng hết tất cả các kết quả của nó để nó tự động được đóng:
Sau khi thiết lập
noCursorTimeout
tùy chọn, bạn phải đóng con trỏ theo cách thủ công vớicursor.close()
hoặc bằng cách sử dụng hết các kết quả của con trỏ.
Khi bạn muốn xử lý tất cả các tài liệu trong con trỏ, bạn sẽ không cần phải đóng nó theo cách thủ công, nhưng vẫn có thể xảy ra sự cố khác trong mã của bạn và lỗi xảy ra trước khi bạn hoàn tất, do đó hãy để con trỏ mở .
Nếu bạn vẫn muốn sử dụng phương pháp này, hãy sử dụng try-catch
để đảm bảo rằng bạn đóng con trỏ nếu có bất kỳ sự cố nào xảy ra trước khi bạn sử dụng tất cả các tài liệu của nó.
Lưu ý rằng tôi không coi đây là một giải pháp tồi (do đó là 👍), thậm chí còn cho rằng nó được coi là một thực hành xấu ...:
-
Nó là một tính năng được hỗ trợ bởi trình điều khiển. Nếu nó quá tệ, vì có nhiều cách thay thế để khắc phục vấn đề thời gian chờ, như đã giải thích trong các giải pháp khác, điều này sẽ không được hỗ trợ.
-
Có nhiều cách để sử dụng nó một cách an toàn, chỉ là bạn phải hết sức thận trọng với nó.
-
Tôi giả sử rằng bạn không chạy loại truy vấn này thường xuyên, vì vậy khả năng bạn bắt đầu để con trỏ mở ở mọi nơi là thấp. Nếu không đúng như vậy và bạn thực sự cần phải đối phó với những tình huống này mọi lúc, thì bạn không nên sử dụng
noCursorTimeout
.
3. 👍 Thử lại khi con trỏ hết hạn
Về cơ bản, bạn đặt mã của mình trong try-catch
và khi gặp lỗi, bạn sẽ nhận được một con trỏ mới bỏ qua các tài liệu mà bạn đã xử lý:
let processed = 0;
let updated = 0;
while(true) {
const cursor = db.snapshots.find().sort({ _id: 1 }).skip(processed);
try {
while (cursor.hasNext()) {
const doc = cursor.next();
++processed;
if (doc.stream && doc.roundedDate && !doc.sid) {
db.snapshots.update({
_id: doc._id
}, { $set: {
sid: `${ doc.stream.valueOf() }-${ doc.roundedDate }`
}});
++updated;
}
}
break; // Done processing all, exit outer loop
} catch (err) {
if (err.code !== 43) {
// Something else than a timeout went wrong. Abort loop.
throw err;
}
}
}
Lưu ý rằng bạn cần sắp xếp kết quả để giải pháp này hoạt động.
Với cách tiếp cận này, bạn đang giảm thiểu số lượng yêu cầu đến máy chủ bằng cách sử dụng kích thước lô tối đa có thể là 16 MB, mà không cần phải đoán trước có bao nhiêu tài liệu bạn sẽ có thể xử lý trong 10 phút. Do đó, nó cũng mạnh mẽ hơn so với cách tiếp cận trước đây.
4. 👎 Truy vấn kết quả theo lô theo cách thủ công
Về cơ bản, bạn sử dụng bỏ qua (), giới hạn () và sắp xếp () để thực hiện nhiều truy vấn với một số tài liệu mà bạn nghĩ rằng mình có thể xử lý trong 10 phút.
Tôi coi đây là một giải pháp tồi vì trình điều khiển đã có tùy chọn đặt kích thước lô, vì vậy không có lý do gì để làm điều này theo cách thủ công, chỉ cần sử dụng giải pháp 1 và không phát minh lại bánh xe.
Ngoài ra, điều đáng nói là nó có những nhược điểm giống như giải pháp 1,
5. 👎 Nhận tất cả các tài liệu trước khi con trỏ hết hạn
Có thể mã của bạn đang mất một chút thời gian để thực thi do xử lý kết quả, vì vậy bạn có thể truy xuất tất cả các tài liệu trước rồi xử lý chúng:
const results = new Array(db.snapshots.find());
Thao tác này sẽ lần lượt truy xuất tất cả các lô và đóng con trỏ. Sau đó, bạn có thể lặp lại tất cả các tài liệu bên trong results
và làm những gì bạn cần làm.
Tuy nhiên, nếu bạn gặp vấn đề về thời gian chờ, rất có thể tập kết quả của bạn khá lớn, do đó, xóa mọi thứ trong bộ nhớ có thể không phải là điều nên làm nhất.
Lưu ý về chế độ chụp nhanh và sao chép tài liệu
Có thể một số tài liệu được trả lại nhiều lần nếu các thao tác ghi can thiệp di chuyển chúng do kích thước tài liệu tăng lên. Để giải quyết vấn đề này, hãy sử dụng cursor.snapshot()
. Từ tài liệu:
Nối phương thức snapshot () vào con trỏ để chuyển đổi chế độ “snapshot”. Điều này đảm bảo rằng truy vấn sẽ không trả lại một tài liệu nhiều lần, ngay cả khi các thao tác ghi can thiệp dẫn đến việc di chuyển tài liệu do kích thước tài liệu tăng lên.
Tuy nhiên, hãy nhớ những hạn chế của nó:
-
Nó không hoạt động với các bộ sưu tập được chia nhỏ.
-
Nó không hoạt động với
sort()
hoặchint()
, vì vậy nó sẽ không hoạt động với giải pháp 3 và 4. -
Nó không đảm bảo cách ly khỏi việc chèn hoặc xóa.
Lưu ý với giải pháp 5, khoảng thời gian di chuyển tài liệu có thể gây ra việc truy xuất tài liệu trùng lặp hẹp hơn so với các giải pháp khác, vì vậy bạn có thể không cần snapshot()
.
Trong trường hợp cụ thể của bạn, vì bộ sưu tập được gọi là snapshot
, có thể nó không có khả năng thay đổi, vì vậy bạn có thể không cần snapshot()
. Hơn nữa, bạn đang cập nhật tài liệu dựa trên dữ liệu của chúng và sau khi cập nhật xong, tài liệu đó sẽ không được cập nhật lại mặc dù nó được truy xuất nhiều lần, dưới dạng if
điều kiện sẽ bỏ qua nó.
Lưu ý về con trỏ đang mở
Để xem số lượng con trỏ đang mở, hãy sử dụng db.serverStatus().metrics.cursor
.