Docs Menu
Docs Home
/ /
Atlas Architecture Center
/

Guidance for Atlas Latency Reduction

The following sections list configuration choices you can make to reduce latency in your Atlas deployment.

Physical distance is the primary cause of latency. The distance between users and your application, between your application and your data, and between cluster nodes all impact system latency and application performance.

To reduce latency for read and write operations, it's crucial to place both your application and data geographically closer to users. Data-bearing Atlas nodes are server nodes within an Atlas cluster that store your database's data and handle read and write operations. To support faster data access for your application users, deploy data-bearing Atlas nodes to cloud provider regions that are geographically close to the majority of your application users.

If your application's users are distributed across multiple geographies, such as between the US and Europe, we recommend that you deploy to one or more regions in each geography to reduce latency for users in each location. To learn more about multi-region deployments, see Multi-Region Deployment Paradigm.

If your data is divided by geography, such that users in each geography access different sets of data, you can also shard your data by region or geography in order to optimize read and write performance for users in each geography. This approach allows you to handle large data sets and high throughput while ensuring data locality.

Replication is the copying of data from the primary node to secondary nodes. The following replication configuration options can be adjusted to minimize latency for read and write operations in a replica set:

  • Write Concern Levels: There is a trade-off between write latency and write durability when you configure write concern. MongoDB's default global write concern is set to majority, which requires each write operation to be replicated across the majority of voting, data-bearing nodes in your replica set before Atlas acknowledges the operation as complete and successful to the client. Setting a higher write concern increases write latency, but also improves write durability and prevents rollbacks during a replica set failover.

  • Read Concern and Read Preference: There is a trade-off between query latency, data availability, and the consistency of your query responses when you configure read concern and read preference. MongoDB's default global read concern is local, which requires read operations to read from only one node in the local replica set without waiting to confirm that data is replicated across other nodes. Whether this node is the primary or a secondary node is determined by your read preference, which Atlas sets to primary by default. This default read concern and preference combination optimizes for the lowest latency reads from the most up-to-date node in the replica set, but it also runs the risk that the primary node's data may not be durable and can potentially be rolled back during a failover, and that users will not be able to query data if there is no available primary node.

    You can change your read preference to primaryPreferred to allow read operations to read from a secondary node when there is no available primary node, or if there is a geographically closer secondary node, but this runs the risk of returning stale data if a secondary node is not up-to-date. This risk can be mitigated by increasing your write concern to ensure that more secondary nodes are up-to-date, with the trade-off that this increases your write latency.

    Important

    Keep in mind that there is the possibility of a secondary node returning stale data due to replication lag.

  • Query Timeout Limits: You can set global and operation-level query timeout limits on your deployment to reduce the amount of time your application waits for a response before timing out. This can prevent ongoing queries from negatively impacting deployment performance for long periods of time.

  • Node Election Priority: To increase the likelihood that the members in your main data center be elected primary before the members in an alternate data center during a replica set election, you can set the members[n].priority of the members in the alternate data center to be lower than that of the members in the primary data center. For example, if you deploy your cluster across the AWS regions us-east-1 (Northern Virginia) and us-west-1 (Northern California), and the majority of your users are in California, you can prioritize nodes in the AWS us-west-1 (Northern California) region to ensure that the primary node is always geographically close to the majority of your users and can respond to read and write operations with minimal latency.

  • Mirrored Reads: Mirrored reads reduce the impact of primary elections following an outage by pre-warming the caches on the secondary nodes. For more information, see Mirrored Reads.

For more guidance on implementing the best replication configuration for your needs, contact MongoDB's Professional Services.

You can increase security and further reduce latency using the following network connectivity options:

  • Private Endpoints: Private endpoints establish direct and secure connections between your application's virtual network and your Atlas cluster, potentially reducing network hops and improving latency.

  • VPC Peering: Configure VPC peering in your replica sets to allow applications to connect to peered regions. In the event of a failover, VPC peering provides a way for an application server to detect a new primary node and route traffic accordingly.

