The following sections list configuration choices you can make to reduce latency in your Atlas deployment.
Physical Distance
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 Configuration
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 toprimary
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 regionsus-east-1
(Northern Virginia) andus-west-1
(Northern California), and the majority of your users are in California, you can prioritize nodes in the AWSus-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.
Network Configuration
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.
Data Modeling and Query Optimization
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.
Monitoring and Testing Latency
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.
Connection Configuration
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.
Example Low-Latency Application
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 |
---|---|---|
|
| Gets a list of user names from a |
|
| Requires a |
Note
1 // File: App.java 2 3 import java.util.Map; 4 import java.util.logging.Logger; 5 6 import org.bson.Document; 7 import org.json.JSONArray; 8 9 import com.mongodb.MongoException; 10 import com.mongodb.client.MongoClient; 11 import com.mongodb.client.MongoClients; 12 import com.mongodb.client.MongoCollection; 13 import com.mongodb.client.MongoDatabase; 14 15 import fi.iki.elonen.NanoHTTPD; 16 17 public 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 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.
1 const express = require('express'); 2 const bodyParser = require('body-parser'); 3 4 // Use the latest drivers by installing & importing them 5 const MongoClient = require('mongodb').MongoClient; 6 7 const app = express(); 8 app.use(bodyParser.json()); 9 app.use(bodyParser.urlencoded({ extended: true })); 10 11 const uri = "mongodb+srv://<db_username>:<db_password>@cluster0-111xx.mongodb.net/test?retryWrites=true&w=majority"; 12 13 const client = new MongoClient(uri, { 14 useNewUrlParser: true, 15 useUnifiedTopology: true 16 }); 17 18 // ----- API routes ----- // 19 app.get('/', (req, res) => res.send('Welcome to my API!')); 20 21 app.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 35 app.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 46 app.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 3 from fastapi import FastAPI, Body, Request, Response, HTTPException, status 4 from fastapi.encoders import jsonable_encoder 5 6 from typing import List 7 from models import User 8 9 import pymongo 10 from pymongo import MongoClient 11 from pymongo import errors 12 13 # Replace the uri string with your |service| connection string 14 uri = "<atlas-connection-string>" 15 db = "test" 16 17 app = FastAPI() 18 19 20 def startup_db_client(): 21 app.mongodb_client = MongoClient(uri) 22 app.database = app.mongodb_client[db] 23 24 25 def shutdown_db_client(): 26 app.mongodb_client.close() 27 28 ##### API ROUTES ##### 29 30 def 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 38 def 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.")