summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDivjot Arora <divjot.arora@10gen.com>2019-02-14 13:04:54 -0500
committerDivjot Arora <divjot.arora@10gen.com>2019-02-26 09:41:40 -0500
commit6bf33306809526707fa08b93a0d8ee19eae0f9e9 (patch)
treee1686db2ca4f95300af3ee4fe2fd8aca9834cfa4 /src
parentfa959b60b65dbe543dcbd6531690a4d93f305679 (diff)
downloadmongo-6bf33306809526707fa08b93a0d8ee19eae0f9e9.tar.gz
SERVER-38886 Refactor RecordStore::validate implementations
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp61
-rw-r--r--src/mongo/db/catalog/collection_test.cpp81
-rw-r--r--src/mongo/db/catalog/private/record_store_validate_adaptor.cpp7
-rw-r--r--src/mongo/db/storage/SConscript1
-rw-r--r--src/mongo/db/storage/biggie/biggie_record_store.cpp56
-rw-r--r--src/mongo/db/storage/biggie/biggie_record_store.h6
-rw-r--r--src/mongo/db/storage/devnull/devnull_kv_engine.cpp8
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp28
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h6
-rw-r--r--src/mongo/db/storage/mobile/mobile_record_store.cpp96
-rw-r--r--src/mongo/db/storage/mobile/mobile_record_store.h13
-rw-r--r--src/mongo/db/storage/record_store.h16
-rw-r--r--src/mongo/db/storage/record_store_test_validate.cpp167
-rw-r--r--src/mongo/db/storage/record_store_test_validate.h117
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp83
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h9
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp121
17 files changed, 202 insertions, 674 deletions
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 85243b4519d..f6c3b3bb3c9 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -1008,6 +1008,61 @@ namespace {
using ValidateResultsMap = std::map<std::string, ValidateResults>;
+// General validation logic for any RecordStore. Performs sanity checks to confirm that each
+// record in the store is valid according to the given RecordStoreValidateAdaptor and updates
+// record store stats to match.
+void _genericRecordStoreValidate(OperationContext* opCtx,
+ RecordStore* recordStore,
+ RecordStoreValidateAdaptor* indexValidator,
+ ValidateResults* results,
+ BSONObjBuilder* output) {
+ long long nrecords = 0;
+ long long dataSizeTotal = 0;
+ long long nInvalid = 0;
+
+ results->valid = true;
+ std::unique_ptr<SeekableRecordCursor> cursor = recordStore->getCursor(opCtx, true);
+ int interruptInterval = 4096;
+ RecordId prevRecordId;
+
+ while (auto record = cursor->next()) {
+ if (!(nrecords % interruptInterval)) {
+ opCtx->checkForInterrupt();
+ }
+ ++nrecords;
+ auto dataSize = record->data.size();
+ dataSizeTotal += dataSize;
+ size_t validatedSize;
+ Status status = indexValidator->validate(record->id, record->data, &validatedSize);
+
+ // Check to ensure isInRecordIdOrder() is being used properly.
+ if (prevRecordId.isValid()) {
+ invariant(prevRecordId < record->id);
+ }
+
+ // ValidatedSize = dataSize is not a general requirement as some storage engines may use
+ // padding, but we still require that they return the unpadded record data.
+ if (!status.isOK() || validatedSize != static_cast<size_t>(dataSize)) {
+ if (results->valid) {
+ // Only log once.
+ results->errors.push_back("detected one or more invalid documents (see logs)");
+ }
+ nInvalid++;
+ results->valid = false;
+ log() << "document at location: " << record->id << " is corrupted";
+ }
+
+ prevRecordId = record->id;
+ }
+
+ if (results->valid) {
+ recordStore->updateStatsAfterRepair(opCtx, nrecords, dataSizeTotal);
+ }
+
+ output->append("nInvalidDocuments", nInvalid);
+ output->append("nrecords", nrecords);
+}
+
void _validateRecordStore(OperationContext* opCtx,
RecordStore* recordStore,
ValidateCmdLevel level,
@@ -1021,10 +1076,8 @@ void _validateRecordStore(OperationContext* opCtx,
if (background) {
indexValidator->traverseRecordStore(recordStore, level, results, output);
} else {
- auto status = recordStore->validate(opCtx, level, indexValidator, results, output);
- // RecordStore::validate always returns Status::OK(). Errors are reported through
- // `results`.
- dassert(status.isOK());
+ recordStore->validate(opCtx, level, results, output);
+ _genericRecordStoreValidate(opCtx, recordStore, indexValidator, results, output);
}
}
diff --git a/src/mongo/db/catalog/collection_test.cpp b/src/mongo/db/catalog/collection_test.cpp
index 27d91ce788d..3b85aa479c9 100644
--- a/src/mongo/db/catalog/collection_test.cpp
+++ b/src/mongo/db/catalog/collection_test.cpp
@@ -29,6 +29,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/bson/oid.h"
#include "mongo/db/catalog/capped_utils.h"
#include "mongo/db/catalog/catalog_test_fixture.h"
#include "mongo/db/catalog/collection.h"
@@ -45,6 +46,9 @@ using namespace mongo;
class CollectionTest : public CatalogTestFixture {
protected:
void makeCapped(NamespaceString nss, long long cappedSize = 8192);
+ void makeUncapped(NamespaceString nss);
+ void checkValidate(Collection* coll, bool valid, int records, int invalid, int errors);
+ std::vector<ValidateCmdLevel> levels{kValidateIndex, kValidateRecordStore, kValidateFull};
};
void CollectionTest::makeCapped(NamespaceString nss, long long cappedSize) {
@@ -54,6 +58,32 @@ void CollectionTest::makeCapped(NamespaceString nss, long long cappedSize) {
ASSERT_OK(storageInterface()->createCollection(operationContext(), nss, options));
}
+void CollectionTest::makeUncapped(NamespaceString nss) {
+ CollectionOptions options;
+ ASSERT_OK(storageInterface()->createCollection(operationContext(), nss, options));
+}
+
+// Call validate with different validation levels and verify the results.
+void CollectionTest::checkValidate(
+ Collection* coll, bool valid, int records, int invalid, int errors) {
+ auto opCtx = operationContext();
+ auto collLock =
+ std::make_unique<Lock::CollectionLock>(opCtx->lockState(), coll->ns().ns(), MODE_X);
+
+ for (auto level : levels) {
+ ValidateResults results;
+ BSONObjBuilder output;
+ auto status = coll->validate(opCtx, level, false, std::move(collLock), &results, &output);
+ ASSERT_OK(status);
+ ASSERT_EQ(results.valid, valid);
+ ASSERT_EQ(results.errors.size(), (long unsigned int)errors);
+
+ BSONObj obj = output.obj();
+ ASSERT_EQ(obj.getIntField("nrecords"), records);
+ ASSERT_EQ(obj.getIntField("nInvalidDocuments"), invalid);
+ }
+}
+
TEST_F(CollectionTest, CappedNotifierKillAndIsDead) {
NamespaceString nss("test.t");
makeCapped(nss);
@@ -203,4 +233,55 @@ TEST_F(CollectionTest, AsynchronouslyNotifyCappedWaitersIfNeeded) {
thread.join();
ASSERT_EQ(notifier->getVersion(), thisVersion);
}
+
+// Verify that calling validate() on an empty collection with different validation levels returns an
+// OK status.
+TEST_F(CollectionTest, ValidateEmpty) {
+ NamespaceString nss("test.t");
+ makeUncapped(nss);
+
+ auto opCtx = operationContext();
+ AutoGetCollection agc(opCtx, nss, MODE_X);
+ Collection* coll = agc.getCollection();
+
+ checkValidate(coll, true, 0, 0, 0);
+}
+
+// Verify calling validate() on a nonempty collection with different validation levels.
+TEST_F(CollectionTest, Validate) {
+ NamespaceString nss("test.t");
+ makeUncapped(nss);
+
+ auto opCtx = operationContext();
+ AutoGetCollection agc(opCtx, nss, MODE_X);
+ Collection* coll = agc.getCollection();
+
+ std::vector<InsertStatement> inserts;
+ for (int i = 0; i < 5; i++) {
+ auto doc = BSON("_id" << i);
+ inserts.push_back(InsertStatement(doc));
+ }
+
+ auto status = coll->insertDocuments(opCtx, inserts.begin(), inserts.end(), nullptr, false);
+ ASSERT_OK(status);
+ checkValidate(coll, true, inserts.size(), 0, 0);
+}
+
+// Verify calling validate() on a collection with an invalid document.
+TEST_F(CollectionTest, ValidateError) {
+ NamespaceString nss("test.t");
+ makeUncapped(nss);
+
+ auto opCtx = operationContext();
+ AutoGetCollection agc(opCtx, nss, MODE_X);
+ Collection* coll = agc.getCollection();
+ RecordStore* rs = coll->getRecordStore();
+
+ std::string invalidBson = "\0\0\0\0\0";
+ const char* recordData = invalidBson.c_str();
+ auto statusWithId = rs->insertRecord(opCtx, recordData, 5, Timestamp::min());
+ ASSERT_OK(statusWithId.getStatus());
+ checkValidate(coll, false, 1, 1, 1);
+}
+
} // namespace
diff --git a/src/mongo/db/catalog/private/record_store_validate_adaptor.cpp b/src/mongo/db/catalog/private/record_store_validate_adaptor.cpp
index 34c85b5fc27..3151fca3268 100644
--- a/src/mongo/db/catalog/private/record_store_validate_adaptor.cpp
+++ b/src/mongo/db/catalog/private/record_store_validate_adaptor.cpp
@@ -65,7 +65,12 @@ KeyString makeWildCardMultikeyMetadataKeyString(const BSONObj& indexKey) {
Status RecordStoreValidateAdaptor::validate(const RecordId& recordId,
const RecordData& record,
size_t* dataSize) {
- BSONObj recordBson = record.toBson();
+ BSONObj recordBson;
+ try {
+ recordBson = record.toBson();
+ } catch (...) {
+ return exceptionToStatus();
+ }
const Status status = validateBSON(
recordBson.objdata(), recordBson.objsize(), Validator<BSONObj>::enabledBSONVersion());
diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript
index 4d3f4bec2af..648faadb60a 100644
--- a/src/mongo/db/storage/SConscript
+++ b/src/mongo/db/storage/SConscript
@@ -179,7 +179,6 @@ env.Library(
'record_store_test_truncate.cpp',
'record_store_test_updaterecord.cpp',
'record_store_test_updatewithdamages.cpp',
- 'record_store_test_validate.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/service_context',
diff --git a/src/mongo/db/storage/biggie/biggie_record_store.cpp b/src/mongo/db/storage/biggie/biggie_record_store.cpp
index 198e35d9141..8f69ee8d617 100644
--- a/src/mongo/db/storage/biggie/biggie_record_store.cpp
+++ b/src/mongo/db/storage/biggie/biggie_record_store.cpp
@@ -336,62 +336,6 @@ void RecordStore::cappedTruncateAfter(OperationContext* opCtx, RecordId end, boo
wuow.commit();
}
-Status RecordStore::validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) {
- long long nrecords = 0;
- long long dataSizeTotal = 0;
- long long nInvalid = 0;
-
- results->valid = true;
- int interruptInterval = 4096;
-
- auto ru = RecoveryUnit::get(opCtx);
- StringStore* workingCopy(ru->getHead());
- auto end = workingCopy->upper_bound(_postfix);
- for (auto it = workingCopy->lower_bound(_prefix); it != end; ++it) {
- if (!(nrecords % interruptInterval))
- opCtx->checkForInterrupt();
- ++nrecords;
- Record record{RecordId(extractRecordId(it->first)),
- RecordData(it->second.c_str(), it->second.length())};
- auto dataSize = record.data.size();
- dataSizeTotal += dataSize;
- size_t validatedSize;
- Status status = adaptor->validate(record.id, record.data, &validatedSize);
-
- // The validatedSize equals dataSize below is not a general requirement, but is true
- // for this storage engine as we don't pad records.
- if (!status.isOK() || validatedSize != static_cast<size_t>(dataSize)) {
- if (results->valid) {
- // Only log once.
- results->errors.push_back("detected one or more invalid documents (see logs)");
- }
- nInvalid++;
- results->valid = false;
- log() << "document at location: " << record.id << " is corrupted";
- }
- }
-
- const bool strictChecking = false; // TODO(SERVER-38883): enable this
- if (results->valid && strictChecking) {
- auto numRecords = this->numRecords(opCtx);
- auto dataSize = this->dataSize(opCtx);
- invariant(
- nrecords == numRecords,
- str::stream() << "nrecords == " << nrecords << ", store numRecords == " << numRecords);
- invariant(dataSizeTotal == dataSize,
- str::stream() << "dataSizeTotal == " << dataSizeTotal << ", store dataSize == "
- << dataSize);
- }
-
- output->append("nInvalidDocuments", nInvalid);
- output->appendNumber("nrecords", nrecords);
- return Status::OK();
-}
-
void RecordStore::appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
double scale) const {
diff --git a/src/mongo/db/storage/biggie/biggie_record_store.h b/src/mongo/db/storage/biggie/biggie_record_store.h
index 9a1cdeff859..5f62b5ab5e8 100644
--- a/src/mongo/db/storage/biggie/biggie_record_store.h
+++ b/src/mongo/db/storage/biggie/biggie_record_store.h
@@ -102,12 +102,6 @@ public:
virtual void cappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive);
- virtual Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output);
-
virtual void appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
double scale) const;
diff --git a/src/mongo/db/storage/devnull/devnull_kv_engine.cpp b/src/mongo/db/storage/devnull/devnull_kv_engine.cpp
index 25ef616830a..a8f0fac6489 100644
--- a/src/mongo/db/storage/devnull/devnull_kv_engine.cpp
+++ b/src/mongo/db/storage/devnull/devnull_kv_engine.cpp
@@ -153,14 +153,6 @@ public:
virtual void cappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive) {}
- virtual Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) {
- return Status::OK();
- }
-
virtual void appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
double scale) const {
diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
index b3489bab119..ed39c18f5ee 100644
--- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
+++ b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.cpp
@@ -576,34 +576,6 @@ void EphemeralForTestRecordStore::cappedTruncateAfter(OperationContext* opCtx,
}
}
-Status EphemeralForTestRecordStore::validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) {
- stdx::lock_guard<stdx::recursive_mutex> lock(_data->recordsMutex);
-
- results->valid = true;
-
- for (Records::const_iterator it = _data->records.begin(); it != _data->records.end(); ++it) {
- const EphemeralForTestRecord& rec = it->second;
- size_t dataSize;
- const Status status = adaptor->validate(it->first, rec.toRecordData(), &dataSize);
- if (!status.isOK()) {
- if (results->valid) {
- // Only log once.
- results->errors.push_back("detected one or more invalid documents (see logs)");
- }
- results->valid = false;
- log() << "Invalid object detected in " << _ns << ": " << status.reason();
- }
- }
-
- output->appendNumber("nrecords", _data->records.size());
-
- return Status::OK();
-}
-
void EphemeralForTestRecordStore::appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
double scale) const {
diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h
index 89ec90cbbc1..87701b81eb1 100644
--- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h
+++ b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_record_store.h
@@ -95,12 +95,6 @@ public:
virtual void cappedTruncateAfter(OperationContext* opCtx, RecordId end, bool inclusive);
- virtual Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output);
-
virtual void appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
double scale) const;
diff --git a/src/mongo/db/storage/mobile/mobile_record_store.cpp b/src/mongo/db/storage/mobile/mobile_record_store.cpp
index d0c67b9d8f3..d83ba4f5c89 100644
--- a/src/mongo/db/storage/mobile/mobile_record_store.cpp
+++ b/src/mongo/db/storage/mobile/mobile_record_store.cpp
@@ -412,94 +412,13 @@ Status MobileRecordStore::truncate(OperationContext* opCtx) {
* Note: on full validation, this validates the entire database file, not just the table used by
* this record store.
*/
-Status MobileRecordStore::validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) {
+void MobileRecordStore::validate(OperationContext* opCtx,
+ ValidateCmdLevel level,
+ ValidateResults* results,
+ BSONObjBuilder* output) {
if (level == kValidateFull) {
doValidate(opCtx, results);
}
-
- if (!results->valid) {
- // The database was corrupt, so return without checking the table.
- return Status::OK();
- }
-
- MobileSession* session = MobileRecoveryUnit::get(opCtx)->getSession(opCtx);
- try {
- std::string selectQuery = "SELECT rec_id, data FROM \"" + _ident + "\";";
- SqliteStatement selectStmt(*session, selectQuery);
-
- int interruptInterval = 4096;
- long long actualNumRecs = 0;
- long long actualDataSize = 0;
- long long numInvalidRecs = 0;
-
- int status;
- while ((status = selectStmt.step()) == SQLITE_ROW) {
- if (!(actualNumRecs % interruptInterval)) {
- opCtx->checkForInterrupt();
- }
-
- long long id = selectStmt.getColInt(0);
- const void* data = selectStmt.getColBlob(1);
- int dataSize = selectStmt.getColBytes(1);
-
- ++actualNumRecs;
- actualDataSize += dataSize;
-
- RecordId recId(id);
- RecordData recData(reinterpret_cast<const char*>(data), dataSize);
-
- size_t validatedSize;
- Status status = adaptor->validate(recId, recData, &validatedSize);
-
- if (!status.isOK() || validatedSize != static_cast<size_t>(dataSize)) {
- if (results->valid) {
- std::string errMsg = "detected one or more invalid documents";
- validateLogAndAppendError(results, errMsg);
- }
-
- ++numInvalidRecs;
- log() << "document at location " << recId << " is corrupted";
- }
- }
-
- if (status == SQLITE_CORRUPT) {
- uasserted(ErrorCodes::UnknownError, sqlite3_errstr(status));
- }
- checkStatus(status, SQLITE_DONE, "sqlite3_step");
-
- // Verify that _numRecs and _dataSize are accurate.
- int64_t cachedNumRecs = numRecords(opCtx);
- if (_resetNumRecsIfNeeded(opCtx, actualNumRecs)) {
- str::stream errMsg;
- errMsg << "cached number of records does not match actual number of records - ";
- errMsg << "cached number of records = " << cachedNumRecs << "; ";
- errMsg << "actual number of records = " << actualNumRecs;
- validateLogAndAppendError(results, errMsg);
- }
- int64_t cachedDataSize = dataSize(opCtx);
- if (_resetDataSizeIfNeeded(opCtx, actualDataSize)) {
- str::stream errMsg;
- errMsg << "cached data size does not match actual data size - ";
- errMsg << "cached data size = " << cachedDataSize << "; ";
- errMsg << "actual data size = " << actualDataSize;
- validateLogAndAppendError(results, errMsg);
- }
-
- if (level == kValidateFull) {
- output->append("nInvalidDocuments", numInvalidRecs);
- }
- output->appendNumber("nrecords", actualNumRecs);
-
- } catch (const DBException& e) {
- std::string errMsg = "record store is corrupt, could not read documents - " + e.toString();
- validateLogAndAppendError(results, errMsg);
- }
-
- return Status::OK();
}
Status MobileRecordStore::touch(OperationContext* opCtx, BSONObjBuilder* output) const {
@@ -613,4 +532,11 @@ void MobileRecordStore::create(OperationContext* opCtx, const std::string& ident
SqliteStatement::execQuery(session, sqlQuery);
}
+void MobileRecordStore::updateStatsAfterRepair(OperationContext* opCtx,
+ long long numRecords,
+ long long dataSize) {
+ _resetDataSizeIfNeeded(opCtx, (int64_t)dataSize);
+ _resetNumRecsIfNeeded(opCtx, (int64_t)numRecords);
+}
+
} // namespace mongo
diff --git a/src/mongo/db/storage/mobile/mobile_record_store.h b/src/mongo/db/storage/mobile/mobile_record_store.h
index 5d9d5abd722..ac2d7e4e229 100644
--- a/src/mongo/db/storage/mobile/mobile_record_store.h
+++ b/src/mongo/db/storage/mobile/mobile_record_store.h
@@ -99,11 +99,10 @@ public:
return false;
}
- Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) override;
+ void validate(OperationContext* opCtx,
+ ValidateCmdLevel level,
+ ValidateResults* results,
+ BSONObjBuilder* output) override;
void appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
@@ -126,9 +125,7 @@ public:
void waitForAllEarlierOplogWritesToBeVisible(OperationContext* opCtx) const override {}
- void updateStatsAfterRepair(OperationContext* opCtx,
- long long numRecords,
- long long dataSize) override {}
+ void updateStatsAfterRepair(OperationContext* opCtx, long long numRecords, long long dataSize);
bool isCapped() const override {
// Capped Collections are not supported
diff --git a/src/mongo/db/storage/record_store.h b/src/mongo/db/storage/record_store.h
index ff5c6c0fb46..af9faa245d8 100644
--- a/src/mongo/db/storage/record_store.h
+++ b/src/mongo/db/storage/record_store.h
@@ -513,15 +513,13 @@ public:
}
/**
- * @return OK if the validate run successfully
- * OK will be returned even if corruption is found
- * deatils will be in result
- */
- virtual Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) = 0;
+ * Performs record store specific validation to ensure consistency of underlying data
+ * structures. If corruption is found, details of the errors will be in the results parameter.
+ */
+ virtual void validate(OperationContext* opCtx,
+ ValidateCmdLevel level,
+ ValidateResults* results,
+ BSONObjBuilder* output) {}
/**
* @param scaleSize - amount by which to scale size metrics
diff --git a/src/mongo/db/storage/record_store_test_validate.cpp b/src/mongo/db/storage/record_store_test_validate.cpp
deleted file mode 100644
index 892b34edaf1..00000000000
--- a/src/mongo/db/storage/record_store_test_validate.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/storage/record_store_test_validate.h"
-
-
-#include "mongo/db/storage/record_store.h"
-#include "mongo/db/storage/record_store_test_harness.h"
-#include "mongo/unittest/unittest.h"
-
-using std::unique_ptr;
-using std::string;
-
-namespace mongo {
-namespace {
-
-// Verify that calling validate() on an empty collection returns an OK status.
-TEST(RecordStoreTestHarness, ValidateEmpty) {
- const auto harnessHelper(newRecordStoreHarnessHelper());
- unique_ptr<RecordStore> rs(harnessHelper->newNonCappedRecordStore());
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
- }
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- {
- ValidateAdaptorSpy adaptor;
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateIndex, &adaptor, &results, &stats));
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-// Verify that calling validate() on an empty collection returns an OK status.
-TEST(RecordStoreTestHarness, ValidateEmptyAndScanData) {
- const auto harnessHelper(newRecordStoreHarnessHelper());
- unique_ptr<RecordStore> rs(harnessHelper->newNonCappedRecordStore());
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
- }
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- {
- ValidateAdaptorSpy adaptor;
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateRecordStore, &adaptor, &results, &stats));
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-// Verify that calling validate() on an empty collection returns an OK status.
-TEST(RecordStoreTestHarness, FullValidateEmptyAndScanData) {
- const auto harnessHelper(newRecordStoreHarnessHelper());
- unique_ptr<RecordStore> rs(harnessHelper->newNonCappedRecordStore());
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
- }
-
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- {
- ValidateAdaptorSpy adaptor;
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateFull, &adaptor, &results, &stats));
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-// Insert multiple records, and verify that calling validate() on a nonempty collection
-// returns an OK status.
-TEST_F(ValidateTest, ValidateNonEmpty) {
- {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- {
- ValidateAdaptorSpy adaptor(getInsertedRecords());
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(
- getRecordStore().validate(opCtx.get(), kValidateIndex, &adaptor, &results, &stats));
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-// Insert multiple records, and verify that calling validate() on a nonempty collection
-// returns an OK status.
-TEST_F(ValidateTest, ValidateAndScanDataNonEmpty) {
- {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- {
- ValidateAdaptorSpy adaptor(getInsertedRecords());
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(getRecordStore().validate(
- opCtx.get(), kValidateRecordStore, &adaptor, &results, &stats));
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-// Insert multiple records, and verify that calling validate() on a nonempty collection
-// returns an OK status.
-TEST_F(ValidateTest, FullValidateNonEmptyAndScanData) {
- {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- {
- ValidateAdaptorSpy adaptor(getInsertedRecords());
- ValidateResults results;
- BSONObjBuilder stats;
- ASSERT_OK(
- getRecordStore().validate(opCtx.get(), kValidateFull, &adaptor, &results, &stats));
- ASSERT(adaptor.allValidated());
- ASSERT(results.valid);
- ASSERT(results.errors.empty());
- }
- }
-}
-
-} // namespace
-} // namespace mongo
diff --git a/src/mongo/db/storage/record_store_test_validate.h b/src/mongo/db/storage/record_store_test_validate.h
deleted file mode 100644
index 5e4d361e5c7..00000000000
--- a/src/mongo/db/storage/record_store_test_validate.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#pragma once
-
-
-#include "mongo/db/storage/record_data.h"
-#include "mongo/db/storage/record_store.h"
-#include "mongo/db/storage/record_store_test_harness.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-
-class ValidateAdaptorSpy : public ValidateAdaptor {
-public:
- ValidateAdaptorSpy() {}
-
- ValidateAdaptorSpy(const std::set<std::string>& remain) : _remain(remain) {}
-
- ~ValidateAdaptorSpy() {}
-
- Status validate(const RecordId& recordId, const RecordData& recordData, size_t* dataSize) {
- std::string s(recordData.data());
- ASSERT(1 == _remain.erase(s));
-
- *dataSize = recordData.size();
- return Status::OK();
- }
-
- bool allValidated() {
- return _remain.empty();
- }
-
-private:
- std::set<std::string> _remain; // initially contains all inserted records
-};
-
-class ValidateTest : public mongo::unittest::Test {
-public:
- ValidateTest()
- : _harnessHelper(newRecordStoreHarnessHelper()),
- _rs(_harnessHelper->newNonCappedRecordStore()) {}
-
- ServiceContext::UniqueOperationContext newOperationContext() {
- return _harnessHelper->newOperationContext();
- }
-
- RecordStore& getRecordStore() {
- return *_rs;
- }
-
- const std::set<std::string>& getInsertedRecords() {
- return _remain;
- }
-
- void setUp() {
- {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- ASSERT_EQUALS(0, _rs->numRecords(opCtx.get()));
- }
-
- int nToInsert = 10;
- for (int i = 0; i < nToInsert; i++) {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- {
- std::stringstream ss;
- ss << "record " << i;
- std::string data = ss.str();
- ASSERT(_remain.insert(data).second);
-
- WriteUnitOfWork uow(opCtx.get());
- StatusWith<RecordId> res =
- _rs->insertRecord(opCtx.get(), data.c_str(), data.size() + 1, Timestamp());
- ASSERT_OK(res.getStatus());
- uow.commit();
- }
- }
-
- {
- ServiceContext::UniqueOperationContext opCtx(newOperationContext());
- ASSERT_EQUALS(nToInsert, _rs->numRecords(opCtx.get()));
- }
- }
-
-private:
- std::unique_ptr<RecordStoreHarnessHelper> _harnessHelper;
- std::unique_ptr<RecordStore> _rs;
- std::set<std::string> _remain;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
index 7d476789338..425a1d470fd 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
@@ -1494,72 +1494,39 @@ Status WiredTigerRecordStore::compact(OperationContext* opCtx) {
return Status::OK();
}
-Status WiredTigerRecordStore::validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output) {
+void WiredTigerRecordStore::validate(OperationContext* opCtx,
+ ValidateCmdLevel level,
+ ValidateResults* results,
+ BSONObjBuilder* output) {
dassert(opCtx->lockState()->isReadLocked());
- if (!_isEphemeral && level == kValidateFull) {
- int err = WiredTigerUtil::verifyTable(opCtx, _uri, &results->errors);
- if (err == EBUSY) {
- std::string msg = str::stream()
- << "Could not complete validation of " << _uri << ". "
- << "This is a transient issue as the collection was actively "
- "in use by other operations.";
-
- warning() << msg;
- results->warnings.push_back(msg);
- } else if (err) {
- std::string msg = str::stream() << "verify() returned " << wiredtiger_strerror(err)
- << ". "
- << "This indicates structural damage. "
- << "Not examining individual documents.";
- error() << msg;
- results->errors.push_back(msg);
- results->valid = false;
- return Status::OK();
- }
+ // Only full validate should verify table.
+ if (_isEphemeral || level != kValidateFull) {
+ return;
}
- long long nrecords = 0;
- long long dataSizeTotal = 0;
- long long nInvalid = 0;
-
- results->valid = true;
- std::unique_ptr<SeekableRecordCursor> cursor = getCursor(opCtx, true);
- int interruptInterval = 4096;
-
- while (auto record = cursor->next()) {
- if (!(nrecords % interruptInterval))
- opCtx->checkForInterrupt();
- ++nrecords;
- auto dataSize = record->data.size();
- dataSizeTotal += dataSize;
- size_t validatedSize;
- Status status = adaptor->validate(record->id, record->data, &validatedSize);
-
- // The validatedSize equals dataSize below is not a general requirement, but must be
- // true for WT today because we never pad records.
- if (!status.isOK() || validatedSize != static_cast<size_t>(dataSize)) {
- if (results->valid) {
- // Only log once.
- results->errors.push_back("detected one or more invalid documents (see logs)");
- }
- nInvalid++;
- results->valid = false;
- log() << "document at location: " << record->id << " is corrupted";
- }
+ int err = WiredTigerUtil::verifyTable(opCtx, _uri, &results->errors);
+ if (!err) {
+ return;
}
- if (results->valid) {
- updateStatsAfterRepair(opCtx, nrecords, dataSizeTotal);
+ if (err == EBUSY) {
+ std::string msg = str::stream()
+ << "Could not complete validation of " << _uri << ". "
+ << "This is a transient issue as the collection was actively "
+ "in use by other operations.";
+
+ warning() << msg;
+ results->warnings.push_back(msg);
+ return;
}
- output->append("nInvalidDocuments", nInvalid);
- output->appendNumber("nrecords", nrecords);
- return Status::OK();
+ std::string msg = str::stream() << "verify() returned " << wiredtiger_strerror(err) << ". "
+ << "This indicates structural damage. "
+ << "Not examining individual documents.";
+ error() << msg;
+ results->errors.push_back(msg);
+ results->valid = false;
}
void WiredTigerRecordStore::appendCustomStats(OperationContext* opCtx,
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h
index 9c3487c60d5..f47db72d8a7 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.h
@@ -187,11 +187,10 @@ public:
return true;
}
- virtual Status validate(OperationContext* opCtx,
- ValidateCmdLevel level,
- ValidateAdaptor* adaptor,
- ValidateResults* results,
- BSONObjBuilder* output);
+ virtual void validate(OperationContext* opCtx,
+ ValidateCmdLevel level,
+ ValidateResults* results,
+ BSONObjBuilder* output);
virtual void appendCustomStats(OperationContext* opCtx,
BSONObjBuilder* result,
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp
index e1b2c62cdbd..0d4c5fbc477 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp
@@ -297,23 +297,7 @@ TEST(WiredTigerRecordStoreTest, SizeStorer1) {
rs.reset(nullptr); // this has to be deleted before ss
}
-class GoodValidateAdaptor : public ValidateAdaptor {
-public:
- virtual Status validate(const RecordId& recordId, const RecordData& record, size_t* dataSize) {
- *dataSize = static_cast<size_t>(record.size());
- return Status::OK();
- }
-};
-
-class BadValidateAdaptor : public ValidateAdaptor {
-public:
- virtual Status validate(const RecordId& recordId, const RecordData& record, size_t* dataSize) {
- *dataSize = static_cast<size_t>(record.size());
- return Status(ErrorCodes::UnknownError, "");
- }
-};
-
-class SizeStorerValidateTest : public mongo::unittest::Test {
+class SizeStorerUpdateTest : public mongo::unittest::Test {
private:
virtual void setUp() {
harnessHelper.reset(new WiredTigerHarnessHelper());
@@ -327,26 +311,8 @@ private:
wtrs->setSizeStorer(sizeStorer.get());
ident = wtrs->getIdent();
uri = wtrs->getURI();
-
- expectedNumRecords = 100;
- expectedDataSize = expectedNumRecords * 2;
- {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- WriteUnitOfWork uow(opCtx.get());
- for (int i = 0; i < expectedNumRecords; i++) {
- ASSERT_OK(rs->insertRecord(opCtx.get(), "a", 2, Timestamp()).getStatus());
- }
- uow.commit();
- }
- auto info = sizeStorer->load(uri);
- info->numRecords.store(0);
- info->dataSize.store(0);
- sizeStorer->store(uri, info);
}
virtual void tearDown() {
- expectedNumRecords = 0;
- expectedDataSize = 0;
-
rs.reset(nullptr);
sizeStorer->flush(false);
sizeStorer.reset(nullptr);
@@ -367,90 +333,15 @@ protected:
std::unique_ptr<RecordStore> rs;
std::string ident;
std::string uri;
-
- long long expectedNumRecords;
- long long expectedDataSize;
};
// Basic validation - size storer data is updated.
-TEST_F(SizeStorerValidateTest, Basic) {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- GoodValidateAdaptor adaptor;
- ValidateResults results;
- BSONObjBuilder output;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateIndex, &adaptor, &results, &output));
- BSONObj obj = output.obj();
- ASSERT_EQUALS(expectedNumRecords, obj.getIntField("nrecords"));
- ASSERT_EQUALS(expectedNumRecords, getNumRecords());
- ASSERT_EQUALS(expectedDataSize, getDataSize());
-}
-
-// Full validation - size storer data is updated.
-TEST_F(SizeStorerValidateTest, FullWithGoodAdaptor) {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- GoodValidateAdaptor adaptor;
- ValidateResults results;
- BSONObjBuilder output;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateFull, &adaptor, &results, &output));
- BSONObj obj = output.obj();
- ASSERT_EQUALS(expectedNumRecords, obj.getIntField("nrecords"));
- ASSERT_EQUALS(expectedNumRecords, getNumRecords());
- ASSERT_EQUALS(expectedDataSize, getDataSize());
-}
-
-// Full validation with a validation adaptor that fails - size storer data is not updated.
-TEST_F(SizeStorerValidateTest, FullWithBadAdapter) {
- ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- BadValidateAdaptor adaptor;
- ValidateResults results;
- BSONObjBuilder output;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateFull, &adaptor, &results, &output));
- BSONObj obj = output.obj();
- ASSERT_EQUALS(expectedNumRecords, obj.getIntField("nrecords"));
- ASSERT_EQUALS(0, getNumRecords());
- ASSERT_EQUALS(0, getDataSize());
-}
-
-// Load bad _numRecords and _dataSize values at record store creation.
-TEST_F(SizeStorerValidateTest, InvalidSizeStorerAtCreation) {
- rs.reset(NULL);
-
+TEST_F(SizeStorerUpdateTest, Basic) {
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
- auto info = sizeStorer->load(uri);
- info->numRecords.store(expectedNumRecords * 2);
- info->dataSize.store(expectedDataSize * 2);
- sizeStorer->store(uri, info);
-
- WiredTigerRecordStore::Params params;
- params.ns = "a.b"_sd;
- params.ident = ident;
- params.engineName = kWiredTigerEngineName;
- params.isCapped = false;
- params.isEphemeral = false;
- params.cappedMaxSize = -1;
- params.cappedMaxDocs = -1;
- params.cappedCallback = nullptr;
- params.sizeStorer = sizeStorer.get();
-
- auto ret = new StandardWiredTigerRecordStore(nullptr, opCtx.get(), params);
- ret->postConstructorInit(opCtx.get());
- rs.reset(ret);
-
- ASSERT_EQUALS(expectedNumRecords * 2, rs->numRecords(opCtx.get()));
- ASSERT_EQUALS(expectedDataSize * 2, rs->dataSize(opCtx.get()));
-
- // Full validation should fix record and size counters.
- GoodValidateAdaptor adaptor;
- ValidateResults results;
- BSONObjBuilder output;
- ASSERT_OK(rs->validate(opCtx.get(), kValidateFull, &adaptor, &results, &output));
- BSONObj obj = output.obj();
- ASSERT_EQUALS(expectedNumRecords, obj.getIntField("nrecords"));
- ASSERT_EQUALS(expectedNumRecords, getNumRecords());
- ASSERT_EQUALS(expectedDataSize, getDataSize());
-
- ASSERT_EQUALS(expectedNumRecords, rs->numRecords(opCtx.get()));
- ASSERT_EQUALS(expectedDataSize, rs->dataSize(opCtx.get()));
+ long long val = 5;
+ rs->updateStatsAfterRepair(opCtx.get(), val, val);
+ ASSERT_EQUALS(getNumRecords(), val);
+ ASSERT_EQUALS(getDataSize(), val);
}
} // namespace