Considerações de desempenho
Nesta página
Visão geral
Neste guia, você verá como otimizar o desempenho do driver do Rust. Para se conectar ao MongoDB, você deve criar uma instância do Client
. Sua instância do Client
lida automaticamente com a maioria dos aspectos da conexão, como descobrir a topologia do servidor, monitorar sua conexão e manter um pool de conexões interno. Este guia descreve as melhores práticas para configurar e utilizar sua instância do Client
.
Este guia inclui as seguintes seções:
O Ciclo de vida do cliente descreve as melhores práticas para criar e gerenciar uma instância
Client
O Pool de conexões descreve como o pool de conexões funciona no driver
Paralelismo fornece código de amostra para executar tarefas paralelas e assíncronas
O tempo de execução descreve como gerenciar tempos de execução usando as funcionalidades das caixas
tokio
easync_std
Informações adicionais fornecem links para recursos e documentação da API para os tipos e métodos mencionados neste guia
Ciclo de vida do cliente
Recomendamos que você reutilize seu cliente entre sessões e operações. Você pode utilizar a mesma instância do Client
para executar múltiplas tarefas, pois o tipo do Client
é seguro para uso simultâneo por vários threads. Criar uma nova instância do Client
para cada solicitação resulta em desempenho mais lento.
O seguinte código cria um método que aceita um ponteiro para uma instância do Client
existente, que permite a você executar muitas solicitações utilizando o mesmo cliente:
// ... Create a client earlier in your code async fn make_request(client: &Client) -> Result<(), Box<dyn Error>> { // Use the client to perform operations Ok(()) }
Pool de Conexões
Cada instância Client
tem um pool de conexões integrado para cada servidor em sua topologia do MongoDB. Os pools de conexões abrem soquetes sob demanda para oferecer suporte a solicitações simultâneas ao MongoDB em seu aplicativo.
A configuração padrão de um Client
funciona para a maioria dos aplicativos. O seguinte código mostra como criar um cliente com configurações de conexão padrão:
let client = Client::with_uri_str("<connection string>").await?;
Como alternativa, você pode ajustar o pool de conexões para melhor atender às necessidades do seu aplicativo e otimizar o desempenho. Para obter mais informações sobre como personalizar suas configurações de conexão, consulte as seguintes subseções deste guia:
Dica
Para saber mais sobre como configurar um pool de conexões, consulte Ajustando as configurações do pool de conexões no manual do servidor MongoDB.
Configurar tamanho máximo do pool
O tamanho máximo de cada conjunto de conexões é definido pela opção max_pool_size
, que padroniza para 10
. Se o número de conexões em uso com um servidor atingir o valor de max_pool_size
, a próxima solicitação para esse servidor aguardará até que uma conexão se torne disponível.
Além dos soquetes necessários para dar suporte às solicitações do seu aplicativo, cada instância Client
abre mais dois soquetes por servidor em sua topologia MongoDB para monitorar o estado do servidor. Por exemplo, um cliente conectado a um conjunto de réplicas de três nós abre seis soquetes de monitoramento. Se a aplicação usar a configuração padrão para max_pool_size
e query somente o nó primário (padrão), poderá haver no máximo 16 conexões totais no pool de conexões. Se a aplicação usar uma preferência de leitura para query os nós secundários, esses pool de conexões crescerão e poderá haver um total de 36 conexões.
Para oferecer suporte a um grande número de solicitações simultâneas do MongoDB em um processo, você pode aumentar o valor da opção max_pool_size
. O seguinte código demonstra como especificar um valor para max_pool_size
ao instanciar um Client
:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_pool_size = Some(20); let client = Client::with_options(client_options)?;
Configurar opções de conexão simultânea
Pool de conexões têm taxa limitada. A opção max_connecting
determina o número de conexões que o grupo pode criar em paralelo a qualquer momento. Por exemplo, se o valor de max_connecting
for 2
, o valor padrão, a terceira solicitação que tenta fazer o check-out simultâneo de uma conexão será bem-sucedida somente quando ocorrer um dos seguintes casos:
O pool de conexões termina de criar uma conexão e o número de conexões no pool é menor ou igual ao valor de
max_pool_size
.Uma conexão existente é verificada novamente no pool.
A capacidade do driver de reutilizar conexões existentes melhora devido aos limites de taxa na criação de conexões.
Você pode configurar o número mínimo de conexões simultâneas para cada servidor com a opção min_pool_size
, que padroniza para 0
. O condutor inicia o pool de ligações com este número de tomadas. Se os soquetes forem fechados e o número total de soquetes, em uso e ociosos, cair abaixo do mínimo, o pool de conexões abrirá mais soquetes até que o mínimo seja atingido.
O seguinte código define as opções max_connecting
e min_pool_size
ao instanciar um Client
:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_connecting = Some(3); client_options.min_pool_size = Some(1); let client = Client::with_options(client_options)?;
Configurar tempo máximo de inatividade
Você pode definir o limite de tempo máximo que uma conexão pode ficar inativa no pool de conexões definindo a opção max_idle_time
. Depois que uma conexão fica inativa pelo tempo especificado em max_idle_time
, o pool de conexões a remove e a substitui. Esta opção tem como padrão 0
, ou sem limite.
Quando o método Client::shutdown()
é chamado em qualquer ponto do seu aplicativo, o driver fecha todos os soquetes ociosos e fecha todos os soquetes que estão em uso à medida que são retornados ao pool. A chamada Client::shutdown()
fecha somente os soquetes inativos, portanto, não é possível interromper ou encerrar nenhuma operação em andamento usando esse método. O condutor fecha estas tomadas apenas quando o processo é concluído.
O seguinte código define o valor da opção max_idle_time
para 90
segundos ao instanciar um Client
:
let mut client_options = ClientOptions::parse("<connection string>").await?; client_options.max_idle_time = Some(Duration::new(90, 0)); let client = Client::with_options(client_options)?;
Paralelismo
Se você puder executar operações de dados paralelas, poderá otimizar o desempenho executando tarefas simultâneas e assíncronas. O código abaixo usa o método spawn()
do módulo tokio::task
para criar tarefas simultâneas separadas para executar operações de inserção:
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).await }); }
Tempo de execução
Uma instância Client
está vinculada à instância do tempo de execução tokio
ou async-std
no qual você a criou. Se você utilizar uma instância do Client
para executar operações em um tempo de execução diferente, você poderá enfrentar comportamentos inesperados ou falhas.
Se usar a macro auxiliar test
da caixa tokio
ou async_std
para testar seu aplicativo, você poderá executar acidentalmente as operações em um tempo de execução diferente do pretendido. Isso ocorre porque essas macros auxiliares criam um novo tempo de execução para cada teste. No entanto, você pode usar uma das seguintes estratégias para evitar esse problema:
Anexe o tempo de execução à instância do
Client
sem utilizar as macros auxiliares dotest
.Crie uma nova instância
Client
para cada testeasync
.
Este exemplo segue a primeira estratégia e cria um tempo de execução global usado apenas para testes. No código a seguir, o método test_list_dbs()
usa um cliente que se conecta manualmente a esse tempo de execução para listar os reconhecimento de data center na implantação:
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().await })?; Ok(()) }
Implementando a segunda estratégia, o código a seguir cria uma nova instância Client
para cada teste executado com tokio::test
, garantindo que não haja interações não intencionais entre os tempos de execução:
async fn test_list_dbs() -> Result<(), Box<dyn Error>> { let client = Client::with_uri_str("<connection string>").await?; client.list_database_names().await?; Ok(()) }
Informações adicionais
Para saber mais sobre como se conectar ao MongoDB, consulte oGuia de Conexão .
Para saber mais sobre os tempos de execução disponíveis para o Rust, consulte o guia sobre API assíncronas e síncronas.
Documentação da API
gerar() no
tokio::task
módulotokio::runtime módulo