diff options
author | Divjot Arora <divjot.arora@10gen.com> | 2019-02-14 13:04:54 -0500 |
---|---|---|
committer | Divjot Arora <divjot.arora@10gen.com> | 2019-02-26 09:41:40 -0500 |
commit | 6bf33306809526707fa08b93a0d8ee19eae0f9e9 (patch) | |
tree | e1686db2ca4f95300af3ee4fe2fd8aca9834cfa4 /src | |
parent | fa959b60b65dbe543dcbd6531690a4d93f305679 (diff) | |
download | mongo-6bf33306809526707fa08b93a0d8ee19eae0f9e9.tar.gz |
SERVER-38886 Refactor RecordStore::validate implementations
Diffstat (limited to 'src')
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 |