The speed at which your application accesses data contributes to latency. Good data modeling and query optimization can improve data access speeds. For example, you can:

  • Reduce Document Size: Consider shortening field names and value lengths to decrease the amount of data transferred over the network.

  • Optimize Query Patterns: Use indexes effectively to minimize the amount of data that needs to be read across regions.

Atlas provides the Real-Time Performance Panel (RTPP) to observe latency metrics for different regions. You can also implement application-level monitoring to track end-to-end latency to and from the application. Before final production deployment, we suggest conducting performance testing under the various multi-region scenarios to identify and address latency bottlenecks.

To learn more about monitoring your deployment, see Guidance for Atlas Monitoring and Alerts.

We recommend that you use a connection method built on the most current driver version for your application's programming language whenever possible. While the default connection string Atlas provides is a good place to start, you can add connection string options to your connection string to improve performance in the context of your specific application and deployment architecture.

For enterprise-level application deployments, it's especially important to tune your connection pool settings to meet user demand while minimizing operational latency. For example, you can use the minPoolSize and maxPoolSize options to adjust how and when the majority of your database client connections open, allowing you to prevent or plan for the latency spikes that come with the associated network overhead.

The extent to which you can configre these settings depends on your deployment architecture. For example, if your application deployment is leveraging single-threaded resources, like AWS Lambda, your application will only ever be able to open and use one client connection. To learn more about how and where to create and use a connection pool, and where to specify connection pool settings, see Connection Pool Overview.

The following sample application brings together key recommendations on this page to reduce data operation latency:

  • Use the Atlas-provided connection string with retryable writes, majority write concern, and default read concern.

  • Specify an operation time limit with the maxTimeMS method. For instructions on how to set maxTimeMS, refer to your specific Driver Documentation.

  • Handle errors for duplicate keys and timeouts.

The application is an HTTP API that allows clients to create or list user records. It exposes an endpoint that accepts GET and POST requests http://localhost:3000:

Method
Endpoint
Description

GET

/users

Gets a list of user names from a users collection.

POST

/users

Requires a name in the request body. Adds a new user to a users collection.

Note

The following server application uses NanoHTTPD and json which you need to add to your project as dependencies before you can run it.

1// File: App.java
2
3import java.util.Map;
4import java.util.logging.Logger;
5
6import org.bson.Document;
7import org.json.JSONArray;
8
9import com.mongodb.MongoException;
10import com.mongodb.client.MongoClient;
11import com.mongodb.client.MongoClients;
12import com.mongodb.client.MongoCollection;
13import com.mongodb.client.MongoDatabase;
14
15import fi.iki.elonen.NanoHTTPD;
16
17public class App extends NanoHTTPD {
18 private static final Logger LOGGER = Logger.getLogger(App.class.getName());
19
20 static int port = 3000;
21 static MongoClient client = null;
22
23 public App() throws Exception {
24 super(port);
25
26 // Replace the uri string with your MongoDB deployment's connection string
27 String uri = "<atlas-connection-string>";
28 client = MongoClients.create(uri);
29
30 start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
31 LOGGER.info("\nStarted the server: http://localhost:" + port + "/ \n");
32 }
33
34 public static void main(String[] args) {
35 try {
36 new App();
37 } catch (Exception e) {
38 LOGGER.severe("Couldn't start server:\n" + e);
39 }
40 }
41
42 @Override
43 public Response serve(IHTTPSession session) {
44 StringBuilder msg = new StringBuilder();
45 Map<String, String> params = session.getParms();
46
47 Method reqMethod = session.getMethod();
48 String uri = session.getUri();
49
50 if (Method.GET == reqMethod) {
51 if (uri.equals("/")) {
52 msg.append("Welcome to my API!");
53 } else if (uri.equals("/users")) {
54 msg.append(listUsers(client));
55 } else {
56 msg.append("Unrecognized URI: ").append(uri);
57 }
58 } else if (Method.POST == reqMethod) {
59 try {
60 String name = params.get("name");
61 if (name == null) {
62 throw new Exception("Unable to process POST request: 'name' parameter required");
63 } else {
64 insertUser(client, name);
65 msg.append("User successfully added!");
66 }
67 } catch (Exception e) {
68 msg.append(e);
69 }
70 }
71
72 return newFixedLengthResponse(msg.toString());
73 }
74
75 static String listUsers(MongoClient client) {
76 MongoDatabase database = client.getDatabase("test");
77 MongoCollection<Document> collection = database.getCollection("users");
78
79 final JSONArray jsonResults = new JSONArray();
80 collection.find().forEach((result) -> jsonResults.put(result.toJson()));
81
82 return jsonResults.toString();
83 }
84
85 static String insertUser(MongoClient client, String name) throws MongoException {
86 MongoDatabase database = client.getDatabase("test");
87 MongoCollection<Document> collection = database.getCollection("users");
88
89 collection.insertOne(new Document().append("name", name));
90 return "Successfully inserted user: " + name;
91 }
92}

