Skip to content

Commit 77d99b2

Browse files
committed
SERVER-23116 Add versioning scheme to the KVCatalog.
The feature document is inserted into the KVCatalog only after the first feature is enabled on the data files.
1 parent 07b62eb commit 77d99b2

File tree

6 files changed

+931
-3
lines changed

6 files changed

+931
-3
lines changed

src/mongo/db/storage/kv/SConscript

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ env.Library(
77
'kv_collection_catalog_entry.cpp',
88
],
99
LIBDEPS=[
10+
'$BUILD_DIR/mongo/bson/util/bson_extract',
1011
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
1112
'$BUILD_DIR/mongo/db/index/index_descriptor',
1213
'$BUILD_DIR/mongo/db/index_names',
@@ -83,6 +84,7 @@ env.Library(
8384
env.Library(
8485
target='kv_engine_test_harness',
8586
source=[
87+
'kv_catalog_feature_tracker_test.cpp',
8688
'kv_engine_test_harness.cpp',
8789
'kv_engine_test_snapshots.cpp',
8890
],

src/mongo/db/storage/kv/kv_catalog.cpp

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@
3434

3535
#include <stdlib.h>
3636

37+
#include "mongo/bson/util/bson_extract.h"
38+
#include "mongo/bson/util/builder.h"
3739
#include "mongo/db/concurrency/d_concurrency.h"
3840
#include "mongo/db/namespace_string.h"
3941
#include "mongo/db/operation_context.h"
42+
#include "mongo/db/storage/kv/kv_catalog_feature_tracker.h"
4043
#include "mongo/db/storage/record_store.h"
4144
#include "mongo/db/storage/recovery_unit.h"
45+
#include "mongo/platform/bits.h"
4246
#include "mongo/platform/random.h"
4347
#include "mongo/util/log.h"
4448
#include "mongo/util/mongoutils/str.h"
@@ -51,6 +55,28 @@ namespace {
5155
//
5256
// NOTE: Must be locked *before* _identLock.
5357
const ResourceId resourceIdCatalogMetadata(RESOURCE_METADATA, 1ULL);
58+
59+
const char kIsFeatureDocumentFieldName[] = "isFeatureDoc";
60+
const char kNamespaceFieldName[] = "ns";
61+
const char kNonRepairableFeaturesFieldName[] = "nonRepairable";
62+
const char kRepairableFeaturesFieldName[] = "repairable";
63+
64+
void appendPositionsOfBitsSet(uint64_t value, StringBuilder* sb) {
65+
invariant(sb);
66+
67+
*sb << "[ ";
68+
bool firstIteration = true;
69+
while (value) {
70+
const int lowestSetBitPosition = countTrailingZeros64(value);
71+
if (!firstIteration) {
72+
*sb << ", ";
73+
}
74+
*sb << lowestSetBitPosition;
75+
value ^= (1 << lowestSetBitPosition);
76+
firstIteration = false;
77+
}
78+
*sb << " ]";
79+
}
5480
}
5581

5682
using std::unique_ptr;
@@ -87,6 +113,192 @@ class KVCatalog::RemoveIdentChange : public RecoveryUnit::Change {
87113
const Entry _entry;
88114
};
89115

116+
bool KVCatalog::FeatureTracker::isFeatureDocument(BSONObj obj) {
117+
BSONElement firstElem = obj.firstElement();
118+
if (firstElem.fieldNameStringData() == kIsFeatureDocumentFieldName) {
119+
return firstElem.booleanSafe();
120+
}
121+
return false;
122+
}
123+
124+
Status KVCatalog::FeatureTracker::isCompatibleWithCurrentCode(OperationContext* opCtx) const {
125+
FeatureBits versionInfo = getInfo(opCtx);
126+
127+
uint64_t unrecognizedNonRepairableFeatures =
128+
versionInfo.nonRepairableFeatures & ~_usedNonRepairableFeaturesMask;
129+
if (unrecognizedNonRepairableFeatures) {
130+
StringBuilder sb;
131+
sb << "The data files use features not recognized by this version of mongod; the NR feature"
132+
" bits in positions ";
133+
appendPositionsOfBitsSet(unrecognizedNonRepairableFeatures, &sb);
134+
sb << " aren't recognized by this version of mongod";
135+
return {ErrorCodes::MustUpgrade, sb.str()};
136+
}
137+
138+
uint64_t unrecognizedRepairableFeatures =
139+
versionInfo.repairableFeatures & ~_usedRepairableFeaturesMask;
140+
if (unrecognizedRepairableFeatures) {
141+
StringBuilder sb;
142+
sb << "The data files use features not recognized by this version of mongod; the R feature"
143+
" bits in positions ";
144+
appendPositionsOfBitsSet(unrecognizedRepairableFeatures, &sb);
145+
sb << " aren't recognized by this version of mongod";
146+
return {ErrorCodes::CanRepairToDowngrade, sb.str()};
147+
}
148+
149+
return Status::OK();
150+
}
151+
152+
std::unique_ptr<KVCatalog::FeatureTracker> KVCatalog::FeatureTracker::get(OperationContext* opCtx,
153+
KVCatalog* catalog,
154+
RecordId rid) {
155+
auto record = catalog->_rs->dataFor(opCtx, rid);
156+
BSONObj obj = record.toBson();
157+
invariant(isFeatureDocument(obj));
158+
return std::unique_ptr<KVCatalog::FeatureTracker>(new KVCatalog::FeatureTracker(catalog, rid));
159+
}
160+
161+
std::unique_ptr<KVCatalog::FeatureTracker> KVCatalog::FeatureTracker::create(
162+
OperationContext* opCtx, KVCatalog* catalog) {
163+
return std::unique_ptr<KVCatalog::FeatureTracker>(
164+
new KVCatalog::FeatureTracker(catalog, RecordId()));
165+
}
166+
167+
bool KVCatalog::FeatureTracker::isNonRepairableFeatureInUse(OperationContext* opCtx,
168+
NonRepairableFeature feature) const {
169+
std::unique_ptr<Lock::ResourceLock> rLk;
170+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
171+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_S));
172+
}
173+
174+
FeatureBits versionInfo = getInfo(opCtx);
175+
return versionInfo.nonRepairableFeatures & static_cast<NonRepairableFeatureMask>(feature);
176+
}
177+
178+
void KVCatalog::FeatureTracker::markNonRepairableFeatureAsInUse(OperationContext* opCtx,
179+
NonRepairableFeature feature) {
180+
std::unique_ptr<Lock::ResourceLock> rLk;
181+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
182+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_X));
183+
}
184+
185+
FeatureBits versionInfo = getInfo(opCtx);
186+
versionInfo.nonRepairableFeatures |= static_cast<NonRepairableFeatureMask>(feature);
187+
putInfo(opCtx, versionInfo);
188+
}
189+
190+
void KVCatalog::FeatureTracker::markNonRepairableFeatureAsNotInUse(OperationContext* opCtx,
191+
NonRepairableFeature feature) {
192+
std::unique_ptr<Lock::ResourceLock> rLk;
193+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
194+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_X));
195+
}
196+
197+
FeatureBits versionInfo = getInfo(opCtx);
198+
versionInfo.nonRepairableFeatures &= ~static_cast<NonRepairableFeatureMask>(feature);
199+
putInfo(opCtx, versionInfo);
200+
}
201+
202+
bool KVCatalog::FeatureTracker::isRepairableFeatureInUse(OperationContext* opCtx,
203+
RepairableFeature feature) const {
204+
std::unique_ptr<Lock::ResourceLock> rLk;
205+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
206+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_S));
207+
}
208+
209+
FeatureBits versionInfo = getInfo(opCtx);
210+
return versionInfo.repairableFeatures & static_cast<RepairableFeatureMask>(feature);
211+
}
212+
213+
void KVCatalog::FeatureTracker::markRepairableFeatureAsInUse(OperationContext* opCtx,
214+
RepairableFeature feature) {
215+
std::unique_ptr<Lock::ResourceLock> rLk;
216+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
217+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_X));
218+
}
219+
220+
FeatureBits versionInfo = getInfo(opCtx);
221+
versionInfo.repairableFeatures |= static_cast<RepairableFeatureMask>(feature);
222+
putInfo(opCtx, versionInfo);
223+
}
224+
225+
void KVCatalog::FeatureTracker::markRepairableFeatureAsNotInUse(OperationContext* opCtx,
226+
RepairableFeature feature) {
227+
std::unique_ptr<Lock::ResourceLock> rLk;
228+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
229+
rLk.reset(new Lock::ResourceLock(opCtx->lockState(), resourceIdCatalogMetadata, MODE_X));
230+
}
231+
232+
FeatureBits versionInfo = getInfo(opCtx);
233+
versionInfo.repairableFeatures &= ~static_cast<RepairableFeatureMask>(feature);
234+
putInfo(opCtx, versionInfo);
235+
}
236+
237+
KVCatalog::FeatureTracker::FeatureBits KVCatalog::FeatureTracker::getInfo(
238+
OperationContext* opCtx) const {
239+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
240+
invariant(opCtx->lockState()->isLockHeldForMode(resourceIdCatalogMetadata, MODE_S));
241+
}
242+
243+
if (_rid.isNull()) {
244+
return {};
245+
}
246+
247+
auto record = _catalog->_rs->dataFor(opCtx, _rid);
248+
BSONObj obj = record.toBson();
249+
invariant(isFeatureDocument(obj));
250+
251+
BSONElement nonRepairableFeaturesElem;
252+
auto nonRepairableFeaturesStatus = bsonExtractTypedField(
253+
obj, kNonRepairableFeaturesFieldName, BSONType::NumberLong, &nonRepairableFeaturesElem);
254+
fassert(40111, nonRepairableFeaturesStatus);
255+
256+
BSONElement repairableFeaturesElem;
257+
auto repairableFeaturesStatus = bsonExtractTypedField(
258+
obj, kRepairableFeaturesFieldName, BSONType::NumberLong, &repairableFeaturesElem);
259+
fassert(40112, repairableFeaturesStatus);
260+
261+
FeatureBits versionInfo;
262+
versionInfo.nonRepairableFeatures =
263+
static_cast<NonRepairableFeatureMask>(nonRepairableFeaturesElem.numberLong());
264+
versionInfo.repairableFeatures =
265+
static_cast<RepairableFeatureMask>(repairableFeaturesElem.numberLong());
266+
return versionInfo;
267+
}
268+
269+
void KVCatalog::FeatureTracker::putInfo(OperationContext* opCtx, const FeatureBits& versionInfo) {
270+
if (!_catalog->_isRsThreadSafe && opCtx->lockState()) {
271+
invariant(opCtx->lockState()->isLockHeldForMode(resourceIdCatalogMetadata, MODE_X));
272+
}
273+
274+
BSONObjBuilder bob;
275+
bob.appendBool(kIsFeatureDocumentFieldName, true);
276+
// We intentionally include the "ns" field with a null value in the feature document to prevent
277+
// older versions that do 'obj["ns"].String()' from starting up. This way only versions that are
278+
// aware of the feature document's existence can successfully start up.
279+
bob.appendNull(kNamespaceFieldName);
280+
bob.append(kNonRepairableFeaturesFieldName,
281+
static_cast<long long>(versionInfo.nonRepairableFeatures));
282+
bob.append(kRepairableFeaturesFieldName,
283+
static_cast<long long>(versionInfo.repairableFeatures));
284+
BSONObj obj = bob.done();
285+
286+
if (_rid.isNull()) {
287+
// This is the first time a feature is being marked as in-use or not in-use, so we must
288+
// insert the feature document rather than update it.
289+
const bool enforceQuota = false;
290+
auto rid = _catalog->_rs->insertRecord(opCtx, obj.objdata(), obj.objsize(), enforceQuota);
291+
fassert(40113, rid.getStatus());
292+
_rid = rid.getValue();
293+
} else {
294+
const bool enforceQuota = false;
295+
UpdateNotifier* notifier = nullptr;
296+
auto status = _catalog->_rs->updateRecord(
297+
opCtx, _rid, obj.objdata(), obj.objsize(), enforceQuota, notifier);
298+
fassert(40114, status);
299+
}
300+
}
301+
90302
KVCatalog::KVCatalog(RecordStore* rs,
91303
bool isRsThreadSafe,
92304
bool directoryPerDb,
@@ -132,12 +344,28 @@ void KVCatalog::init(OperationContext* opCtx) {
132344
while (auto record = cursor->next()) {
133345
BSONObj obj = record->data.releaseToBson();
134346

347+
if (FeatureTracker::isFeatureDocument(obj)) {
348+
// There should be at most one version document in the catalog.
349+
invariant(!_featureTracker);
350+
351+
// Initialize the feature tracker and skip over the version document because it doesn't
352+
// correspond to a namespace entry.
353+
_featureTracker = std::move(FeatureTracker::get(opCtx, this, record->id));
354+
continue;
355+
}
356+
135357
// No rollback since this is just loading already committed data.
136358
string ns = obj["ns"].String();
137359
string ident = obj["ident"].String();
138360
_idents[ns] = Entry(ident, record->id);
139361
}
140362

363+
if (!_featureTracker) {
364+
// If there wasn't a feature document, then just an initialize a feature tracker that
365+
// doesn't manage a feature document yet.
366+
_featureTracker = std::move(KVCatalog::FeatureTracker::create(opCtx, this));
367+
}
368+
141369
// In the unlikely event that we have used this _rand before generate a new one.
142370
while (_hasEntryCollidingWithRand()) {
143371
_rand = _newRand();

src/mongo/db/storage/kv/kv_catalog.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#pragma once
3232

3333
#include <map>
34+
#include <memory>
3435
#include <string>
3536

3637
#include "mongo/base/string_data.h"
@@ -46,6 +47,8 @@ class RecordStore;
4647

4748
class KVCatalog {
4849
public:
50+
class FeatureTracker;
51+
4952
/**
5053
* @param rs - does NOT take ownership
5154
*/
@@ -82,6 +85,11 @@ class KVCatalog {
8285

8386
bool isUserDataIdent(StringData ident) const;
8487

88+
FeatureTracker* getFeatureTracker() const {
89+
invariant(_featureTracker);
90+
return _featureTracker.get();
91+
}
92+
8593
private:
8694
class AddIdentChange;
8795
class RemoveIdentChange;
@@ -117,5 +125,9 @@ class KVCatalog {
117125
typedef std::map<std::string, Entry> NSToIdentMap;
118126
NSToIdentMap _idents;
119127
mutable stdx::mutex _identsLock;
128+
129+
// Manages the feature document that may be present in the KVCatalog. '_featureTracker' is
130+
// guaranteed to be non-null after KVCatalog::init() is called.
131+
std::unique_ptr<FeatureTracker> _featureTracker;
120132
};
121133
}

0 commit comments

Comments
 (0)