MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como otimizar o desempenho do MongoDB


Excelente desempenho de banco de dados é importante quando você está desenvolvendo aplicativos com o MongoDB. Às vezes, o processo geral de fornecimento de dados pode ser prejudicado devido a vários motivos, alguns dos quais incluem:
  • Padrões de design de esquema inadequados
  • Uso inadequado ou não uso de estratégias de indexação
  • Hardware inadequado
  • Atraso de replicação
  • Técnicas de consulta de baixo desempenho

Alguns desses contratempos podem forçá-lo a aumentar os recursos de hardware, enquanto outros não. Por exemplo, estruturas de consulta ruins podem fazer com que a consulta demore muito para ser processada, causando atraso na réplica e talvez até perda de dados. Nesse caso, pode-se pensar que talvez a memória de armazenamento não seja suficiente e que provavelmente precise ser ampliada. Este artigo discute os procedimentos mais apropriados que você pode empregar para aumentar o desempenho do seu banco de dados MongoDB.

Design de esquema


Basicamente, os dois relacionamentos de esquema mais comumente empregados são...
  • Um para poucos
  • Um para muitos

Embora o design de esquema mais eficiente seja o relacionamento Um-para-Muitos, cada um tem seus próprios méritos e limitações.

Um para poucos


Nesse caso, para um determinado campo, existem documentos incorporados, mas não são indexados com a identidade do objeto.

Aqui está um exemplo simples:
{
      userName: "Brian Henry",
      Email : "[email protected]",
      grades: [
             {subject: ‘Mathematics’,  grade: ‘A’},
             {subject: English,  grade: ‘B’},
      ]
}

Uma vantagem de usar esse relacionamento é que você pode obter os documentos incorporados com apenas uma única consulta. No entanto, do ponto de vista de consulta, você não pode acessar um único documento incorporado. Portanto, se você não fizer referência a documentos incorporados separadamente, será ideal usar esse design de esquema.

Um para muitos


Para esse relacionamento, os dados de um banco de dados estão relacionados aos dados de um banco de dados diferente. Por exemplo, você pode ter um banco de dados para usuários e outro para postagens. Portanto, se um usuário fizer uma postagem, ela será registrada com o ID do usuário.

Esquema de usuários
{ 
    Full_name: “John Doh”,
    User_id: 1518787459607.0
}

Esquema de postagens
{
    "_id" : ObjectId("5aa136f0789cf124388c1955"),
    "postTime" : "16:13",
    "postDate" : "8/3/2018",
    "postOwnerNames" : "John Doh",
    "postOwner" : 1518787459607.0,
    "postId" : "1520514800139"
}

A vantagem desse projeto de esquema é que os documentos são considerados autônomos (podem ser selecionados separadamente). Outra vantagem é que esse design permite que usuários de diferentes ids compartilhem informações do esquema de postagens (daí o nome One-to-Many) e às vezes pode ser esquema “N-to-N” - basicamente sem usar junção de tabela. A limitação desse design de esquema é que você precisa fazer pelo menos duas consultas para buscar ou selecionar dados na segunda coleção.

Como modelar os dados dependerá, portanto, do padrão de acesso do aplicativo. Além disso, você precisa considerar o design do esquema que discutimos acima.

Técnicas de otimização para design de esquema


  1. Empregue a incorporação de documentos o máximo possível, pois reduz o número de consultas que você precisa executar para um determinado conjunto de dados.

  2. Não use a desnormalização para documentos que são atualizados com frequência. Se um campo for atualizado com frequência, haverá a tarefa de encontrar todas as instâncias que precisam ser atualizadas. Isso resultará em processamento de consulta lento, sobrecarregando até mesmo os méritos associados à desnormalização.

  3. Se houver a necessidade de buscar um documento separadamente, não há necessidade de usar a incorporação, pois consultas complexas, como pipelining agregado, levam mais tempo para serem executadas.

  4. Se a matriz de documentos a serem incorporados for grande o suficiente, não os incorpore. O crescimento da matriz deve ter pelo menos um limite limitado.

Indexação adequada


