diff --git a/jstests/core/timeseries/repro.js b/jstests/core/timeseries/repro.js new file mode 100644 index 00000000000..17a7e4030fb --- /dev/null +++ b/jstests/core/timeseries/repro.js @@ -0,0 +1,93 @@ +/** + * This test will print "BUG-97488" at the end if the identified behavior is still present. This is + * because it appears to fail in resmoke due to validation failing at the end. Requires the + * temporary failpoint 'hangBeforeWrite' to be enabled on master (hangs an insert / update between + * preparing a commit and performing the write). + */ + +import {configureFailPoint} from "jstests/libs/fail_point_util.js"; +import {funWithArgs} from "jstests/libs/parallel_shell_helpers.js"; + +const conn = MongoRunner.runMongod(); +const db = conn.getDB(jsTestName()); +const coll = db.coll; + +assert.commandWorked(db.createCollection(coll.getName(), { + timeseries: { + timeField: 't', + }, + collation: {locale: 'en', numericOrdering: false} // Would have min = 1 and max = 3 +})); + +// On 8.0, this failpoint already exists. +const fp = configureFailPoint(conn, 'hangBeforeWrite'); + +let insertShell = + startParallelShell(funWithArgs(function(dbName, collName) { + // Lexicographically sorted as 1, 10, 3 + assert.commandWorked(db.getSiblingDB(dbName)[collName].insertMany([ + {t: new Date(), value: "3"}, + {t: new Date(), value: "10"}, + {t: new Date(), value: "1"}, + ])); + }, db.getName(), coll.getName()), conn.port); +fp.wait(); + +coll.drop(); + +assert.commandWorked(db.createCollection(coll.getName(), { + timeseries: { + timeField: 't', + }, + collation: { + locale: 'en', + numericOrdering: true + } // Under the new collation, the colllection's minmax no longer matches the data +})); + +fp.off(); +insertShell(); + +var bucket = db.getCollection("system.buckets.coll").find({}).toArray(); + +// This bucket will have the min: 1 and max: 3 due to old lex sorting. +assert.eq(bucket.length, 1); +assert.eq(bucket[0].control.min.value, "1"); +assert.eq(bucket[0].control.max.value, "3"); + +// This filter should return the value 10, but it does not. +var result = coll.find({value: {$gte: "6"}}).toArray(); +assert.eq(result.length, 0); + +// Another insert with value 5. This will be in a new bucket. with min: 5 and max: 5 +// NB: This specific detail is only tangentially related to the bug and just serves to formalize the +// current behavior. +assert.commandWorked(coll.insert({t: new Date(), value: "5"})); +bucket = db.getCollection("system.buckets.coll").find({}).toArray(); +assert.eq(bucket.length, 2); +assert.eq(bucket[1].control.min.value, "5"); +assert.eq(bucket[1].control.max.value, "5"); +assert.eq(bucket[0].control.min.value, "1"); +assert.eq(bucket[0].control.max.value, "3"); + +// Another insert with value 2. Note this is within the range of the first bucket. However, it will +// be in the second bucket, setting it's min and max to 2 and 5. +assert.commandWorked(coll.insert({t: new Date(), value: "2"})); +bucket = db.getCollection("system.buckets.coll").find({}).toArray(); +assert.eq(bucket.length, 2); +assert.eq(bucket[1].control.min.value, "2"); +assert.eq(bucket[1].control.max.value, "5"); +assert.eq(bucket[0].control.min.value, "1"); +assert.eq(bucket[0].control.max.value, "3"); + +// Full find on the collection, sorted ascending by value. +var result = coll.find({}).sort({value: 1}).toArray(); +var expectedResults = [1, 2, 3, 5, 10].map(String); +assert.eq(result.length, expectedResults.length); +for (var i = 0; i < result.length; i++) { + assert.eq(result[i].value, expectedResults[i]); +} + +print("BUG-97488"); + +MongoRunner.stopMongod(conn); diff --git a/src/mongo/db/timeseries/write_ops/timeseries_write_ops.cpp b/src/mongo/db/timeseries/write_ops/timeseries_write_ops.cpp index 6655ae90dc2..6228d77a7ce 100644 --- a/src/mongo/db/timeseries/write_ops/timeseries_write_ops.cpp +++ b/src/mongo/db/timeseries/write_ops/timeseries_write_ops.cpp @@ -60,6 +60,7 @@ MONGO_FAIL_POINT_DEFINE(failAtomicTimeseriesWrites); MONGO_FAIL_POINT_DEFINE(failUnorderedTimeseriesInsert); MONGO_FAIL_POINT_DEFINE(hangInsertIntoBucketCatalogBeforeCheckingTimeseriesCollection); MONGO_FAIL_POINT_DEFINE(hangTimeseriesInsertBeforeCommit); +MONGO_FAIL_POINT_DEFINE(hangBeforeWrite); using TimeseriesBatches = std::vector, size_t>>; @@ -329,6 +330,8 @@ bool commitTimeseriesBucket(OperationContext* opCtx, return true; } + hangBeforeWrite.pauseWhileSet(); + const auto docId = batch->bucketId.oid; const bool performInsert = batch->numPreviouslyCommittedMeasurements == 0; if (performInsert) { @@ -446,6 +449,8 @@ bool commitTimeseriesBucketsAtomically(OperationContext* opCtx, opCtx, batch, metadata, stmtIds, nss, &insertOps, &updateOps); } + hangBeforeWrite.pauseWhileSet(); + auto result = details::performAtomicTimeseriesWrites(opCtx, insertOps, updateOps); if (!result.isOK()) {