summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Saltz <matthew.saltz@mongodb.com>2018-06-14 23:56:22 -0400
committerMatthew Saltz <matthew.saltz@mongodb.com>2018-06-21 16:26:43 -0400
commit1f490e309da23a6c2dff567c1f8dc2751934fb13 (patch)
treea4cf1b46b7d351604eeb9ed252a17754de9969d5
parent2cbfb0732afa5b8bc86cbb3b9e1ebfdc144340e1 (diff)
downloadmongo-1f490e309da23a6c2dff567c1f8dc2751934fb13.tar.gz
SERVER-35612 Implement ChunkWritesTracker with unit tests, make ChunkInfo use it
-rw-r--r--src/mongo/db/s/shard_server_op_observer.cpp6
-rw-r--r--src/mongo/s/SConscript41
-rw-r--r--src/mongo/s/chunk.cpp25
-rw-r--r--src/mongo/s/chunk.h22
-rw-r--r--src/mongo/s/chunk_split_state_driver.cpp89
-rw-r--r--src/mongo/s/chunk_split_state_driver.h131
-rw-r--r--src/mongo/s/chunk_split_state_driver_test.cpp142
-rw-r--r--src/mongo/s/chunk_writes_tracker.cpp68
-rw-r--r--src/mongo/s/chunk_writes_tracker.h99
-rw-r--r--src/mongo/s/chunk_writes_tracker_test.cpp143
-rw-r--r--src/mongo/s/write_ops/cluster_write.cpp9
11 files changed, 740 insertions, 35 deletions
diff --git a/src/mongo/db/s/shard_server_op_observer.cpp b/src/mongo/db/s/shard_server_op_observer.cpp
index 9b8ba53ae89..20508022dbf 100644
--- a/src/mongo/db/s/shard_server_op_observer.cpp
+++ b/src/mongo/db/s/shard_server_op_observer.cpp
@@ -145,11 +145,7 @@ bool shouldSplitChunk(OperationContext* opCtx,
const auto balancerConfig = Grid::get(opCtx)->getBalancerConfiguration();
invariant(balancerConfig);
- const auto& keyPattern = shardKeyPattern.getKeyPattern();
- const bool minIsInf = (0 == keyPattern.globalMin().woCompare(chunk.getMin()));
- const bool maxIsInf = (0 == keyPattern.globalMax().woCompare(chunk.getMax()));
-
- return chunk.shouldSplit(balancerConfig->getMaxChunkSizeBytes(), minIsInf, maxIsInf);
+ return chunk.shouldSplit(balancerConfig->getMaxChunkSizeBytes());
}
/**
diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript
index bfce3ffc687..bda3163ced7 100644
--- a/src/mongo/s/SConscript
+++ b/src/mongo/s/SConscript
@@ -64,6 +64,7 @@ env.Library(
'$BUILD_DIR/mongo/db/storage/key_string',
'$BUILD_DIR/mongo/db/update/update_common',
'$BUILD_DIR/mongo/util/concurrency/ticketholder',
+ 'chunk_writes_tracker',
'common_s',
],
)
@@ -363,6 +364,46 @@ env.CppUnitTest(
)
env.Library(
+ target='chunk_writes_tracker',
+ source=[
+ 'chunk_writes_tracker.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ],
+)
+
+env.CppUnitTest(
+ target='chunk_writes_tracker_test',
+ source=[
+ 'chunk_writes_tracker_test.cpp',
+ ],
+ LIBDEPS=[
+ 'chunk_writes_tracker',
+ ]
+)
+
+env.Library(
+ target='chunk_split_state_driver',
+ source=[
+ 'chunk_split_state_driver.cpp',
+ ],
+ LIBDEPS=[
+ 'chunk_writes_tracker'
+ ],
+)
+
+env.CppUnitTest(
+ target='chunk_split_state_driver_test',
+ source=[
+ 'chunk_split_state_driver_test.cpp',
+ ],
+ LIBDEPS=[
+ 'chunk_split_state_driver',
+ ]
+)
+
+env.Library(
target='catalog_cache_test_fixture',
source=[
'catalog_cache_test_fixture.cpp',
diff --git a/src/mongo/s/chunk.cpp b/src/mongo/s/chunk.cpp
index b32c6d375f4..2bc6c8748ac 100644
--- a/src/mongo/s/chunk.cpp
+++ b/src/mongo/s/chunk.cpp
@@ -33,6 +33,7 @@
#include "mongo/s/chunk.h"
#include "mongo/platform/random.h"
+#include "mongo/s/chunk_writes_tracker.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
@@ -43,7 +44,7 @@ PseudoRandom prng(static_cast<int64_t>(time(0)));
// Assume user has 64MB chunkSize setting. It is ok if this assumption is wrong since it is only
// a heuristic.
-const int64_t kMaxDataWritten = 64 / Chunk::kSplitTestFactor;
+const int64_t kMaxDataWritten = 64 / ChunkWritesTracker::kSplitTestFactor;
/**
* Generates a random value for _dataWritten so that a mongos restart wouldn't cause delay in
@@ -61,11 +62,12 @@ ChunkInfo::ChunkInfo(const ChunkType& from)
_lastmod(from.getVersion()),
_history(from.getHistory()),
_jumbo(from.getJumbo()),
- _dataWritten(mkDataWritten()) {
+ _writesTracker(std::make_shared<ChunkWritesTracker>()) {
invariant(from.validate());
if (!_history.empty()) {
invariant(_shardId == _history.front().getShard());
}
+ _writesTracker->addBytesWritten(mkDataWritten());
}
const ShardId& ChunkInfo::getShardIdAt(const boost::optional<Timestamp>& ts) const {
@@ -97,27 +99,20 @@ bool ChunkInfo::containsKey(const BSONObj& shardKey) const {
}
uint64_t ChunkInfo::getBytesWritten() const {
- return _dataWritten;
+ return _writesTracker->getBytesWritten();
}
-uint64_t ChunkInfo::addBytesWritten(uint64_t bytesWrittenIncrement) {
- _dataWritten += bytesWrittenIncrement;
- return _dataWritten;
+void ChunkInfo::addBytesWritten(uint64_t bytesWrittenIncrement) {
+ _writesTracker->addBytesWritten(bytesWrittenIncrement);
}
void ChunkInfo::clearBytesWritten() {
- _dataWritten = 0;
+ _writesTracker->clearBytesWritten();
}
-bool ChunkInfo::shouldSplit(uint64_t desiredChunkSize, bool minIsInf, bool maxIsInf) const {
- // If this chunk is at either end of the range, trigger auto-split at 10% less data written in
- // order to trigger the top-chunk optimization.
- const uint64_t splitThreshold = (minIsInf || maxIsInf)
- ? static_cast<uint64_t>((double)desiredChunkSize * 0.9)
- : desiredChunkSize;
-
+bool ChunkInfo::shouldSplit(uint64_t maxChunkSize) const {
// Check if there are enough estimated bytes written to warrant a split
- return _dataWritten >= splitThreshold / Chunk::kSplitTestFactor;
+ return _writesTracker->shouldSplit(maxChunkSize);
}
std::string ChunkInfo::toString() const {
diff --git a/src/mongo/s/chunk.h b/src/mongo/s/chunk.h
index 34a2e02b57d..4e26d006402 100644
--- a/src/mongo/s/chunk.h
+++ b/src/mongo/s/chunk.h
@@ -35,6 +35,7 @@
namespace mongo {
class BSONObj;
+class ChunkWritesTracker;
/**
* Represents a cache entry for a single Chunk. Owned by a RoutingTableHistory.
@@ -80,10 +81,10 @@ public:
* Get/increment/set the estimation of how much data was written for this chunk.
*/
uint64_t getBytesWritten() const;
- uint64_t addBytesWritten(uint64_t bytesWrittenIncrement);
+ void addBytesWritten(uint64_t bytesWrittenIncrement);
void clearBytesWritten();
- bool shouldSplit(uint64_t desiredChunkSize, bool minIsInf, bool maxIsInf) const;
+ bool shouldSplit(uint64_t desiredChunkSize) const;
/**
* Marks this chunk as jumbo. Only moves from false to true once and is used by the balancer.
@@ -103,15 +104,14 @@ private:
// split
mutable bool _jumbo;
- // Statistics for the approximate data written to this chunk
- mutable uint64_t _dataWritten;
+ // Used for tracking writes to this chunk, to estimate its size for the autosplitter. Since
+ // ChunkInfo obejcts are always treated as const, and this contains metadata about the chunk
+ // that needs to change, it's okay (and necessary) to mark it mutable
+ mutable std::shared_ptr<ChunkWritesTracker> _writesTracker;
};
class Chunk {
public:
- // Test whether we should split once data * kSplitTestFactor > chunkSize (approximately)
- static const uint64_t kSplitTestFactor = 5;
-
Chunk(ChunkInfo& chunkInfo, const boost::optional<Timestamp>& atClusterTime)
: _chunkInfo(chunkInfo), _atClusterTime(atClusterTime) {}
@@ -160,15 +160,15 @@ public:
uint64_t getBytesWritten() const {
return _chunkInfo.getBytesWritten();
}
- uint64_t addBytesWritten(uint64_t bytesWrittenIncrement) {
- return _chunkInfo.addBytesWritten(bytesWrittenIncrement);
+ void addBytesWritten(uint64_t bytesWrittenIncrement) {
+ _chunkInfo.addBytesWritten(bytesWrittenIncrement);
}
void clearBytesWritten() {
_chunkInfo.clearBytesWritten();
}
- bool shouldSplit(uint64_t desiredChunkSize, bool minIsInf, bool maxIsInf) const {
- return _chunkInfo.shouldSplit(desiredChunkSize, minIsInf, maxIsInf);
+ bool shouldSplit(uint64_t desiredChunkSize) const {
+ return _chunkInfo.shouldSplit(desiredChunkSize);
}
/**
diff --git a/src/mongo/s/chunk_split_state_driver.cpp b/src/mongo/s/chunk_split_state_driver.cpp
new file mode 100644
index 00000000000..707033d6961
--- /dev/null
+++ b/src/mongo/s/chunk_split_state_driver.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General Public License in all respects for
+ * all of the code used source 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/s/chunk_split_state_driver.h"
+
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+
+boost::optional<ChunkSplitStateDriver> ChunkSplitStateDriver::tryInitiateSplit(
+ std::shared_ptr<ChunkWritesTracker> writesTracker) {
+ bool acquiredSplitLock = writesTracker->acquireSplitLock();
+ return acquiredSplitLock
+ ? boost::optional<ChunkSplitStateDriver>(ChunkSplitStateDriver(writesTracker))
+ : boost::none;
+}
+
+ChunkSplitStateDriver::ChunkSplitStateDriver(ChunkSplitStateDriver&& source) {
+ _writesTracker = source._writesTracker;
+ source._writesTracker.reset();
+ _splitState = source._splitState;
+ // Ensure that the source driver does not cancel the ongoing split
+ source._splitState = SplitState::kNotSplitting;
+}
+
+ChunkSplitStateDriver::~ChunkSplitStateDriver() {
+ if (_splitState == SplitState::kSplitInProgress || _splitState == SplitState::kSplitPrepared) {
+ cancelSplit();
+ }
+}
+
+void ChunkSplitStateDriver::prepareSplit() {
+ invariant(_splitState == SplitState::kSplitInProgress);
+ _splitState = SplitState::kSplitPrepared;
+
+ _stashedBytesWritten = _getWritesTracker()->getBytesWritten();
+ _getWritesTracker()->clearBytesWritten();
+}
+
+void ChunkSplitStateDriver::commitSplit() {
+ invariant(_splitState == SplitState::kSplitPrepared);
+ _splitState = SplitState::kSplitCommitted;
+}
+
+void ChunkSplitStateDriver::cancelSplit() {
+ invariant(_splitState == SplitState::kSplitInProgress ||
+ _splitState == SplitState::kSplitPrepared);
+ _splitState = SplitState::kNotSplitting;
+
+ _getWritesTracker()->releaseSplitLock();
+ _getWritesTracker()->addBytesWritten(_stashedBytesWritten);
+}
+
+std::shared_ptr<ChunkWritesTracker> ChunkSplitStateDriver::_getWritesTracker() {
+ auto wt = _writesTracker.lock();
+ invariant(wt, "ChunkWritesTracker was destructed before ChunkSplitStateDriver");
+ return wt;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_split_state_driver.h b/src/mongo/s/chunk_split_state_driver.h
new file mode 100644
index 00000000000..427f3cda056
--- /dev/null
+++ b/src/mongo/s/chunk_split_state_driver.h
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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 <boost/optional.hpp>
+#include <memory>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/s/chunk_writes_tracker.h"
+
+
+namespace mongo {
+
+/**
+ * Drives state transitions and the status of a ChunkWritesTracker for a chunk
+ * while it is splitting. Supports cancelability so that we don't lose
+ * information from the writes tracker if the split errors. Should be created
+ * when it is decided that a split should be performed and then passed along to
+ * the ChunkSplitter which will drive these state changes.
+ */
+class ChunkSplitStateDriver {
+ MONGO_DISALLOW_COPYING(ChunkSplitStateDriver);
+
+public:
+ /**
+ * Tries to lock the writesTracker for splitting, and if it succeeds,
+ * constructs and returns a ChunkSplitStateDriver object. If it fails due to the
+ * writesTracker already being locked, returns boost::none.
+ */
+ static boost::optional<ChunkSplitStateDriver> tryInitiateSplit(
+ std::shared_ptr<ChunkWritesTracker> writesTracker);
+
+ /**
+ * Sets the other pointer back to default initialized state so that it
+ * doesn't try to cancel any ongoing split in its destructor. This
+ * constructor is required for boost::optional.
+ */
+ ChunkSplitStateDriver(ChunkSplitStateDriver&& other);
+
+ /**
+ * Not needed.
+ */
+ ChunkSplitStateDriver& operator=(ChunkSplitStateDriver&&) = delete;
+
+ /**
+ * If there's an ongoing split, cancels it. Otherwise does nothing.
+ */
+ ~ChunkSplitStateDriver();
+
+ /**
+ * Clears the current bytes written, but stashes them in a variable in case
+ * the split is later canceled.
+ */
+ void prepareSplit();
+
+ /**
+ * Marks the split as committed, which means that shouldSplit will
+ * never again return true.
+ */
+ void commitSplit();
+
+ /**
+ * Marks the split state of an in-progress split back to kNotSplitting.
+ * If a split has already been prepared, resets the byte counter to what it
+ * was prior to prepare plus any new bytes that have been written.
+ */
+ void cancelSplit();
+
+private:
+ /**
+ * Should only be used by tryInitiateSplit
+ */
+ ChunkSplitStateDriver(std::shared_ptr<ChunkWritesTracker> writesTracker)
+ : _writesTracker(writesTracker), _splitState(SplitState::kSplitInProgress) {}
+
+ /**
+ * Pointer to the writes tracker object for which we're driving the split
+ */
+ std::weak_ptr<ChunkWritesTracker> _writesTracker;
+
+ /**
+ * Carries over result from prepare into cancelSplit.
+ */
+ uint64_t _stashedBytesWritten{0};
+
+ /**
+ * The current state of the chunk with respect to its progress being split.
+ */
+ enum class SplitState {
+ kNotSplitting,
+ kSplitInProgress,
+ kSplitPrepared,
+ kSplitCommitted,
+ } _splitState{SplitState::kNotSplitting};
+
+
+ /**
+ * Returns the ChunkWritesTracker whose state this driver is controlling,
+ * checking to make sure it has not yet been destroyed.
+ */
+ std::shared_ptr<ChunkWritesTracker> _getWritesTracker();
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_split_state_driver_test.cpp b/src/mongo/s/chunk_split_state_driver_test.cpp
new file mode 100644
index 00000000000..89042b3dbec
--- /dev/null
+++ b/src/mongo/s/chunk_split_state_driver_test.cpp
@@ -0,0 +1,142 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/s/chunk_split_state_driver.h"
+
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+
+namespace mongo {
+
+class ChunkSplitStateDriverTest : public unittest::Test {
+public:
+ // Add bytes to write tracker and create the ChunkSplitStateDriver object
+ // to test, which starts the split on the writes tracker
+ void setUp() override {
+ _writesTracker = std::make_shared<ChunkWritesTracker>();
+ uint64_t bytesToAdd{4};
+ _writesTracker->addBytesWritten(bytesToAdd);
+ _splitDriver = std::make_unique<boost::optional<ChunkSplitStateDriver>>(
+ ChunkSplitStateDriver::tryInitiateSplit(_writesTracker));
+ }
+
+ void tearDown() override {
+ _splitDriver.reset();
+ _writesTracker.reset();
+ }
+
+ ChunkWritesTracker& writesTracker() {
+ return *_writesTracker;
+ }
+
+ boost::optional<ChunkSplitStateDriver>& splitDriver() {
+ return *_splitDriver;
+ }
+
+private:
+ std::shared_ptr<ChunkWritesTracker> _writesTracker;
+ std::unique_ptr<boost::optional<ChunkSplitStateDriver>> _splitDriver;
+};
+
+TEST_F(ChunkSplitStateDriverTest, InitiateSplitLeavesBytesWrittenUnchanged) {
+ auto bytesInTracker = writesTracker().getBytesWritten();
+ ASSERT_TRUE(splitDriver());
+ ASSERT_EQ(writesTracker().getBytesWritten(), bytesInTracker);
+}
+
+TEST_F(ChunkSplitStateDriverTest, PrepareSplitClearsBytesWritten) {
+ splitDriver()->prepareSplit();
+ ASSERT_EQ(writesTracker().getBytesWritten(), 0ull);
+}
+
+TEST_F(ChunkSplitStateDriverTest, PrepareSplitFollowedByCancelSplitRestoresBytesWritten) {
+ auto bytesInTracker = writesTracker().getBytesWritten();
+ splitDriver()->prepareSplit();
+ splitDriver()->cancelSplit();
+ ASSERT_EQ(writesTracker().getBytesWritten(), bytesInTracker);
+}
+
+TEST_F(ChunkSplitStateDriverTest,
+ PrepareSplitThenAddBytesThenCancelSplitRestoresOldBytesWrittenPlusNewBytesWritten) {
+ auto bytesInTracker = writesTracker().getBytesWritten();
+ splitDriver()->prepareSplit();
+ uint64_t extraBytesToAdd{4};
+ writesTracker().addBytesWritten(extraBytesToAdd);
+ splitDriver()->cancelSplit();
+
+ ASSERT_EQ(writesTracker().getBytesWritten(), bytesInTracker + extraBytesToAdd);
+}
+
+TEST_F(ChunkSplitStateDriverTest,
+ PrepareSplitThenAddBytesThenCommitSplitLeavesNewBytesWrittenUnchanged) {
+ splitDriver()->prepareSplit();
+ uint64_t extraBytesToAdd{4};
+ writesTracker().addBytesWritten(extraBytesToAdd);
+ splitDriver()->commitSplit();
+
+ ASSERT_EQ(writesTracker().getBytesWritten(), extraBytesToAdd);
+}
+
+TEST_F(ChunkSplitStateDriverTest, ShouldSplitReturnsFalseWhenSplitHasBeenPrepared) {
+ splitDriver()->prepareSplit();
+
+ uint64_t maxChunkSize{0};
+ ASSERT_FALSE(writesTracker().shouldSplit(maxChunkSize));
+}
+
+TEST_F(ChunkSplitStateDriverTest, ShouldSplitReturnsFalseEvenAfterCommit) {
+ splitDriver()->prepareSplit();
+ splitDriver()->commitSplit();
+
+ uint64_t maxChunkSize{0};
+ ASSERT_FALSE(writesTracker().shouldSplit(maxChunkSize));
+}
+
+TEST_F(ChunkSplitStateDriverTest, ShouldSplitReturnsTrueAgainAfterCancel) {
+ splitDriver()->prepareSplit();
+ splitDriver()->cancelSplit();
+
+ uint64_t maxChunkSize{0};
+ ASSERT_TRUE(writesTracker().shouldSplit(maxChunkSize));
+}
+
+DEATH_TEST_F(ChunkSplitStateDriverTest,
+ CommitSplitWhenStartedAndNotPreparedErrors,
+ "Invariant failure") {
+ splitDriver()->commitSplit();
+}
+
+DEATH_TEST_F(ChunkSplitStateDriverTest, CancelSplitAfterCommitErrors, "Invariant failure") {
+ splitDriver()->commitSplit();
+ splitDriver()->cancelSplit();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_writes_tracker.cpp b/src/mongo/s/chunk_writes_tracker.cpp
new file mode 100644
index 00000000000..35bb0bf3fe4
--- /dev/null
+++ b/src/mongo/s/chunk_writes_tracker.cpp
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
+
+#include "mongo/platform/basic.h"
+
+#include <cstdint>
+
+#include "mongo/s/chunk_writes_tracker.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+
+void ChunkWritesTracker::clearBytesWritten() {
+ _bytesWritten.store(0);
+}
+
+bool ChunkWritesTracker::shouldSplit(uint64_t maxChunkSize) {
+ if (_isLockedForSplitting) {
+ return false;
+ }
+
+ // Check if there are enough estimated bytes written to warrant a split
+ return getBytesWritten() > maxChunkSize / ChunkWritesTracker::kSplitTestFactor;
+}
+
+bool ChunkWritesTracker::acquireSplitLock() {
+ stdx::lock_guard<stdx::mutex> lk(_mtx);
+
+ if (!_isLockedForSplitting) {
+ _isLockedForSplitting = true;
+ return true;
+ }
+ return false;
+}
+
+void ChunkWritesTracker::releaseSplitLock() {
+ invariant(_isLockedForSplitting);
+ _isLockedForSplitting = false;
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_writes_tracker.h b/src/mongo/s/chunk_writes_tracker.h
new file mode 100644
index 00000000000..0511b3bc7fa
--- /dev/null
+++ b/src/mongo/s/chunk_writes_tracker.h
@@ -0,0 +1,99 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/base/disallow_copying.h"
+#include "mongo/platform/atomic_word.h"
+#include "mongo/stdx/mutex.h"
+
+namespace mongo {
+
+class ChunkWritesTracker {
+public:
+ /**
+ * A factor that determines when a chunk should be split. We should split once data *
+ * kSplitTestFactor > chunkSize (approximately).
+ */
+ static constexpr uint64_t kSplitTestFactor = 5;
+
+ /**
+ * Add more bytes written to the chunk.
+ */
+ void addBytesWritten(uint64_t bytesWritten) {
+ _bytesWritten.fetchAndAdd(bytesWritten);
+ }
+
+ /**
+ * Returns the total number of bytes that have been written to the chunk.
+ */
+ uint64_t getBytesWritten() {
+ return _bytesWritten.loadRelaxed();
+ }
+
+ /**
+ * Sets the number of bytes in the tracker to zero.
+ */
+ void clearBytesWritten();
+
+ /**
+ * Returns whether or not this chunk is ready to be split based on the
+ * maximum allowable size of a chunk.
+ */
+ bool shouldSplit(uint64_t maxChunkSize);
+
+ /**
+ * Locks the chunk for splitting, returning false if it is already locked.
+ * While it is locked, shouldSplit will always return false.
+ */
+ bool acquireSplitLock();
+
+ /**
+ * Releases the lock acquired for splitting.
+ */
+ void releaseSplitLock();
+
+private:
+ /**
+ * The number of bytes that have been written to this chunk. May be
+ * modified concurrently by several threads.
+ */
+ AtomicUInt64 _bytesWritten{0};
+
+ /**
+ * Protects _splitState when starting a split.
+ */
+ stdx::mutex _mtx;
+
+ /**
+ * Whether or not a current split is in progress for this chunk.
+ */
+ bool _isLockedForSplitting{false};
+};
+
+} // namespace mongo
diff --git a/src/mongo/s/chunk_writes_tracker_test.cpp b/src/mongo/s/chunk_writes_tracker_test.cpp
new file mode 100644
index 00000000000..098397ca11c
--- /dev/null
+++ b/src/mongo/s/chunk_writes_tracker_test.cpp
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/s/chunk_writes_tracker.h"
+
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+
+namespace mongo {
+
+TEST(ChunkWritesTrackerTest, BytesWrittenStartsAtZero) {
+ ChunkWritesTracker wt;
+ ASSERT_EQ(wt.getBytesWritten(), 0ull);
+}
+
+TEST(ChunkWritesTrackerTest, AddBytesWrittenCorrectlyAddsBytes) {
+ ChunkWritesTracker wt;
+ uint64_t bytesToAdd{4};
+ wt.addBytesWritten(bytesToAdd);
+ ASSERT_EQ(wt.getBytesWritten(), bytesToAdd);
+}
+
+TEST(ChunkWritesTrackerTest, ClearBytesWrittenSetsBytesToZero) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ wt.clearBytesWritten();
+ ASSERT_EQ(wt.getBytesWritten(), 0ull);
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsTrueWithBytesWrittenAndMaxChunkSizeZero) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ uint64_t maxChunkSize{0};
+ ASSERT_TRUE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsFalseWithNoBytesWrittenAndMaxChunkSizeZero) {
+ ChunkWritesTracker wt;
+ uint64_t maxChunkSize{0};
+ ASSERT_FALSE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsTrueWithBytesWrittenGreaterThanMaxChunkSize) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ uint64_t maxChunkSize{3};
+ ASSERT_TRUE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsFalseWithBytesWrittenLessThanThreshold) {
+ ChunkWritesTracker wt;
+ uint64_t maxChunkSize{10};
+ auto expectedCutoff = maxChunkSize / ChunkWritesTracker::kSplitTestFactor;
+ wt.addBytesWritten(expectedCutoff - 1);
+ ASSERT_FALSE(wt.shouldSplit(maxChunkSize));
+ wt.addBytesWritten(1ul);
+ ASSERT_FALSE(wt.shouldSplit(maxChunkSize));
+ wt.addBytesWritten(1ul);
+ ASSERT_TRUE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsFalseWithBytesWrittenEqualToThreshold) {
+ ChunkWritesTracker wt;
+ uint64_t maxChunkSize{10};
+ auto expectedCutoff = maxChunkSize / ChunkWritesTracker::kSplitTestFactor;
+ wt.addBytesWritten(expectedCutoff);
+ ASSERT_FALSE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsFalseWithBytesWrittenGreaterThanThreshold) {
+ ChunkWritesTracker wt;
+ uint64_t maxChunkSize{10};
+ auto expectedCutoff = maxChunkSize / ChunkWritesTracker::kSplitTestFactor;
+ wt.addBytesWritten(expectedCutoff + 1);
+ ASSERT_TRUE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsFalseWhenSplitLockAcquired) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ wt.acquireSplitLock();
+ uint64_t maxChunkSize{0};
+ ASSERT_FALSE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, ShouldSplitReturnsTrueAfterSplitLockReleased) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ wt.acquireSplitLock();
+ wt.releaseSplitLock();
+ uint64_t maxChunkSize{0};
+ ASSERT_TRUE(wt.shouldSplit(maxChunkSize));
+}
+
+TEST(ChunkWritesTrackerTest, AcquireSplitLockReturnsFalseAfterReturningTrue) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ ASSERT_TRUE(wt.acquireSplitLock());
+ ASSERT_FALSE(wt.acquireSplitLock());
+}
+
+TEST(ChunkWritesTrackerTest, AcquireSplitLockThenReleaseThenReacquireReturnsTrue) {
+ ChunkWritesTracker wt;
+ wt.addBytesWritten(4ull);
+ wt.acquireSplitLock();
+ wt.releaseSplitLock();
+ ASSERT_TRUE(wt.acquireSplitLock());
+}
+
+DEATH_TEST(ChunkWritesTrackerTest, ReleaseSplitLockWithoutAcquiringErrors, "Invariant failure") {
+ ChunkWritesTracker wt;
+ wt.releaseSplitLock();
+}
+
+} // namespace mongo
diff --git a/src/mongo/s/write_ops/cluster_write.cpp b/src/mongo/s/write_ops/cluster_write.cpp
index 18850598cf0..19311ea39ab 100644
--- a/src/mongo/s/write_ops/cluster_write.cpp
+++ b/src/mongo/s/write_ops/cluster_write.cpp
@@ -237,7 +237,7 @@ void ClusterWriter::write(OperationContext* opCtx,
void updateChunkWriteStatsAndSplitIfNeeded(OperationContext* opCtx,
ChunkManager* manager,
Chunk chunk,
- long dataWritten) {
+ long chunkBytesWritten) {
// Disable lastError tracking so that any errors, which occur during auto-split do not get
// bubbled up on the client connection doing a write
LastError::Disabled disableLastError(&LastError::get(opCtx->getClient()));
@@ -249,11 +249,11 @@ void updateChunkWriteStatsAndSplitIfNeeded(OperationContext* opCtx,
const bool maxIsInf =
(0 == manager->getShardKeyPattern().getKeyPattern().globalMax().woCompare(chunk.getMax()));
- const uint64_t chunkBytesWritten = chunk.addBytesWritten(dataWritten);
+ chunk.addBytesWritten(chunkBytesWritten);
const uint64_t desiredChunkSize = balancerConfig->getMaxChunkSizeBytes();
- if (!chunk.shouldSplit(desiredChunkSize, minIsInf, maxIsInf)) {
+ if (!chunk.shouldSplit(desiredChunkSize)) {
return;
}
@@ -287,7 +287,8 @@ void updateChunkWriteStatsAndSplitIfNeeded(OperationContext* opCtx,
// The current desired chunk size will split the chunk into lots of small chunk and
// at the worst case this can result into thousands of chunks. So check and see if a
// bigger value can be used.
- return std::min(chunkBytesWritten, balancerConfig->getMaxChunkSizeBytes());
+ return std::min((uint64_t)chunkBytesWritten,
+ balancerConfig->getMaxChunkSizeBytes());
} else {
return desiredChunkSize;
}