diff options
author | Dewal Gupta <dewal.gupta@10gen.com> | 2018-08-01 16:17:55 -0400 |
---|---|---|
committer | Dewal Gupta <dewal.gupta@10gen.com> | 2018-08-24 16:18:45 -0400 |
commit | d3656459d016d6a1be0788acfe3276734ab59210 (patch) | |
tree | 6223b5caad85b716151297d35845614816a48e5a | |
parent | fe2906300d0458e5421b576319b11274c56ea3c8 (diff) | |
download | mongo-d3656459d016d6a1be0788acfe3276734ab59210.tar.gz |
SERVER-13455 Add new configuration option for mongod to allow separate journal directory for WT
19 files changed, 407 insertions, 30 deletions
diff --git a/buildscripts/resmokeconfig/suites/disk_mobile.yml b/buildscripts/resmokeconfig/suites/disk_mobile.yml index c9d75ceeafc..bfd46a34715 100644 --- a/buildscripts/resmokeconfig/suites/disk_mobile.yml +++ b/buildscripts/resmokeconfig/suites/disk_mobile.yml @@ -5,6 +5,7 @@ selector: - jstests/disk/*.js exclude_files: - jstests/disk/directoryperdb.js + - jstests/disk/journalpath.js exclude_with_any_tags: - requires_wiredtiger diff --git a/jstests/disk/journalpath.js b/jstests/disk/journalpath.js new file mode 100644 index 00000000000..d2349fd6ee9 --- /dev/null +++ b/jstests/disk/journalpath.js @@ -0,0 +1,44 @@ +/** + * Confirms that journal files are stored in the expected directory when using "--journalPath" or + * the default directory when no journalPath is passed. + * @tags: [requires_wiredtiger] + */ +(function() { + "use strict"; + + const baseDir = 'jstests_disk_journalpath'; + const journalSubDir = 'journal_test'; + const dbpath = MongoRunner.dataPath + baseDir + '/'; + const storageEngine = db.serverStatus().storageEngine.name; + + // Matches wiredTiger journal files (WiredTigerLog or WiredTigerPreplog) + const journalFileMatcher = /WiredTiger(Preplog|Log)\..+$/; + + /** + * Returns the current connection after ensuring journal path directory 'dir' contains only + * journal files. + */ + let checkFilesInJournalDirectory = function(conn, dir) { + assert(conn, "Mongod failed to start up."); + let files = listFiles(dir); + let fileCount = 0; + for (let f in files) { + assert(!files[f].isDirectory, 'Unexpected directory found in journal path.'); + fileCount += 1; + assert(journalFileMatcher.test(files[f].name), + 'In directory:' + dir + ' found unexpected file: ' + files[f].name); + } + assert(fileCount > 0, 'Expected more than zero nondirectory files in database directory'); + }; + + // Ensure journal files are found in correct default location. + let mongoServer = MongoRunner.runMongod({storageEngine: storageEngine, dbpath: dbpath}); + checkFilesInJournalDirectory(mongoServer, dbpath + "journal"); + MongoRunner.stopMongod(mongoServer); + + // Check journal files location if a custom journal path directory is passed. + mongoServer = MongoRunner.runMongod( + {storageEngine: storageEngine, dbpath: dbpath, journalPath: dbpath + journalSubDir}); + checkFilesInJournalDirectory(mongoServer, dbpath + journalSubDir); + MongoRunner.stopMongod(mongoServer); +})(); diff --git a/jstests/noPassthrough/wt_journalpath_change.js b/jstests/noPassthrough/wt_journalpath_change.js new file mode 100644 index 00000000000..cc0ea54b744 --- /dev/null +++ b/jstests/noPassthrough/wt_journalpath_change.js @@ -0,0 +1,137 @@ +/** + * Tests that journaled write operations that have occurred since the last checkpoint are replayed + * when the mongod is killed and restarted with --nojournal. + */ +(function() { + 'use strict'; + + // Skip this test if not running with the "wiredTiger" storage engine. + if (jsTest.options().storageEngine && jsTest.options().storageEngine !== 'wiredTiger') { + jsTest.log('Skipping test because storageEngine is not "wiredTiger"'); + return; + } + + // Returns a function that primarily executes unjournaled inserts, but periodically does a + // journaled insert. If 'checkpoint' is true, then the fsync command is run to create a + // checkpoint prior to the mongod being terminated. + function insertFunctionFactory(checkpoint) { + let insertFunction = function() { + for (let iter = 0; iter < 1000; ++iter) { + let bulk = db.nojournal.initializeUnorderedBulkOp(); + for (let i = 0; i < 100; ++i) { + bulk.insert({unjournaled: i}); + } + assert.writeOK(bulk.execute({j: false})); + assert.writeOK(db.nojournal.insert({journaled: iter}, {writeConcern: {j: true}})); + if (__checkpoint_template_placeholder__ && iter === 50) { + assert.commandWorked(db.adminCommand({fsync: 1})); + } + } + }; + + return '(' + + insertFunction.toString().replace('__checkpoint_template_placeholder__', + checkpoint.toString()) + + ')();'; + } + + function runTest(options) { + let dbpath = MongoRunner.dataPath + 'wt_nojournal_toggle'; + let journalPath = MongoRunner.dataPath + 'journal'; + resetDbpath(dbpath); + + // Start a mongod with journaling enabled. + let conn = MongoRunner.runMongod({ + dbpath: dbpath, + noCleanData: true, + journalPath: journalPath, + }); + assert.neq(null, conn, 'mongod was unable to start up'); + + // Run a mixture of journaled and unjournaled write operations against the mongod. + let awaitShell = startParallelShell(insertFunctionFactory(options.checkpoint), conn.port); + + // After some journaled write operations have been performed against the mongod, send a + // SIGKILL to the process to trigger an unclean shutdown. + assert.soon(function() { + let testDB = conn.getDB('test'); + let count = testDB.nojournal.count({journaled: {$exists: true}}); + if (count >= 100) { + // We saw 100 journaled inserts, but visibility does not guarantee durability, so + // do an extra journaled write to make all visible commits durable, before killing + // the mongod. + assert.writeOK(testDB.nojournal.insert({final: true}, {writeConcern: {j: true}})); + MongoRunner.stopMongod(conn, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL}); + return true; + } + return false; + }, 'the parallel shell did not perform at least 100 journaled inserts'); + + let exitCode = awaitShell({checkExitSuccess: false}); + assert.neq(0, exitCode, 'expected shell to exit abnormally due to mongod being terminated'); + + // Restarting mongod with a different journal path but the same db path should not be + // allowed. The proper way to change the journal path is to start mongod with 'nojournal', + // and then restart it with a different journal path. + let newJournalPath = MongoRunner.dataPath + 'test_path_change/journal'; + conn = MongoRunner.runMongod({ + dbpath: dbpath, + noCleanData: true, + journalPath: newJournalPath, + }); + assert.eq(null, conn, 'mongod was able to start despite changing the journal path.'); + + // Restart the mongod with journaling disabled. This should force WT to clean up the journal + // files. + conn = MongoRunner.runMongod({ + dbpath: dbpath, + noCleanData: true, + nojournal: '', + }); + assert.neq(null, conn, 'mongod was unable to restart after receiving a SIGKILL'); + + let testDB = conn.getDB('test'); + assert.eq(1, testDB.nojournal.count({final: true}), 'final journaled write was not found'); + assert.lte(100, + testDB.nojournal.count({journaled: {$exists: true}}), + 'journaled write operations since the last checkpoint were not replayed'); + + let initialNumLogWrites = testDB.serverStatus().wiredTiger.log['log write operations']; + assert.writeOK(testDB.nojournal.insert({a: 1}, {writeConcern: {fsync: true}})); + assert.eq(initialNumLogWrites, + testDB.serverStatus().wiredTiger.log['log write operations'], + 'journaling is still enabled even though --nojournal was specified'); + + MongoRunner.stopMongod(conn); + + // Restart the mongod with journaling enabled and with a different journalpath. Because + // mongod was started without a journal path last time, this should work. + conn = MongoRunner.runMongod({ + dbpath: dbpath, + noCleanData: true, + journalPath: newJournalPath, + }); + assert.neq(null, conn, 'mongod was unable to start up after re-enabling journaling'); + + // Change the database object to connect to the restarted mongod. + testDB = conn.getDB('test'); + initialNumLogWrites = testDB.serverStatus().wiredTiger.log['log write operations']; + + assert.writeOK(testDB.nojournal.insert({a: 1}, {writeConcern: {fsync: true}})); + assert.lt(initialNumLogWrites, + testDB.serverStatus().wiredTiger.log['log write operations'], + 'journaling is still disabled even though --journal was specified'); + + MongoRunner.stopMongod(conn); + } + + // Operations from the journal should be replayed even when the mongod is terminated before + // anything is written to disk. + jsTest.log('Running the test without ever creating a checkpoint'); + runTest({checkpoint: false}); + + // Repeat the test again, but ensure that some data is written to disk before the mongod is + // terminated. + jsTest.log('Creating a checkpoint part-way through running the test'); + runTest({checkpoint: true}); +})(); diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index 78ffeb1a511..52922d17cc3 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -305,7 +305,8 @@ ExitCode _initAndListen(int listenPort) { ProcessId pid = ProcessId::getCurrent(); LogstreamBuilder l = log(LogComponent::kControl); l << "MongoDB starting : pid=" << pid << " port=" << serverGlobalParams.port - << " dbpath=" << storageGlobalParams.dbpath; + << " dbpath=" << storageGlobalParams.dbpath + << " journalPath=" << storageGlobalParams.journalPath; const bool is32bit = sizeof(int*) == 4; l << (is32bit ? " 32" : " 64") << "-bit host=" << getHostNameCached() << endl; diff --git a/src/mongo/db/mongod_options.cpp b/src/mongo/db/mongod_options.cpp index 0376b73cee0..8b76e30adad 100644 --- a/src/mongo/db/mongod_options.cpp +++ b/src/mongo/db/mongod_options.cpp @@ -146,13 +146,25 @@ Status addMongodOptions(moe::OptionSection* options) { #ifdef _WIN32 boost::filesystem::path currentPath = boost::filesystem::current_path(); - std::string defaultPath = currentPath.root_name().string() + storageGlobalParams.kDefaultDbPath; + std::string defaultDbPath = + currentPath.root_name().string() + std::string(storageGlobalParams.kDefaultDbPath); storage_options.addOptionChaining("storage.dbPath", "dbpath", moe::String, std::string("directory for datafiles - defaults to ") + storageGlobalParams.kDefaultDbPath + " which is " + - defaultPath + " based on the current working drive"); + defaultDbPath + " based on the current working drive"); + + std::string defaultJournalPath = std::string(storageGlobalParams.kDefaultDbPath) + + std::string(storageGlobalParams.kDefaultDbJournalSubdir); + std::string defaultJournalPathWindows = + defaultDbPath + storageGlobalParams.kDefaultDbJournalSubdir; + storage_options.addOptionChaining( + "storage.journalPath", + "journalPath", + moe::String, + std::string("directory for journal files - defaults to ") + defaultJournalPath + + " which is " + defaultJournalPathWindows + " based on the current working drive"); #else storage_options.addOptionChaining("storage.dbPath", @@ -161,6 +173,13 @@ Status addMongodOptions(moe::OptionSection* options) { std::string("directory for datafiles - defaults to ") + storageGlobalParams.kDefaultDbPath); + storage_options.addOptionChaining( + "storage.journalPath", + "journalPath", + moe::String, + std::string("directory for journal files - defaults to <dbpath>/") + + storageGlobalParams.kDefaultDbJournalSubdir); + #endif storage_options.addOptionChaining("storage.directoryPerDB", "directoryperdb", @@ -483,6 +502,12 @@ Status validateMongodOptions(const moe::Environment& params) { } if ((params.count("nodur") || params.count("nojournal")) && + params.count("storage.journalPath")) { + return Status(ErrorCodes::BadValue, + "Can't specify both --journalPath and --nojournal options."); + } + + if ((params.count("nodur") || params.count("nojournal")) && (params.count("dur") || params.count("journal"))) { return Status(ErrorCodes::BadValue, "Can't specify both --journal and --nojournal options."); @@ -753,12 +778,21 @@ Status storeMongodOptions(const moe::Environment& params) { storageGlobalParams.dbpath = params["storage.dbPath"].as<std::string>(); if (params.count("processManagement.fork") && storageGlobalParams.dbpath[0] != '/') { // we need to change dbpath if we fork since we change - // cwd to "/" - // fork only exists on *nix - // so '/' is safe + // cwd to "/". Fork only exists on *nix so '/' is safe. storageGlobalParams.dbpath = serverGlobalParams.cwd + "/" + storageGlobalParams.dbpath; } } + + if (params.count("storage.journalPath")) { + storageGlobalParams.journalPath = params["storage.journalPath"].as<std::string>(); + storageGlobalParams.journalPathSetByUser = true; + if (params.count("processManagement.fork") && storageGlobalParams.journalPath[0] != '/') { + // we need to change journalpath if we fork since we change + // cwd to "/". Fork only exists on *nix so '/' is safe. + storageGlobalParams.journalPath = + serverGlobalParams.cwd + "/" + storageGlobalParams.journalPath; + } + } #ifdef _WIN32 if (storageGlobalParams.dbpath.size() > 1 && storageGlobalParams.dbpath[storageGlobalParams.dbpath.size() - 1] == '/') { @@ -766,6 +800,13 @@ Status storeMongodOptions(const moe::Environment& params) { storageGlobalParams.dbpath = storageGlobalParams.dbpath.erase(storageGlobalParams.dbpath.size() - 1); } + + if (storageGlobalParams.journalPath.size() > 1 && + storageGlobalParams.journalPath[storageGlobalParams.journalPath.size() - 1] == '/') { + // size() check is for the unlikely possibility of --journalPath "/" + storageGlobalParams.journalPath = + storageGlobalParams.journalPath.erase(storageGlobalParams.journalPath.size() - 1); + } #endif if (params.count("operationProfiling.mode")) { @@ -972,14 +1013,33 @@ Status storeMongodOptions(const moe::Environment& params) { "****"); } + // set journal path to be dbpath/journal if it is not already explicitly set. + if (!storageGlobalParams.journalPathSetByUser) { + auto path = boost::filesystem::path(storageGlobalParams.dbpath); + path /= storageGlobalParams.kDefaultDbJournalSubdir; + storageGlobalParams.journalPath = path.string(); + } + #ifdef _WIN32 - // If dbPath is a default value, prepend with drive name so log entries are explicit - // We must resolve the dbpath before it stored in repairPath in the default case. + // If dbPath and/or journalPath are default values, prepend with drive name so log entries are + // explicit. We must resolve the dbpath before it stored in repairPath in the default case. if (storageGlobalParams.dbpath == storageGlobalParams.kDefaultDbPath || storageGlobalParams.dbpath == storageGlobalParams.kDefaultConfigDbPath) { - boost::filesystem::path currentPath = boost::filesystem::current_path(); + auto currentPath = boost::filesystem::current_path(); storageGlobalParams.dbpath = currentPath.root_name().string() + storageGlobalParams.dbpath; + + // The journal path will need the drive name if and only if the default db path is used, and + // no journal path is passed by the user. + if (!storageGlobalParams.journalPathSetByUser) { + storageGlobalParams.journalPath = + currentPath.root_name().string() + storageGlobalParams.journalPath; + } } + + // The journal path passed to wired tiger must ONLY contain forward slashes. + auto path = boost::filesystem::path(storageGlobalParams.journalPath); + storageGlobalParams.journalPath = path.generic_string(); + #endif // Check if we are 32 bit and have not explicitly specified any journaling options diff --git a/src/mongo/db/storage/storage_engine_init.cpp b/src/mongo/db/storage/storage_engine_init.cpp index dca1ff7f032..85fec8536ad 100644 --- a/src/mongo/db/storage/storage_engine_init.cpp +++ b/src/mongo/db/storage/storage_engine_init.cpp @@ -172,6 +172,15 @@ void initializeStorageEngine(ServiceContext* service, const StorageEngineInitFla // Validate options in metadata against current startup options. if (metadata.get()) { uassertStatusOK(factory->validateMetadata(*metadata, storageGlobalParams)); + + // If '--nojournal' is given and there is a journalPath on the metadata, update the params + // filepath to ensure WT cleans up the journal directory. Otherwise if no file path exists + // in the metadata, leave it as is. + BSONObj metaDataOptions = metadata->getStorageEngineOptions(); + BSONElement journalPathElement = metaDataOptions.getField("journalPath"); + if (!storageGlobalParams.dur && !journalPathElement.eoo()) { + storageGlobalParams.journalPath = journalPathElement.String(); + } } ScopeGuard guard = MakeGuard([&] { @@ -195,10 +204,15 @@ void initializeStorageEngine(ServiceContext* service, const StorageEngineInitFla invariant(!storageGlobalParams.readOnly); metadata.reset(new StorageEngineMetadata(storageGlobalParams.dbpath)); metadata->setStorageEngine(factory->getCanonicalName().toString()); - metadata->setStorageEngineOptions(factory->createMetadataOptions(storageGlobalParams)); - uassertStatusOK(metadata->write()); } + // Update the existing metadata file if journalPath was changed. If journalPath existed but + // mongod is started with --nojournal, then journalPath is removed from the metadata file. + // If journalPath did not exist, and mongod is started with a specific journal path, then it + // is added. + metadata->setStorageEngineOptions(factory->createMetadataOptions(storageGlobalParams)); + uassertStatusOK(metadata->write()); + guard.Dismiss(); _supportsDocLocking = service->getStorageEngine()->supportsDocLocking(); diff --git a/src/mongo/db/storage/storage_engine_metadata.cpp b/src/mongo/db/storage/storage_engine_metadata.cpp index 7d1e3af9e41..51632f6ec22 100644 --- a/src/mongo/db/storage/storage_engine_metadata.cpp +++ b/src/mongo/db/storage/storage_engine_metadata.cpp @@ -50,6 +50,7 @@ #include "mongo/base/data_type_validated.h" #include "mongo/db/bson/dotted_path_support.h" #include "mongo/db/jsobj.h" +#include "mongo/db/storage/storage_options.h" #include "mongo/rpc/object_check.h" #include "mongo/util/assert_util.h" #include "mongo/util/file.h" @@ -313,6 +314,34 @@ Status StorageEngineMetadata::write() const { return Status::OK(); } +Status StorageEngineMetadata::validateJournalPath(const StorageGlobalParams& params) const { + BSONElement journalPathMetadataElement = _storageEngineOptions.getField("journalPath"); + if (journalPathMetadataElement.eoo()) + return Status::OK(); + + if (journalPathMetadataElement.type() != mongo::String) { + return Status(ErrorCodes::FailedToParse, + str::stream() << "Expected string field journalPath but got " + << typeName(journalPathMetadataElement.type()) + << " instead: " + << journalPathMetadataElement); + } + + if (params.dur) { + if (journalPathMetadataElement.String() != params.journalPath) { + return Status(ErrorCodes::InvalidOptions, + str::stream() << "Requested option conflicts with current storage engine " + "option for journalPath; you requested " + << params.journalPath + << " but the current server storage is already set to " + << journalPathMetadataElement.String() + << " and cannot be changed"); + } + } + + return Status::OK(); +} + template <> Status StorageEngineMetadata::validateStorageEngineOption<bool>( StringData fieldName, bool expectedValue, boost::optional<bool> defaultValue) const { diff --git a/src/mongo/db/storage/storage_engine_metadata.h b/src/mongo/db/storage/storage_engine_metadata.h index a929397cf48..486da7ce844 100644 --- a/src/mongo/db/storage/storage_engine_metadata.h +++ b/src/mongo/db/storage/storage_engine_metadata.h @@ -35,6 +35,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" #include "mongo/db/jsobj.h" +#include "mongo/db/storage/storage_options.h" namespace mongo { @@ -104,8 +105,15 @@ public: Status write() const; /** - * Validates a single field in the storage engine options. Currently, only boolean fields are - * supported. If the 'fieldName' does not exist in the 'storage.bson' file and a + * Validates the journalPath field only if the durability flag is set, otherwise it just returns + * Status::OK. If it does not exist in the 'storage.bson' file, then any journalPath works, + * otherwise the passed in path must match the one in 'storage.bson' file. + */ + Status validateJournalPath(const StorageGlobalParams& params) const; + + /** + * Validates a single field in the storage engine options. Currently, only boolean fields + * are supported. If the 'fieldName' does not exist in the 'storage.bson' file and a * 'defaultValue' is passed in, the 'expectedValue' must match the 'defaultValue'. */ template <typename T> diff --git a/src/mongo/db/storage/storage_options.cpp b/src/mongo/db/storage/storage_options.cpp index dfbb7766562..a02d808abd9 100644 --- a/src/mongo/db/storage/storage_options.cpp +++ b/src/mongo/db/storage/storage_options.cpp @@ -44,6 +44,8 @@ void StorageGlobalParams::reset() { engine = "wiredTiger"; engineSetByUser = false; dbpath = kDefaultDbPath; + journalPath = std::string(kDefaultDbPath) + std::string(kDefaultDbJournalSubdir); + journalPathSetByUser = false; upgrade = false; repair = false; @@ -60,14 +62,16 @@ void StorageGlobalParams::reset() { StorageGlobalParams storageGlobalParams; /** - * The directory where the mongod instance stores its data. + * The directories where the mongod instance stores its data and journaling files. */ #ifdef _WIN32 const char* StorageGlobalParams::kDefaultDbPath = "\\data\\db\\"; const char* StorageGlobalParams::kDefaultConfigDbPath = "\\data\\configdb\\"; +const char* StorageGlobalParams::kDefaultDbJournalSubdir = "journal\\"; #else const char* StorageGlobalParams::kDefaultDbPath = "/data/db"; const char* StorageGlobalParams::kDefaultConfigDbPath = "/data/configdb"; +const char* StorageGlobalParams::kDefaultDbJournalSubdir = "/journal"; #endif const int StorageGlobalParams::kMaxJournalCommitIntervalMs = 500; diff --git a/src/mongo/db/storage/storage_options.h b/src/mongo/db/storage/storage_options.h index 8d18506053c..c269493026c 100644 --- a/src/mongo/db/storage/storage_options.h +++ b/src/mongo/db/storage/storage_options.h @@ -64,6 +64,16 @@ struct StorageGlobalParams { // The directory where the mongod instance stores its data. std::string dbpath; + // --journalPath + // Default journal sub-directory under the <dbpath> directory. + static const char* kDefaultDbJournalSubdir; + + // The directory where the mongod instance stores its journaling data. + std::string journalPath; + + // True if --journalPath was passed on the command line, and false otherwise. + bool journalPathSetByUser; + // --upgrade // Upgrades the on-disk data format of the files specified by the --dbpath to the // latest version, if needed. diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp index 9290b113405..df62aa2dbd2 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp @@ -104,6 +104,7 @@ public: WiredTigerKVEngine* kv = new WiredTigerKVEngine(getCanonicalName().toString(), params.dbpath, + params.journalPath, getGlobalServiceContext()->getFastClockSource(), wiredTigerGlobalOptions.engineConfig, cacheMB, @@ -144,6 +145,11 @@ public: return status; } + status = metadata.validateJournalPath(params); + if (!status.isOK()) { + return status; + } + status = metadata.validateStorageEngineOption("directoryForIndexes", wiredTigerGlobalOptions.directoryForIndexes); if (!status.isOK()) { @@ -171,6 +177,9 @@ public: builder.appendBool("directoryPerDB", params.directoryperdb); builder.appendBool("directoryForIndexes", wiredTigerGlobalOptions.directoryForIndexes); builder.appendBool("groupCollections", params.groupCollections); + if (params.journalPathSetByUser) + builder.append("journalPath", params.journalPath); + return builder.obj(); } diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp index 94eea809a29..968852b41e0 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp @@ -447,6 +447,7 @@ stdx::function<bool(StringData)> initRsOplogBackgroundThreadCallback = [](String WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, const std::string& path, + const std::string& journalPath, ClockSource* cs, const std::string& extraOpenOptions, size_t cacheSizeMB, @@ -458,19 +459,20 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, _oplogManager(stdx::make_unique<WiredTigerOplogManager>()), _canonicalName(canonicalName), _path(path), + _journalPath(journalPath), _sizeStorerSyncTracker(cs, 100000, Seconds(60)), _durable(durable), _ephemeral(ephemeral), _inRepairMode(repair), _readOnly(readOnly) { - boost::filesystem::path journalPath = path; - journalPath /= "journal"; + boost::filesystem::path boostJournalPath = _journalPath; if (_durable) { - if (!boost::filesystem::exists(journalPath)) { + if (!boost::filesystem::exists(boostJournalPath)) { try { - boost::filesystem::create_directory(journalPath); - } catch (std::exception& e) { - log() << "error creating journal dir " << journalPath.string() << ' ' << e.what(); + boost::filesystem::create_directories(boostJournalPath); + } catch (const std::exception& e) { + error() << "error creating journal dir " << boostJournalPath.string() << ' ' + << e.what(); throw; } } @@ -493,9 +495,10 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, // The setting may have a later setting override it if not using the journal. We make it // unconditional here because even nojournal may need this setting if it is a transition // from using the journal. + + // If we're readOnly skip all WAL-related settings. if (!_readOnly) { - // If we're readOnly skip all WAL-related settings. - ss << "log=(enabled=true,archive=true,path=journal,compressor="; + ss << "log=(enabled=true,archive=true,path=\"" << _journalPath << "\",compressor="; ss << wiredTigerGlobalOptions.journalCompressor << "),"; ss << "file_manager=(close_idle_time=100000),"; //~28 hours, will put better fix in 3.1.x ss << "statistics_log=(wait=" << wiredTigerGlobalOptions.statisticsLogDelaySecs << "),"; @@ -518,7 +521,7 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, // If we started without the journal, but previously used the journal then open with the // WT log enabled to perform any unclean shutdown recovery and then close and reopen in // the normal path without the journal. - if (boost::filesystem::exists(journalPath)) { + if (boost::filesystem::exists(boostJournalPath)) { string config = ss.str(); log() << "Detected WT journal files. Running recovery from last checkpoint."; log() << "journal to nojournal transition config: " << config; @@ -533,9 +536,10 @@ WiredTigerKVEngine::WiredTigerKVEngine(const std::string& canonicalName, invariantWTOK(_conn->close(_conn, NULL)); // After successful recovery, remove the journal directory. try { - boost::filesystem::remove_all(journalPath); + boost::filesystem::remove_all(boostJournalPath); } catch (std::exception& e) { - error() << "error removing journal dir " << journalPath.string() << ' ' << e.what(); + error() << "error removing journal dir " << boostJournalPath.string() << ' ' + << e.what(); throw; } } @@ -844,8 +848,7 @@ StatusWith<std::vector<std::string>> WiredTigerKVEngine::beginNonBlockingBackup( auto filePath = dbPath; if (name.find(wiredTigerLogFilePrefix) == 0) { - // TODO SERVER-13455:replace `journal/` with the configurable journal path. - filePath /= boost::filesystem::path("journal"); + filePath = boost::filesystem::path(_journalPath); } filePath /= name; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h index 8367830e231..da39c10a3ec 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h @@ -69,6 +69,7 @@ public: static const int kDefaultJournalDelayMillis; WiredTigerKVEngine(const std::string& canonicalName, const std::string& path, + const std::string& rawJournalPath, ClockSource* cs, const std::string& extraOpenOptions, size_t cacheSizeGB, @@ -366,6 +367,7 @@ private: std::string _canonicalName; std::string _path; + std::string _journalPath; std::string _wtOpenConfig; std::unique_ptr<WiredTigerSizeStorer> _sizeStorer; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp index a0e0dcfc1f7..e164dde4894 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp @@ -52,7 +52,7 @@ namespace { class WiredTigerKVHarnessHelper : public KVHarnessHelper { public: WiredTigerKVHarnessHelper(bool forRepair = false) - : _dbpath("wt-kv-harness"), _forRepair(forRepair) { + : _dbpath("wt-kv-harness"), _journalPath("wt-kv-harness-journal"), _forRepair(forRepair) { if (!hasGlobalServiceContext()) setGlobalServiceContext(ServiceContext::make()); _engine.reset(makeEngine()); @@ -85,6 +85,7 @@ private: WiredTigerKVEngine* makeEngine() { return new WiredTigerKVEngine(kWiredTigerEngineName, _dbpath.path(), + _journalPath.path(), _cs.get(), "", 1, @@ -96,6 +97,7 @@ private: const std::unique_ptr<ClockSource> _cs = stdx::make_unique<ClockSourceMock>(); unittest::TempDir _dbpath; + unittest::TempDir _journalPath; std::unique_ptr<WiredTigerKVEngine> _engine; bool _forRepair; }; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_prefixed_record_store_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_prefixed_record_store_test.cpp index c205e361415..d38899586a9 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_prefixed_record_store_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_prefixed_record_store_test.cpp @@ -72,8 +72,10 @@ class PrefixedWiredTigerHarnessHelper final : public RecordStoreHarnessHelper { public: PrefixedWiredTigerHarnessHelper() : _dbpath("wt_test"), + _journalPath("wt_test_journal"), _engine(new WiredTigerKVEngine(kWiredTigerEngineName, _dbpath.path(), + _journalPath.path(), _cs.get(), "", 1, @@ -87,7 +89,8 @@ public: getGlobalServiceContext(), repl::ReplSettings()))); } - PrefixedWiredTigerHarnessHelper(StringData extraStrings) : _dbpath("wt_test") {} + PrefixedWiredTigerHarnessHelper(StringData extraStrings) + : _dbpath("wt_test"), _journalPath("wt_test_journal") {} virtual std::unique_ptr<RecordStore> newNonCappedRecordStore() { return newNonCappedRecordStore("a.b"); @@ -190,6 +193,7 @@ public: private: unittest::TempDir _dbpath; + unittest::TempDir _journalPath; const std::unique_ptr<ClockSource> _cs = stdx::make_unique<ClockSourceMock>(); std::unique_ptr<WiredTigerKVEngine> _engine; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp index f5c0688dd15..83cd9a47c2c 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp @@ -48,8 +48,10 @@ class WiredTigerRecoveryUnitHarnessHelper final : public RecoveryUnitHarnessHelp public: WiredTigerRecoveryUnitHarnessHelper() : _dbpath("wt_test"), + _journalPath("wt_test_journal"), _engine(kWiredTigerEngineName, // .canonicalName _dbpath.path(), // .path + _journalPath.path(), // .journalPath &_cs, // .cs "", // .extraOpenOptions 1, // .cacheSizeGB @@ -111,6 +113,7 @@ public: private: unittest::TempDir _dbpath; + unittest::TempDir _journalPath; ClockSourceMock _cs; WiredTigerKVEngine _engine; }; 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 b914b4d6d57..01d3211334d 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 @@ -73,8 +73,10 @@ public: WiredTigerHarnessHelper(StringData extraStrings) : _dbpath("wt_test"), + _journalPath("wt_test_journal"), _engine(kWiredTigerEngineName, _dbpath.path(), + _journalPath.path(), &_cs, extraStrings.toString(), 1, @@ -187,6 +189,7 @@ public: private: unittest::TempDir _dbpath; + unittest::TempDir _journalPath; ClockSourceMock _cs; WiredTigerKVEngine _engine; diff --git a/src/mongo/dbtests/framework_options.cpp b/src/mongo/dbtests/framework_options.cpp index d9929c5842c..ae25c038b49 100644 --- a/src/mongo/dbtests/framework_options.cpp +++ b/src/mongo/dbtests/framework_options.cpp @@ -50,8 +50,9 @@ namespace mongo { namespace { -// This specifies default dbpath for our testing framework +// This specifies default dbpath and journalPath for our testing framework const std::string default_test_dbpath = "/tmp/unittest"; +const std::string default_test_journalpath_subdir = "journal"; } // namespace @@ -74,6 +75,12 @@ Status addTestFrameworkOptions(moe::OptionSection* options) { "be overwritten if it already exists") .setDefault(moe::Value(default_test_dbpath)); + options->addOptionChaining("journalPath", + "journalPath", + moe::String, + "db journal path for this test run. NOTE: the contents of this " + "directory will be overwritten if it already exists"); + options->addOptionChaining("debug", "debug", moe::Switch, "run tests with verbose output"); options->addOptionChaining("list", "list,l", moe::Switch, "list available test suites"); @@ -137,6 +144,13 @@ Status storeTestFrameworkOptions(const moe::Environment& params, const std::vector<std::string>& args) { if (params.count("dbpath")) { frameworkGlobalParams.dbpathSpec = params["dbpath"].as<string>(); + auto path = boost::filesystem::path(frameworkGlobalParams.dbpathSpec); + path /= default_test_journalpath_subdir; + frameworkGlobalParams.journalPathSpec = path.string(); + } + + if (params.count("journalPath")) { + frameworkGlobalParams.journalPathSpec = params["journalPath"].as<string>(); } if (params.count("seed")) { @@ -157,6 +171,30 @@ Status storeTestFrameworkOptions(const moe::Environment& params, } boost::filesystem::path p(frameworkGlobalParams.dbpathSpec); + boost::filesystem::path journalPath(frameworkGlobalParams.journalPathSpec); + + /* remove the contents of the journal directory if it exists. */ + try { + if (boost::filesystem::exists(journalPath)) { + if (!boost::filesystem::is_directory(journalPath)) { + StringBuilder sb; + sb << "ERROR: path \"" << p.string() << "\" is not a directory"; + sb << getTestFrameworkHelp(args[0], moe::startupOptions); + return Status(ErrorCodes::BadValue, sb.str()); + } + boost::filesystem::directory_iterator end_iter; + for (boost::filesystem::directory_iterator dir_iter(journalPath); dir_iter != end_iter; + ++dir_iter) { + boost::filesystem::remove_all(*dir_iter); + } + } else { + boost::filesystem::create_directories(journalPath); + } + } catch (const boost::filesystem::filesystem_error& e) { + StringBuilder sb; + sb << "boost::filesystem threw exception: " << e.what(); + return Status(ErrorCodes::BadValue, sb.str()); + } /* remove the contents of the test directory if it exists. */ try { @@ -184,7 +222,11 @@ Status storeTestFrameworkOptions(const moe::Environment& params, DEV log() << "DEBUG build" << endl; string dbpathString = p.string(); + // For both Windows and Mac, wiredtiger requires forward slashes in the journal path. Using + // 'generic_string' forces that conversion. + string journalPathString = journalPath.generic_string(); storageGlobalParams.dbpath = dbpathString.c_str(); + storageGlobalParams.journalPath = journalPathString.c_str(); storageGlobalParams.engine = params["storage.engine"].as<string>(); diff --git a/src/mongo/dbtests/framework_options.h b/src/mongo/dbtests/framework_options.h index 3009a0c7f8a..d6b684cb30f 100644 --- a/src/mongo/dbtests/framework_options.h +++ b/src/mongo/dbtests/framework_options.h @@ -47,6 +47,7 @@ struct FrameworkGlobalParams { unsigned long long seed; int runsPerTest; std::string dbpathSpec; + std::string journalPathSpec; std::vector<std::string> suites; std::string filter; }; |