summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDewal Gupta <dewal.gupta@10gen.com>2018-08-01 16:17:55 -0400
committerDewal Gupta <dewal.gupta@10gen.com>2018-08-24 16:18:45 -0400
commitd3656459d016d6a1be0788acfe3276734ab59210 (patch)
tree6223b5caad85b716151297d35845614816a48e5a
parentfe2906300d0458e5421b576319b11274c56ea3c8 (diff)
downloadmongo-d3656459d016d6a1be0788acfe3276734ab59210.tar.gz
SERVER-13455 Add new configuration option for mongod to allow separate journal directory for WT
-rw-r--r--buildscripts/resmokeconfig/suites/disk_mobile.yml1
-rw-r--r--jstests/disk/journalpath.js44
-rw-r--r--jstests/noPassthrough/wt_journalpath_change.js137
-rw-r--r--src/mongo/db/db.cpp3
-rw-r--r--src/mongo/db/mongod_options.cpp76
-rw-r--r--src/mongo/db/storage/storage_engine_init.cpp18
-rw-r--r--src/mongo/db/storage/storage_engine_metadata.cpp29
-rw-r--r--src/mongo/db/storage/storage_engine_metadata.h12
-rw-r--r--src/mongo/db/storage/storage_options.cpp6
-rw-r--r--src/mongo/db/storage/storage_options.h10
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_init.cpp9
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.cpp29
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h2
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_kv_engine_test.cpp4
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_prefixed_record_store_test.cpp6
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp3
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_standard_record_store_test.cpp3
-rw-r--r--src/mongo/dbtests/framework_options.cpp44
-rw-r--r--src/mongo/dbtests/framework_options.h1
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;
};