Skip to content

Commit 83be468

Browse files
committed
fix: detect Cosmos DB on restart, not just initial sync
isCosmosDb was only set during getSnapshotLsn() which runs on initial sync. After restart (snapshot already done), the flag stayed false, causing the stream to open with MongoDB-only features that Cosmos DB rejects: $changeStreamSplitLargeEvent, showExpandedEvents, db.watch(). Extract detection into a reusable detectCosmosDb() method (idempotent, runs hello once per ChangeStream instance). Call it at the top of both initReplication() and streamChangesInternal() so detection happens before any change stream operation, regardless of whether initial sync runs. This was a production-blocking bug: every service restart after initial sync would crash the replication loop with PSYNC_S1346. Result: 25/26 Cosmos DB tests pass. The one remaining failure is "resume after restart" on storage v2 only — a storage-version-specific issue unrelated to detection.
1 parent 74798d4 commit 83be468

1 file changed

Lines changed: 21 additions & 6 deletions

File tree

modules/module-mongodb/src/replication/ChangeStream.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export class ChangeStream {
116116
private changeStreamTimeout: number;
117117

118118
private isCosmosDb = false;
119+
private cosmosDbDetected = false;
119120

120121
private keepaliveIntervalMs: number;
121122

@@ -247,15 +248,18 @@ export class ChangeStream {
247248
}
248249

249250
/**
250-
* This gets a LSN before starting a snapshot, which we can resume streaming from after the snapshot.
251-
*
252-
* This LSN can survive initial replication restarts.
251+
* Detect whether we are connected to Cosmos DB.
252+
* Must be called before any change stream operation.
253+
* Safe to call multiple times — only runs the hello command once.
253254
*/
254-
private async getSnapshotLsn(): Promise<string> {
255+
private async detectCosmosDb(): Promise<void> {
256+
if (this.cosmosDbDetected) {
257+
return;
258+
}
259+
this.cosmosDbDetected = true;
260+
255261
const hello = await this.defaultDb.command({ hello: 1 });
256-
// Basic sanity check
257262
if (hello.internal?.cosmos_versions != null) {
258-
// Example: internal: { cosmos_versions: [ '1.104-1', '1.105.0', '12.1-1' ] },
259263
this.isCosmosDb = true;
260264
this.logger.info('CosmosDB detected. CosmosDB support is experimental.');
261265
this.validatePostImagesForCosmosDb();
@@ -271,6 +275,15 @@ export class ChangeStream {
271275
'Standalone MongoDB instances are not supported - use a replicaset.'
272276
);
273277
}
278+
}
279+
280+
/**
281+
* This gets a LSN before starting a snapshot, which we can resume streaming from after the snapshot.
282+
*
283+
* This LSN can survive initial replication restarts.
284+
*/
285+
private async getSnapshotLsn(): Promise<string> {
286+
await this.detectCosmosDb();
274287

275288
// Open a change stream just to get a resume token for later use.
276289
// We could use clusterTime from the hello command, but that won't tell us if the
@@ -781,6 +794,7 @@ export class ChangeStream {
781794
}
782795

783796
async initReplication() {
797+
await this.detectCosmosDb();
784798
const result = await this.initSlot();
785799
await this.setupCheckpointsCollection();
786800
if (result.needsInitialSync) {
@@ -923,6 +937,7 @@ export class ChangeStream {
923937
}
924938

925939
async streamChangesInternal() {
940+
await this.detectCosmosDb();
926941
const transactionsReplicatedMetric = this.metrics.getCounter(ReplicationMetric.TRANSACTIONS_REPLICATED);
927942
const bytesReplicatedMetric = this.metrics.getCounter(ReplicationMetric.DATA_REPLICATED_BYTES);
928943
const chunksReplicatedMetric = this.metrics.getCounter(ReplicationMetric.CHUNKS_REPLICATED);

0 commit comments

Comments
 (0)