diff options
author | Matthew Saltz <matthew.saltz@mongodb.com> | 2018-06-14 23:56:22 -0400 |
---|---|---|
committer | Matthew Saltz <matthew.saltz@mongodb.com> | 2018-06-21 16:26:43 -0400 |
commit | 1f490e309da23a6c2dff567c1f8dc2751934fb13 (patch) | |
tree | a4cf1b46b7d351604eeb9ed252a17754de9969d5 | |
parent | 2cbfb0732afa5b8bc86cbb3b9e1ebfdc144340e1 (diff) | |
download | mongo-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.cpp | 6 | ||||
-rw-r--r-- | src/mongo/s/SConscript | 41 | ||||
-rw-r--r-- | src/mongo/s/chunk.cpp | 25 | ||||
-rw-r--r-- | src/mongo/s/chunk.h | 22 | ||||
-rw-r--r-- | src/mongo/s/chunk_split_state_driver.cpp | 89 | ||||
-rw-r--r-- | src/mongo/s/chunk_split_state_driver.h | 131 | ||||
-rw-r--r-- | src/mongo/s/chunk_split_state_driver_test.cpp | 142 | ||||
-rw-r--r-- | src/mongo/s/chunk_writes_tracker.cpp | 68 | ||||
-rw-r--r-- | src/mongo/s/chunk_writes_tracker.h | 99 | ||||
-rw-r--r-- | src/mongo/s/chunk_writes_tracker_test.cpp | 143 | ||||
-rw-r--r-- | src/mongo/s/write_ops/cluster_write.cpp | 9 |
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; } |