From e0dc5a093aceaf17ca880db812c704583482933f Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 14:06:05 +0100 Subject: [PATCH 1/4] feat(NODE-7452): restrict server deprioritization on replica sets to overload errors --- src/operations/execute_operation.ts | 9 +- .../retryable_reads.spec.prose.test.ts | 103 +++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/operations/execute_operation.ts b/src/operations/execute_operation.ts index b6ddbba2b6..8f43f212fa 100644 --- a/src/operations/execute_operation.ts +++ b/src/operations/execute_operation.ts @@ -17,6 +17,7 @@ import { } from '../error'; import type { MongoClient } from '../mongo_client'; import { ReadPreference } from '../read_preference'; +import { TopologyType } from '../sdam/common'; import { DeprioritizedServers, sameServerSelector, @@ -304,7 +305,13 @@ async function tryOperation { let client: MongoClient, failPointName; @@ -136,4 +142,99 @@ describe('Retryable Reads Spec Prose', () => { } }); }); + + describe('Retrying Reads in a Replica Set', () => { + // These tests verify that server deprioritization on replica sets only occurs + // for SystemOverloadedError errors. + + const TEST_METADATA: MongoDBMetadataUI = { + requires: { mongodb: '>=4.2', topology: 'replicaset' } + }; + + describe('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => { + let client: MongoClient; + const commandFailedEvents: CommandFailedEvent[] = []; + const commandSucceededEvents: CommandSucceededEvent[] = []; + + beforeEach(async function () { + client = this.configuration.newClient({ + retryReads: true, + readPreference: 'primaryPreferred', + monitorCommands: true + }); + + client.on('commandFailed', filterForCommands('find', commandFailedEvents)); + client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents)); + + await client.db('admin').command({ + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: ['find'], + errorCode: 6, + errorLabels: ['RetryableError', 'SystemOverloadedError'] + } + }); + + commandFailedEvents.length = 0; + commandSucceededEvents.length = 0; + }); + + afterEach(async function () { + await client?.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' }); + await client?.close(); + }); + + it('retries on a different server when SystemOverloadedError', TEST_METADATA, async () => { + await client.db('test').collection('test').find().toArray(); + + expect(commandFailedEvents).to.have.lengthOf(1); + expect(commandSucceededEvents).to.have.lengthOf(1); + expect(commandFailedEvents[0].address).to.not.equal(commandSucceededEvents[0].address); + }); + }); + + describe('Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Server', () => { + let client: MongoClient; + const commandFailedEvents: CommandFailedEvent[] = []; + const commandSucceededEvents: CommandSucceededEvent[] = []; + + beforeEach(async function () { + client = this.configuration.newClient({ + retryReads: true, + readPreference: 'primaryPreferred', + monitorCommands: true + }); + + client.on('commandFailed', filterForCommands('find', commandFailedEvents)); + client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents)); + + await client.db('admin').command({ + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: ['find'], + errorCode: 6, + errorLabels: ['RetryableError'] + } + }); + + commandFailedEvents.length = 0; + commandSucceededEvents.length = 0; + }); + + afterEach(async function () { + await client?.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' }); + await client?.close(); + }); + + it('retries on the same server when no SystemOverloadedError', TEST_METADATA, async () => { + await client.db('test').collection('test').find().toArray(); + + expect(commandFailedEvents).to.have.lengthOf(1); + expect(commandSucceededEvents).to.have.lengthOf(1); + expect(commandFailedEvents[0].address).to.equal(commandSucceededEvents[0].address); + }); + }); + }); }); From 61a4895ed388c2370c3dad250ad3dfd44ce87dc8 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 14:49:40 +0100 Subject: [PATCH 2/4] re-run only one test with debug log --- .../retryable-reads/retryable_reads.spec.prose.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts index 74b9ca8c3f..d6a2c75d1f 100644 --- a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts +++ b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts @@ -151,7 +151,7 @@ describe('Retryable Reads Spec Prose', () => { requires: { mongodb: '>=4.2', topology: 'replicaset' } }; - describe('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => { + describe.only('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => { let client: MongoClient; const commandFailedEvents: CommandFailedEvent[] = []; const commandSucceededEvents: CommandSucceededEvent[] = []; @@ -166,6 +166,8 @@ describe('Retryable Reads Spec Prose', () => { client.on('commandFailed', filterForCommands('find', commandFailedEvents)); client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents)); + await client.connect(); + await client.db('admin').command({ configureFailPoint: 'failCommand', mode: { times: 1 }, @@ -188,6 +190,8 @@ describe('Retryable Reads Spec Prose', () => { it('retries on a different server when SystemOverloadedError', TEST_METADATA, async () => { await client.db('test').collection('test').find().toArray(); + console.log('failed event:', JSON.stringify(commandFailedEvents[0])); + expect(commandFailedEvents).to.have.lengthOf(1); expect(commandSucceededEvents).to.have.lengthOf(1); expect(commandFailedEvents[0].address).to.not.equal(commandSucceededEvents[0].address); @@ -209,6 +213,8 @@ describe('Retryable Reads Spec Prose', () => { client.on('commandFailed', filterForCommands('find', commandFailedEvents)); client.on('commandSucceeded', filterForCommands('find', commandSucceededEvents)); + await client.connect(); + await client.db('admin').command({ configureFailPoint: 'failCommand', mode: { times: 1 }, From 10ecd5768341889968af83dc6711d6651ac1897b Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 14:58:16 +0100 Subject: [PATCH 3/4] do not serialize the entire error --- .../retryable-reads/retryable_reads.spec.prose.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts index d6a2c75d1f..773c59d11a 100644 --- a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts +++ b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts @@ -190,7 +190,14 @@ describe('Retryable Reads Spec Prose', () => { it('retries on a different server when SystemOverloadedError', TEST_METADATA, async () => { await client.db('test').collection('test').find().toArray(); - console.log('failed event:', JSON.stringify(commandFailedEvents[0])); + console.log('failed event:', { + address: commandFailedEvents[0].address, + failure: { + name: commandFailedEvents[0].failure.name, + code: (commandFailedEvents[0].failure as any).code, + labels: (commandFailedEvents[0].failure as any).errorLabels + } + }); expect(commandFailedEvents).to.have.lengthOf(1); expect(commandSucceededEvents).to.have.lengthOf(1); From 7fdd9b177bb52165bd99b5e55da85b2eb748b75e Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Fri, 20 Feb 2026 15:11:40 +0100 Subject: [PATCH 4/4] update mongodb version --- .../retryable_reads.spec.prose.test.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts index 773c59d11a..5277a1b453 100644 --- a/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts +++ b/test/integration/retryable-reads/retryable_reads.spec.prose.test.ts @@ -148,10 +148,10 @@ describe('Retryable Reads Spec Prose', () => { // for SystemOverloadedError errors. const TEST_METADATA: MongoDBMetadataUI = { - requires: { mongodb: '>=4.2', topology: 'replicaset' } + requires: { mongodb: '>=4.4', topology: 'replicaset' } }; - describe.only('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => { + describe('Retryable Reads Caused by Overload Errors Are Retried on a Different Server', () => { let client: MongoClient; const commandFailedEvents: CommandFailedEvent[] = []; const commandSucceededEvents: CommandSucceededEvent[] = []; @@ -190,15 +190,6 @@ describe('Retryable Reads Spec Prose', () => { it('retries on a different server when SystemOverloadedError', TEST_METADATA, async () => { await client.db('test').collection('test').find().toArray(); - console.log('failed event:', { - address: commandFailedEvents[0].address, - failure: { - name: commandFailedEvents[0].failure.name, - code: (commandFailedEvents[0].failure as any).code, - labels: (commandFailedEvents[0].failure as any).errorLabels - } - }); - expect(commandFailedEvents).to.have.lengthOf(1); expect(commandSucceededEvents).to.have.lengthOf(1); expect(commandFailedEvents[0].address).to.not.equal(commandSucceededEvents[0].address);