MongoDB – Tuning

MELHORANDO E/OU ANALISANDO DESEMPENHO DO MongoDB

MongoDB está entre os bancos de dados NoSQL mais populares e utilizados, quando tratamos de desenvolvimento WEB principalmente quando citamos (Angular e Node.js).

O MongoDB utiliza de documentos BSON semelhantes ao JSON para armazenamento de dados. Ele é gratuito e de código aberto.

Sua eficiência é espantosa, no entanto, é necessário ter certos cuidados e evitar/analisar problemas que podem arrasta-lo para baixo, em outras palavras, degradar consideravelmente seu ambiente.

Veremos aqui algumas métricas de desempenho importantes e seus significados. Serão abordados:

  • Desempenho do bloqueio nas transações
  • Uso de memória
  • Manipulação de conexão
  • Problemas em conjuntos de réplicas

Tópicos de desempenho abrangem uma enorme área e atividades do sistema e com MongoDB não é diferente.

Vamos lá:

ANÁLISAR O DESEMPENHO DE BLOQUEIO

Bancos de dados operam com arquiteturas de leituras, gravações e atualizações.  Requisições irão acionar alguma dessas atividades. Normalmente não são sequenciais e em muito costumam usar dados que outra solicitação que está no meio da atualização.

Para manter a consistência de um dado, MongoDB bloqueia determinados documentos ou coleções caso uma requisição tente ler um documento que outra requisição esteja atualizando naquele instante. Isso garante que perda de dados ou alterações inesperadas acorram.

Então, para evitar conflitos, um bloqueio é adquirido o que impede que outra operação possa ler ou modificar o(s) dado(s) até que a operação que iniciou esteja concluída. Isso vale para outros bancos de dados também como SQL Server, Oracle etc onde existem níveis de bloqueios que fogem do escopo deste artigo.

Bloqueios ajudam manter integridade do(s) dado(s), mas, podem também ser extremamente maléficos para o desempenho do banco de dados.

Em um outro exemplo, se uma requisição de leitura estiver aguardando a conclusão de uma operação de gravação e está estiver demorando muito tempo (ahhhh cargas de dados), requisições adicionais também terão que aguardar. No caso de uma operação com um grande tempo de leitura ou gravação, elas por si poderão “agachar” visivelmente o desempenho do banco de dados, e não há índice que salve.

Se o servidor não responder por muito tempo, poderá causar uma alteração no estado da réplica o que pode ocasionar uma grande dor de cabeça em cascata.

bloqueios

 

VISUALIZANDO MÉTRICAS DE BLOQUEIO

MongoDB fornece algumas métricas úteis para que possamos analisar se existem bloqueios que possam estar afetando o desempenho do banco de dados:

db.serverStatus().globalLock

db.serverStatus().locks

> db.serverStatus().globalLock
{
    "totalTime" : <num>,
    "currentQueue" : {
        "total" : <num>,
        "readers" : <num>,
        "writers" : <num>
    },
    "activeClients" : {
        "total" : <num>,
        "readers" : <num>,
        "writers" : <num>
    }
}

