Fix MongoServerError: Topology was destroyed in MongoDB

intermediate๐Ÿƒ MongoDB2026-03-22| Node.js (mongoose / mongodb driver), Python (pymongo), any MongoDB client โ€” MongoDB 4.xโ€“7.x

Error Message

MongoServerError: Topology was destroyed
#mongodb#topology#connection-pool#reconnect#driver

The Error

MongoServerError: Topology was destroyed

You fire a query, an insert, or an aggregation โ€” and instead of a result, you get this. The operation never ran. MongoDB's internal connection topology was torn down before (or while) your code tried to use it.

Root Cause

MongoDB drivers maintain a topology: an internal object tracking live connections, server health, and the connection pool. "Topology was destroyed" means the driver shut that object down while your code was still pointing at it.

The usual suspects:

  • You called client.close() (or mongoose.disconnect()) and then ran a query afterward โ€” classic async timing bug.
  • The process caught SIGINT / SIGTERM and the shutdown handler closed the connection while in-flight queries were still pending.
  • serverSelectionTimeoutMS expired โ€” the driver couldn't find a usable server within the timeout window (default: 30 seconds) and destroyed the topology instead of retrying.
  • A MongoClient was created inside a function or request handler and closed at the end โ€” the next call sometimes still references the old, closed instance.
  • An unhandled promise rejection crashed the app mid-operation, tearing down active connections abruptly.

Fix 1 โ€” Don't Close the Client Too Early (Most Common)

Scripts and Lambda-style functions are the biggest offenders here. A missing await lets client.close() run before the query even starts:

// BAD โ€” client closes before findOne resolves
const client = new MongoClient(uri);
await client.connect();
client.db('mydb').collection('users').findOne({ id: 1 }); // no await!
await client.close(); // topology gone before findOne completes

// GOOD โ€” await every operation before closing
const client = new MongoClient(uri);
try {
  await client.connect();
  const user = await client.db('mydb').collection('users').findOne({ id: 1 });
  console.log(user);
} finally {
  await client.close();
}

Every database call needs await. If you're using .then() chains, client.close() must be chained at the very end โ€” not alongside other operations.

Fix 2 โ€” Fix Shutdown Race Conditions

Long-running servers hit this when the process shuts down mid-request. A naive SIGTERM handler closes MongoDB immediately, leaving active queries stranded:

// BAD โ€” closes DB while HTTP requests may still be running
process.on('SIGTERM', async () => {
  await mongoose.disconnect();
  process.exit(0);
});

// GOOD โ€” drain HTTP first, then close DB
process.on('SIGTERM', async () => {
  server.close(async () => {        // stop accepting new HTTP requests
    await mongoose.disconnect();    // only now close MongoDB
    process.exit(0);
  });
});

Stop the HTTP server โ†’ wait for active connections to drain โ†’ close MongoDB. Skipping any step causes this error under real traffic.

Fix 3 โ€” Increase serverSelectionTimeoutMS

Under high load or with a slow network, the driver can time out trying to find a usable server. Once it gives up, it destroys the topology. Bump the timeout:

// Node.js โ€” mongodb driver
const client = new MongoClient(uri, {
  serverSelectionTimeoutMS: 10000,  // bump from the default 30000 if failing fast
  connectTimeoutMS: 10000,
  socketTimeoutMS: 45000,
});

# mongoose
mongoose.connect(uri, {
  serverSelectionTimeoutMS: 10000,
  connectTimeoutMS: 10000,
  socketTimeoutMS: 45000,
});

Before tweaking timeouts, verify the MongoDB server is actually reachable:

mongosh "mongodb://user:pass@host:27017/dbname" --eval "db.adminCommand({ ping: 1 })"

A successful ping rules out network issues entirely.

Fix 4 โ€” Reuse One MongoClient (Don't Create Per-Request)

Creating a fresh MongoClient per request โ€” then closing it โ€” is a surprisingly common anti-pattern. Under any concurrency, overlapping requests end up sharing a topology that one of them just closed:

// BAD โ€” new client per request, race condition waiting to happen
app.get('/users', async (req, res) => {
  const client = new MongoClient(uri);
  await client.connect();
  const users = await client.db('mydb').collection('users').find().toArray();
  await client.close(); // next overlapping request may still reference this topology
  res.json(users);
});

// GOOD โ€” one client for the whole process
const client = new MongoClient(uri);
await client.connect();

app.get('/users', async (req, res) => {
  const users = await client.db('mydb').collection('users').find().toArray();
  res.json(users);
});

MongoDB's built-in connection pool (100 connections by default) handles all the concurrency for you. One MongoClient per process is the right model.

Fix 5 โ€” Add Retry Logic for Critical Operations

Network blips happen. Wrapping critical queries in retry logic keeps transient topology errors from surfacing to users:

async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 1; attempt  setTimeout(r, 1000 * attempt)); // 1s, 2s, 3s backoff
        continue;
      }
      throw err;
    }
  }
}

// Usage
const result = await withRetry(() =>
  db.collection('orders').findOne({ _id: orderId })
);

The exponential backoff (1 s, 2 s, 3 s) gives the driver time to recover without hammering a struggling server.

Fix 6 โ€” pymongo (Python)

Python surfaces this differently โ€” usually as pymongo.errors.InvalidOperation: Cannot use MongoClient after close โ€” but the root cause is identical. The fix is the context manager:

from pymongo import MongoClient

# BAD โ€” client closed before find_one runs
client = MongoClient(uri)
db = client['mydb']
client.close()
db.users.find_one()  # topology already destroyed

# GOOD โ€” context manager closes the client only after the block exits
with MongoClient(uri) as client:
    db = client['mydb']
    result = db.users.find_one({'id': 1})
    print(result)

Verify the Fix

Don't just restart and hope. Confirm the error is actually gone:

  • Trigger the previously failing code path and check logs โ€” no more Topology was destroyed entries.
  • Check connection pool health in MongoDB Atlas metrics or directly:
db.serverStatus().connections
// { current: 5, available: 195, totalCreated: 12 }

  • Run a load test with autocannon or k6 (50โ€“200 concurrent users works well for most APIs) and confirm zero topology errors across all requests.

Prevention

  • One client per process โ€” initialize MongoClient once at startup, share it everywhere in the app.
  • Graceful shutdown order โ€” drain HTTP connections first, then call mongoose.disconnect() or client.close(). Never the other way around.
  • Await everything โ€” no MongoDB operation should fire without await or a proper .then() chain. A linter rule (no-floating-promises in TypeScript) catches this automatically.
  • Tune timeouts to match reality โ€” if your MongoDB Atlas cluster is in us-east-1 and your app runs in ap-southeast-1, a 5 s serverSelectionTimeoutMS will hurt you. Measure actual round-trip latency first.
  • Watch the connection pool โ€” alert when connections.available drops below 20. Pool exhaustion is often what triggers topology destruction under load.

Related Error Notes