/** * Checks the first batch of results from query. * 'documents' are the first batch of results returned from tailing the remote oplog. * 'lastFetched' optime and hash should be consistent with the predicate in the query. * Returns RemoteOplogStale if the oplog query has no results. * Returns OplogStartMissing if we cannot find the optime of the last fetched operation in * the remote oplog. */ Status checkRemoteOplogStart(const Fetcher::Documents& documents, OpTimeWithHash lastFetched) { if (documents.empty()) { // The GTE query from upstream returns nothing, so we're ahead of the upstream. return Status(ErrorCodes::RemoteOplogStale, str::stream() << "We are ahead of the sync source. Our last op time fetched: " << lastFetched.opTime.toString()); } const auto& o = documents.front(); auto opTimeResult = OpTime::parseFromOplogEntry(o); if (!opTimeResult.isOK()) { return Status(ErrorCodes::OplogStartMissing, str::stream() << "our last op time fetched: " << lastFetched.opTime.toString() << " (hash: " << lastFetched.value << ")" << ". failed to parse optime from first oplog on source: " << o.toString() << ": " << opTimeResult.getStatus().toString()); } auto opTime = opTimeResult.getValue(); long long hash = o["h"].numberLong(); if (opTime != lastFetched.opTime || hash != lastFetched.value) { return Status(ErrorCodes::OplogStartMissing, str::stream() << "our last op time fetched: " << lastFetched.opTime.toString() << ". source's GTE: " << opTime.toString() << " hashes: (" << lastFetched.value << "/" << hash << ")"); } return Status::OK(); }
StatusWith<OplogFetcher::DocumentsInfo> OplogFetcher::validateDocuments( const Fetcher::Documents& documents, bool first, Timestamp lastTS) { if (first && documents.empty()) { return Status(ErrorCodes::OplogStartMissing, str::stream() << "The first batch of oplog entries is empty, but expected at " "least 1 document matching ts: " << lastTS.toString()); } DocumentsInfo info; // The count of the bytes of the documents read off the network. info.networkDocumentBytes = 0; info.networkDocumentCount = 0; for (auto&& doc : documents) { info.networkDocumentBytes += doc.objsize(); ++info.networkDocumentCount; // If this is the first response (to the $gte query) then we already applied the first doc. if (first && info.networkDocumentCount == 1U) { continue; } // Check to see if the oplog entry goes back in time for this document. const auto docOpTime = OpTime::parseFromOplogEntry(doc); // entries must have a "ts" field. if (!docOpTime.isOK()) { return docOpTime.getStatus(); } info.lastDocument = {doc["h"].numberLong(), docOpTime.getValue()}; const auto docTS = info.lastDocument.opTime.getTimestamp(); if (lastTS >= docTS) { return Status(ErrorCodes::OplogOutOfOrder, str::stream() << "Out of order entries in oplog. lastTS: " << lastTS.toString() << " outOfOrderTS:" << docTS.toString() << " in batch with " << info.networkDocumentCount << "docs; first-batch:" << first << ", doc:" << doc); } lastTS = docTS; } // These numbers are for the documents we will apply. info.toApplyDocumentCount = documents.size(); info.toApplyDocumentBytes = info.networkDocumentBytes; if (first) { // The count is one less since the first document found was already applied ($gte $ts query) // and we will not apply it again. --info.toApplyDocumentCount; auto alreadyAppliedDocument = documents.cbegin(); info.toApplyDocumentBytes -= alreadyAppliedDocument->objsize(); } return info; }