Essa é a parte mais crítica do ajuste de desempenho e exige que se tenha uma compreensão abrangente das consultas do aplicativo, proporção de leituras para gravações e quanta memória livre seu sistema possui. Se você usar um índice, a consulta examinará o índice e não a coleção.

Um índice excelente é aquele que envolve todos os campos verificados por uma consulta. Isso é chamado de índice composto.

Para criar um índice único para um campo, você pode usar este código:
db.collection.createIndex({“fields”: 1})

Para um índice composto, para criar a indexação:
db.collection.createIndex({“filed1”: 1, “field2”:  1})

Além de consultas mais rápidas pelo uso de indexação, há uma vantagem adicional de outras operações, como classificação, amostras e limite. Por exemplo, se eu projetar meu esquema como {f:1, m:1}, posso fazer uma operação adicional além de localizar como
db.collection.find( {f: 1} ).sort( {m: 1} )

Ler dados da RAM é mais eficiente do que ler os mesmos dados do disco. Por esse motivo, é sempre aconselhável garantir que seu índice caiba inteiramente na RAM. Para obter o indexSize atual de sua coleção, execute o comando:
db.collection.totalIndexSize()

Você obterá um valor como 36864 bytes. Esse valor também não deve ocupar uma grande porcentagem do tamanho geral da RAM, pois você precisa atender às necessidades de todo o conjunto de trabalho do servidor.

Uma consulta eficiente também deve aumentar a Seletividade. A seletividade pode ser definida como a capacidade de uma consulta restringir o resultado usando o índice. Para ser mais secante, suas consultas devem limitar o número de documentos possíveis com o campo indexado. A seletividade está principalmente associada a um índice composto que inclui um campo de baixa seletividade e outro campo. Por exemplo, se você tiver esses dados:
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }

A consulta {a:7, b:“cd”} examinará 2 documentos para retornar 1 documento correspondente. No entanto, se os dados para o valor a forem distribuídos uniformemente, ou seja,
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }

A consulta {a:7, b:“cd”} percorrerá 1 documento e retornará este documento. Portanto, isso levará menos tempo do que a primeira estrutura de dados.
ClusterControlSingle Console para toda a sua infraestrutura de banco de dados Descubra o que mais há de novo no ClusterControlInstale o ClusterControl GRATUITAMENTE

Aprovisionamento de recursos


Memória de armazenamento inadequada, RAM e outros parâmetros operacionais podem degradar drasticamente o desempenho de um MongoDB. Por exemplo, se o número de conexões de usuário for muito grande, isso impedirá a capacidade do aplicativo de servidor de lidar com solicitações em tempo hábil. Conforme discutido em Principais itens a serem monitorados no MongoDB, você pode obter uma visão geral de quais recursos limitados você possui e como dimensioná-los para atender às suas especificações. Para um grande número de solicitações de aplicativos simultâneos, o sistema de banco de dados ficará sobrecarregado para acompanhar a demanda.

Atraso de replicação


Às vezes você pode notar que alguns dados estão faltando no seu banco de dados ou quando você exclui algo, ele aparece novamente. Por mais que você possa ter um esquema bem projetado, indexação apropriada e recursos suficientes, no início seu aplicativo funcionará sem problemas, mas em algum momento você perceberá os problemas mencionados no último. O MongoDB conta com o conceito de replicação em que os dados são copiados de forma redundante para atender a alguns critérios de design. Uma suposição com isso é que o processo é instantâneo. No entanto, alguns atrasos podem ocorrer devido a falhas na rede ou erros não tratados. Em poucas palavras, haverá um grande intervalo entre o tempo em que uma operação é processada no nó primário e o tempo em que será aplicada no nó secundário.

Retrocessos com atrasos de réplica


  1. Dados inconsistentes. Isso está especialmente associado a operações de leitura que são distribuídas entre secundários.

  2. Se a lacuna de atraso for grande o suficiente, muitos dados não replicados podem estar no nó primário e precisarão ser reconciliados no nó secundário. Em algum momento, isso pode ser impossível, especialmente quando o nó primário não pode ser recuperado.

  3. A falha em recuperar o nó primário pode forçar a execução de um nó com dados que não estão atualizados e, consequentemente, pode descartar todo o banco de dados para fazer com que o primário seja recuperado.