Note

The following server application uses Express, which you need to add to your project as a dependency before you can run it.

1const express = require('express');
2const bodyParser = require('body-parser');
3
4// Use the latest drivers by installing & importing them
5const MongoClient = require('mongodb').MongoClient;
6
7const app = express();
8app.use(bodyParser.json());
9app.use(bodyParser.urlencoded({ extended: true }));
10
11const uri = "mongodb+srv://<db_username>:<db_password>@cluster0-111xx.mongodb.net/test?retryWrites=true&w=majority";
12
13const client = new MongoClient(uri, {
14 useNewUrlParser: true,
15 useUnifiedTopology: true
16});
17
18// ----- API routes ----- //
19app.get('/', (req, res) => res.send('Welcome to my API!'));
20
21app.get('/users', (req, res) => {
22 const collection = client.db("test").collection("users");
23
24 collection
25 .find({})
26 .maxTimeMS(5000)
27 .toArray((err, data) => {
28 if (err) {
29 res.send("The request has timed out. Please check your connection and try again.");
30 }
31 return res.json(data);
32 });
33});
34
35app.post('/users', (req, res) => {
36 const collection = client.db("test").collection("users");
37 collection.insertOne({ name: req.body.name })
38 .then(result => {
39 res.send("User successfully added!");
40 }, err => {
41 res.send("An application error has occurred. Please try again.");
42 })
43});
44// ----- End of API routes ----- //
45
46app.listen(3000, () => {
47 console.log(`Listening on port 3000.`);
48 client.connect(err => {
49 if (err) {
50 console.log("Not connected: ", err);
51 process.exit(0);
52 }
53 console.log('Connected.');
54 });
55});

Note

The following web application uses FastAPI. To create a new application, use the FastAPI sample file structure.

1# File: main.py
2
3from fastapi import FastAPI, Body, Request, Response, HTTPException, status
4from fastapi.encoders import jsonable_encoder
5
6from typing import List
7from models import User
8
9import pymongo
10from pymongo import MongoClient
11from pymongo import errors
12
13# Replace the uri string with your |service| connection string
14uri = "<atlas-connection-string>"
15db = "test"
16
17app = FastAPI()
18
19@app.on_event("startup")
20def startup_db_client():
21 app.mongodb_client = MongoClient(uri)
22 app.database = app.mongodb_client[db]
23
24@app.on_event("shutdown")
25def shutdown_db_client():
26 app.mongodb_client.close()
27
28##### API ROUTES #####
29@app.get("/users", response_description="List all users", response_model=List[User])
30def list_users(request: Request):
31 try:
32 users = list(request.app.database["users"].find().max_time_ms(5000))
33 return users
34 except pymongo.errors.ExecutionTimeout:
35 raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="The request has timed out. Please check your connection and try again.")
36
37@app.post("/users", response_description="Create a new user", status_code=status.HTTP_201_CREATED)
38def new_user(request: Request, user: User = Body(...)):
39 user = jsonable_encoder(user)
40 try:
41 new_user = request.app.database["users"].insert_one(user)
42 return {"message":"User successfully added!"}
43 except pymongo.errors.DuplicateKeyError:
44 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Could not create user due to existing '_id' value in the collection. Try again with a different '_id' value.")

Back

Scalability

On this page