Performance Considerations
Overview
In this guide, you can learn how to optimize the performance of the
Rust driver. To connect to MongoDB, you must create a Client
instance. Your Client
instance automatically handles most aspects of connection, such as
discovering server topology, monitoring your connection, and maintaining
an internal connection pool. This guide describes best practices for
configuring and using your Client
instance.
Client Lifecycle
We recommend that you reuse your client across sessions and operations.
You can use the same Client
instance to perform multiple tasks, as
the Client
type is safe for concurrent use by multiple threads.
Creating a new Client
instance for each request results in slower
performance.
The following code creates a method that accepts a pointer to an
existing Client
instance, which allows you to perform many requests
by using the same client:
// ... Create a client earlier in your code async fn make_request(client: &Client) -> Result<(), Box<dyn Error>> { // Use the client to perform operations Ok(()) }
Parallelism
If you can run parallel data operations, you can optimize performance by
running asynchronous, concurrent tasks. The following code uses the
spawn()
method from the tokio::task
module to create separate,
concurrent tasks to perform insert operations:
let client = Client::with_uri_str("<connection string>").await?; let data = doc! { "title": "1984", "author": "George Orwell" }; for i in 0..5 { let client_ref = client.clone(); let data_ref = data.clone(); task::spawn(async move { let collection = client_ref .database("items") .collection::<Document>(&format!("coll{}", i)); collection.insert_one(data_ref, None).await }); }
Runtime
A Client
instance is bound to the instance of the tokio
or
async-std
runtime in which you created it. If you use a Client
instance to perform operations on a different runtime, you might
experience unexpected behavior or failures.
If use the test
helper macro from the tokio
or
async_std
crate to test your application, you might accidentally run
operations on a different runtime than intended. This is because these
helper macros create a new runtime for each test. However, you can use
one of the following strategies to avoid this issue:
Attach the runtime to the
Client
instance without using thetest
helper macros.Create a new
Client
instance for everyasync
test.
This example follows the first strategy and creates a global runtime used only for testing.
In the following code, the test_list_dbs()
method uses a client that
manually connects to this runtime to list databases in the deployment:
use tokio::runtime::Runtime; use once_cell::sync::Lazy; static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| { let rt = Runtime::new().unwrap(); let client = rt.block_on(async { Client::with_uri_str("<connection string>").await.unwrap() }); (client, rt) }); fn test_list_dbs() -> Result<(), Box<dyn Error>> { let (client, rt) = &*CLIENT_RUNTIME; rt.block_on(async { client.list_database_names(None, None).await })?; Ok(()) }
Implementing the second strategy, the following code creates a new
Client
instance for each test run with tokio::test
,
ensuring that there are no unintended interactions between runtimes:
async fn test_list_dbs() -> Result<(), Box<dyn Error>> { let client = Client::with_uri_str("<connection string>").await?; client.list_database_names(None, None).await?; Ok(()) }
Additional Information
To learn more about connecting to MongoDB, see the Connection Guide.
To learn more about the available runtimes for the Rust driver, see the guide on Asynchronous and Synchronous APIs.
API Documentation
spawn() in the
tokio::task
moduletokio::runtime module