Causas da falha do nó secundário


  1. Superando a potência primária sobre a secundária em relação à CPU, IOPS de disco e especificações de E/S de rede.

  2. Operações de gravação complexas. Por exemplo, um comando como
    db.collection.update( { a: 7}  , {$set: {m: 4} }, {multi: true} )

    O nó primário registrará esta operação no oplog com rapidez suficiente. No entanto, para o nó secundário, ele precisa buscar essas operações, ler na RAM qualquer índice e páginas de dados para atender a algumas especificações de critérios, como o id. Como ele precisa fazer isso rápido o suficiente para manter a taxa com o nó primário que faz a operação, se o número de operações for grande o suficiente, haverá um atraso esperado.

  3. Bloqueio do secundário ao fazer um backup. Nesse caso, podemos esquecer de desabilitar o primário, portanto, continuaremos com suas operações normalmente. No momento em que o bloqueio for liberado, o atraso de replicação terá uma grande lacuna, especialmente ao lidar com uma grande quantidade de backup de dados.

  4. Construção de índice. Se um índice se acumular no nó secundário, todas as outras operações associadas a ele serão bloqueadas. Se o índice for de longa duração, o soluço de atraso de replicação será encontrado.

  5. Secundário não conectado. Às vezes, o nó secundário pode falhar devido a desconexões de rede e isso resulta em um atraso de replicação quando ele é reconectado.

Como minimizar o atraso de replicação


  • Use índices exclusivos além de sua coleção com o campo _id. Isso é para evitar que o processo de replicação falhe completamente.

  • Considere outros tipos de backup, como instantâneos point-in-time e do sistema de arquivos, que não necessariamente requerem bloqueio.

  • Evite construir índices grandes, pois eles causam a operação de bloqueio em segundo plano.

  • Faça o secundário poderoso o suficiente. Se a operação de gravação for leve, o uso de secundários com pouca potência será econômico. Mas, para cargas de gravação pesadas, o nó secundário pode ficar atrás do primário. Para ser mais secante, o secundário deve ter largura de banda suficiente para ajudar a ler os oplogs com rapidez suficiente para manter sua taxa com o nó primário.

Técnicas de consulta eficientes


Além de criar consultas indexadas e usar a Seletividade de Consulta conforme discutido acima, há outros conceitos que você pode empregar para fixar e tornar suas consultas efetivas.

Otimizando suas consultas


  1. Usando uma consulta coberta. Uma consulta coberta é aquela que é sempre completamente satisfeita por um índice, portanto, não precisa examinar nenhum documento. A consulta coberta, portanto, deve ter todos os campos como parte do índice e, consequentemente, o resultado deve conter todos esses campos.

    Vamos considerar este exemplo:
    {_id: 1, product: { price: 50 }

    Se criarmos um índice para esta coleção como
    {“product.price”: 1} 

    Considerando uma operação de busca, então este índice cobrirá esta consulta;
    db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0}  )

    e retorne apenas o campo product.price e o valor.

  2. Para documentos incorporados, use a notação de ponto (.). A notação de ponto ajuda no acesso a elementos de uma matriz e campos de documento incorporado.

    Acessando uma matriz:
    {
       prices: [12, 40, 100, 50, 40]  
    }

    Para especificar o quarto elemento, por exemplo, você pode escrever este comando:
    “prices.3”

    Acessando uma matriz de objetos:
    {
    
       vehicles: [{name: toyota, quantity: 50},
                 {name: bmw, quantity: 100},
                 {name: subaru, quantity: 300}                    
    } 

    Para especificar o campo de nome na matriz de veículos você pode usar este comando
    “vehicles.name”

  3. Verifique se uma consulta está coberta. Para fazer isso, use o db.collection.explain(). Esta função fornecerá informações sobre a execução de outras operações - por exemplo. db.collection.explain().aggregate(). Para saber mais sobre a função de explicação, você pode conferir o explain().

Em geral, a técnica suprema no que diz respeito à consulta é o uso de índices. Consultar apenas um índice é muito mais rápido do que consultar documentos fora do índice. Eles podem caber na memória, portanto, disponíveis na RAM e não no disco. Isso torna fácil e rápido o suficiente para buscá-los da memória.