Docs Menu

Docs HomeRust Driver

Performance Considerations

On this page

  • Overview
  • Client Lifecycle
  • Parallelism
  • Runtime
  • Additional Information
  • API Documentation

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.

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(())
}

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
});
}

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 the test helper macros.

  • Create a new Client instance for every async 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)
});
#[test]
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:

#[tokio::test]
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(())
}

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.

  • Client()

  • spawn() in the tokio::task module

  • tokio::runtime module

←  Run a CommandAsynchronous and Synchronous APIs →
Share Feedback