> db.serverStatus().locks
{
    <type> : {
        "acquireCount" : {
        <mode> : NumberLong(<num>),
        ...
    },
    "acquireWaitCount" : {
        <mode> : NumberLong(<num>),
        ...
    },
    "timeAcquiringMicros" : {
        <mode> : NumberLong(<num>),
        ...
    },
    "deadlockCount" : {
        <mode> : NumberLong(<num>),
        ...
    }
}

 

serverStatus().globalLocks:

  • globalLock.currentQueue.total:  Caso várias solicitações estiverem aguardando a liberação de um bloqueio, este número poderá apresentar-se consistentemente alto, o que pode indicar um possível problema de simultaneidade.
  • globalLock.totalTime: Se este valor estiver maior que o tempo de atividade total do banco de dados, o mesmo estará em um estado de bloqueio por muito tempo.

serverStatus().locks:

  • locks.<type>.acquireCount: Número de vezes que bloqueio foi adquirido.
  • locks.<type>.acquireWaitCount: Número de vezes que o locks.acqurieCount encontrado aguarda devido a bloqueios conflitantes.
  • locks.<type>.tipeAquiringMicros: Tempo de espera cumulativo para aquisições de bloqueios, em microssegundos.
  • locks.deadlockCount: Número de vezes que as aquisições de bloqueio encontraram conflitos.

Níveis de bloqueios que podemos encontrar com as métricas acima:

  • Global: Bloqueios globais.
  • MMAPV1Journal: Bloqueios para sincronizar gravações no diário.
  • Banco de dados: Bloqueios de banco de dados.
  • Coleção: Bloqueios de coleção.
  • Metadados: Bloqueios relacionados a metadados.
  • Oplog: Bloqueios de log operacionais.

Pode-se analisar também o tempo médio de espera para um tipo de bloqueio específico com a seguinte divisão: locks.timeAcquiringMicros / locks.acquireWaitCount.

MECANISMOS DE ARMAZENAMENTO E TRAVAMENTO

Caso encontremos frequentemente bloqueios de consultas, isso pode indicar problemas de design do esquema, estrutura de consulta ou arquitetura do sistema.

Versões anteriores a 3.2 o padrão de armazenamento é o MMAPv1, já as versões em diante o padrão é o WiredTiger. As diferenças de armazenamento influenciam diretamente no tipo de bloqueio. O MMAPv1 bloqueia coleções inteiras enquanto o WirdTiger realiza o bloqueio a nível de documento. Isso permite ler ou atualizar vários documentos em uma coleção simultaneamente além de reduzir consideravelmente os bloqueios.

EXAMINAR O USO DE MEMÓRIA

Para o MMAPv1 em uso de armazenamento, MongoDB mapeia arquivos na memória para armazenar os dados. Em outras palavras, se o conjunto de dados for grande, ele poderá alocar toda a memória para uso.

Para entender como MongoDB está usando a memória do sistema, podemos analisar as métricas de seção de memória do serverStatus():

> db.serverStatus().mem
"mem" : {
    "bits" : <int>,
    "resident" : <int>,
    "virtual" : <int>,
    "supported" : <boolean>,
    "mapped" : <int>,
    "mappedWithJournal" : <int>
},
  • mem.resident: Equivalencia aproximada à quantidade de RAM em megabytes que o procesos de banco de dados usa.
  • mem.mapped: Quantidade de memória que o banco de dados mapeia, em megabytes.

Comparando o valor de mem.resident com a quantidade de memória do sistema, conseguiremos verificar se excedeu a capacidade computacional. Caso exista uma quantidade de dados não mapeados no disco e o valor de mem.resident estiver maior que o valor da memória, provavelmente excederemos a capacidade do sistema.

Se o valor de mem.mapped for maior que a quantidade de memória do sistema, operações poderão sofrer falhas nas páginas.

AJUSTAR O CACHE DO WiredTiger

O mecanismo de armazenamento WiredTiger é uma melhoria significativa em relação ao MMAPv1 em desempenho e simultaneidade. Ele também oferece os benefícios de compactação e criptografia. O mesmo foi descontinuado à partir da versão 4.0.

O tamanho do cache do WrideTiger é de suma importância para que esse sistema de armazenamento funcione adequadamente. Por padrão, MongoDB reserva 50% da memória disponível para o cache de dados do WiredTiger.

Em boas práticas, o tamanho do cache deve ser grande o suficiente para manter todo o conjunto de trabalho do aplicativo.

Vamos analisa-lo:

> db.serverStatus().wiredTiger.cache
{
    "tracked dirty bytes in the cache" : <num>,
    "tracked bytes belonging to internal pages in the cache" : <num>,
    "bytes currently in the cache" : <num>,
    "tracked bytes belonging to leaf pages in the cache" : <num>,
    "maximum bytes configured" : <num>,
    "tracked bytes belonging to overflow pages in the cache" : <num>,
    "bytes read into cache" : <num>,
    "bytes written from cache" : <num>,
    "pages evicted by application threads" : <num>,
    "checkpoint blocked page eviction" : <num>,
    "unmodified pages evicted" : <num>,
    "page split during eviction deepened the tree" : <num>,
    "modified pages evicted" : <num>,
    "pages selected for eviction unable to be evicted" : <num>,
    "pages evicted because they exceeded the in-memory maximum" : <num>,
    "pages evicted because they had chains of deleted items" : <num>,
    "failed eviction of pages that exceeded the in-memory maximum" : <num>,
    "hazard pointer blocked page eviction" : <num>,
    "internal pages evicted" : <num>,
    "maximum page size at eviction" : <num>,
    "eviction server candidate queue empty when topping up" : <num>,
    "eviction server candidate queue not empty when topping up" : <num>,
    "eviction server evicting pages" : <num>,
    "eviction server populating queue, but not evicting pages" : <num>,
    "eviction server unable to reach eviction goal" : <num>,
    "internal pages split during eviction" : <num>,
    "leaf pages split during eviction" : <num>,
    "pages walked for eviction" : <num>,
    "eviction worker thread evicting pages" : <num>,
    "in-memory page splits" : <num>,
    "in-memory page passed criteria to be split" : <num>,
    "lookaside table insert calls" : <num>,
    "lookaside table remove calls" : <num>,
    "percentage overhead" : <num>,
    "tracked dirty pages in the cache" : <num>,
    "pages currently held in the cache" : <num>,
    "pages read into cache" : <num>,
    "pages read into cache requiring lookaside entries" : <num>,
    "pages written from cache" : <num>,
    "page written requiring lookaside records" : <num>,
    "pages written requiring in-memory restoration" : <num>
}

Muita informação, não? Vamos nos concentrar nos mais relevantes:

  • wiredTiger.cache.maximum bytes configured: Tamanho máximo do cache.
  • wiredTiger.cache.bytes currently in the cache: Tamanho dos dados atualmente no cache. Não deve ser maior que maximum bytes configured.
  • wiredTiger.cache.tracked dirty bytes in the cache: Tamanho dos dados sujos no cache. Este valor deve ser menor que o valor de bytes currently in the cache.

Observando esses valores, podemos determinar se precisamos aumentar o tamanho do cache da nossa instância. Além disso, podemos observar o valor do cache do  wiredTiger.cache.bytes para aplicativos com muita leitura. Se esse valor for consistentemente alto, aumentar o tamanho do cache pode melhorar o desempenho geral da leitura.

NÚMERO DE CONEXÕES ATUAIS

numeroConexoes

MongoDB não tem limites para conexões de entrada. Por isso, em alguns casos um grande número de conexões entre o aplicativo e o banco de dados pode sobrecarregar o BD, limitando assim sua capacidade de lidar com novas conexões.

As seguintes métricas podem ser examinadas:

> db.serverStatus().connections
{
    "current" : <num>,
    "available" : <num>,
    "totalCreated" : NumberLong(<num>)
}

Monitorar as conexões:

  • connections.current: Número total de conexões atuais na instância.
  • connections.available: N´mero total de conexões não utilizadas disponíveis para novas requisições na instância.

Verificar se o aplicativo está com muita leitura é uma estratégia para resolver problemas conexões. Uma solução é aumentar o tamanho do conjunto de réplicas e distribuir as operações de leitura para os slaves. Se for o caso de uma carga grande de gravações, sharding dentro de um cluster sharded e assim distribuir a carga.

Erros relacionados a aplicativos ou drivers também podem causar problemas de conexão. Por exemplo, uma conexão pode ser descartada incorretamente ou abrir quando não for necessária, se houver um erro no driver ou no aplicativo. Você verá isso se o número de conexões for alto, mas não houver carga de trabalho correspondente.

MONITORAR ATRASO NA REPLICAÇÃO

Atrasos na replicação podem acontecer, desde latência de rede, aplicação entre inúmeros eventos. Se um atraso entre um nó primário e o secundário for alto e o secundário se tornar primário, como a replicação enfrentava uma latência, os dados serão perdidos quando o primário recém-eleito replicar para o novo secundário.

Verificando o atraso na replicação:

  • db.printSlaveReplicationInfo()
  • rs.printSlaveReplicationInfo()

Com esses comandos podemos ver o status de um conjunto de réplicas da perspectiva do conjunto slave. 

source: m1.book.net:27017
syncedTo: Thu Oct 31 2019 01:21:50 GMT-0400 (EDT)
0 secs (0 hrs) behind the primary
source: m2.book.net:27017
syncedTo: Thu Oct 31 2019 01:21:50 GMT-0400 (EDT)
0 secs (0 hrs) behind the primary

A saída do comando mostra se há latência entre o membro primário com os secundários. O valor deve ser o mais baixo possível. Essa métrica deve ser monitorada de perto. Se o atraso na replicação é consistentemente alto ou aumenta regularmente, isso é um sinal claro de problemas ambientais ou sistêmicos. Sempre investigue esses problemas para entender os motivos do atraso.

Se um secundário for eleito primário, sempre, SEMPRE investigue as causas que levaram a alteração de status. Isso pode ocorrer por uma falha de rede ou hardware. Não é normal que os nós alternem entre o primário e o secundário.

PROFILE DATABASE

Por último e não menos importante, o Profile. Você pode reunir informações detalhadas adicionais de desempenho usando o profile de banco de dados. Com o Profile, você tem a compreensão mais profunda do comportamento do banco de dados. O create profile coleta informações sobre todos os comandos do banco de dados que são executados em uma instância. Isso inclui as operações comuns de criação, leitura, atualização e exclusão. Ele também abrange todos os comandos de configuração e administração. No entanto, há um problema. A ativação do profile pode afetar o desempenho do sistema devido à atividade adicional.

 

Depois de tudo, boa diversão.

Nos próximos capítulos ensinarei a como implementar um sistema de monitoramento visual de desempenho de querys para MongoDB e também, boas práticas na criação de indices (casos reais).

Até a próxima!

Marcado com: , , ,
Publicado em NoSQL

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Arquivos
Follow SQL DATA BLOG on WordPress.com
%d blogueiros gostam disto: