Originally written for Percona Blog in October, 2023 – 5 Changes You Should Know in MongoDB 7.0
Como quase uma tradição, uma nova versão principal do MongoDB é lançada todos os anos, e este ano não é diferente. Muitas mudanças e novos recursos são adicionados ao produto. Para nos mantermos atualizados sobre essas alterações e entender como elas podem nos impactar, analisei as mudanças para compreendê-las melhor. Foi a partir disso que este artigo nasceu.
Nosso objetivo aqui é revisar cinco novas mudanças ou implementações que você deve conhecer no MongoDB 7.0. Além disso, a ideia é apresentar alterações que podem estar passando despercebidas, mas que, em minha visão, podem influenciar diretamente sua operação. Sem mais delongas, vamos começar.
1. Tickets Dinâmicos do WiredTiger
Como um breve resumo, os tickets do WiredTiger no MongoDB servem como o mecanismo de controle de concorrência dentro do mecanismo de armazenamento WiredTiger. Esses tickets são divididos em Read tickets (leitura) e Write tickets (escrita).
Quando múltiplas operações, como leituras e gravações, tentam acessar o banco simultaneamente, o WiredTiger utiliza tickets para garantir que essas operações não entrem em conflito de forma que comprometa a integridade dos dados ou o desempenho.
Cada transação obtém um ticket no início de sua execução e o devolve ao pool de tickets ao ser concluída. Desde os primórdios do mecanismo WiredTiger, o número de tickets era controlado pelas variáveis:
Essas variáveis determinam o número de transações concorrentes de leitura/gravação permitidas no mecanismo WiredTiger. Antes do MongoDB 7.0, o valor padrão era 128 tickets:
1 2 3 4 5 6 7 8 9 10 11 |
db.runCommand({ getParameter: 1, wiredTigerConcurrentReadTransactions: 1 }); { "wiredTigerConcurrentReadTransactions" : 128, [...] } db.runCommand({ getParameter: 1, wiredTigerConcurrentWriteTransactions: 1 }); { "wiredTigerConcurrentWriteTransactions" : 128, [...] } |
- A partir do MongoDB 7.0:
1 2 3 4 5 6 7 8 9 10 11 |
db.runCommand({ getParameter: 1, wiredTigerConcurrentReadTransactions: 1 }); { "wiredTigerConcurrentReadTransactions": 0, [...] } db.runCommand({ getParameter: 1, wiredTigerConcurrentWriteTransactions: 1 }); { "wiredTigerConcurrentWriteTransactions": 0, [...] } |
Os tickets agora são definidos dinamicamente:
1 2 3 4 5 |
db.serverStatus().wiredTiger.concurrentTransactions.read.totalTickets 12 db.serverStatus().wiredTiger.concurrentTransactions.write.totalTickets 12 |
Um algoritmo completamente novo foi implementado para gerenciar os tickets de forma dinâmica, e pode ser conferido aqui. Com essa nova mudança, é importante destacar que as métricas do WiredTiger devem ser analisadas em sua totalidade, e não de forma isolada, para evitar conclusões equivocadas.
Minha opinião sobre essa mudança: Essa é uma abordagem interessante e válida, principalmente porque é comum encontrar sistemas com recursos limitados enfrentando problemas de desempenho devido a limites superestimados (algo bastante comum em muitos parâmetros padrão do MongoDB), o que adiciona pressão desnecessária ao sistema. Embora a alocação dinâmica possa ter algumas falhas, sem dúvida é uma adição muito positiva.
2. Auxiliar de Análise de Shard Key – .analyzeShardKey()
A shard key é um componente crítico do seu cluster, pois determina a distribuição dos seus dados pelos shards. E é exatamente aí que os problemas podem surgir.
Uma parte considerável dos problemas em Sharded Clusters está relacionada a escolhas inadequadas de shard keys. Não abordaremos cada componente em detalhe, pois não é o objetivo deste artigo, mas para escolher uma boa shard key, você deve considerar:
- A cardinalidade da shard key
- A frequência com que os valores da shard key ocorrem
- Se uma shard key em potencial cresce de forma monótona
- Padrões de consulta para sharding
No passado, a shard key era imutável, mas agora (a partir da versão 4.4) o MongoDB inclui novos recursos para melhorar ou até mesmo alterar sua shard key, como refiná-la ou reconfigurá-la (reshard).
Apesar dessas melhorias, você ainda precisava consultar manualmente sua coleção para responder a perguntas como: “Minha shard key possui uma boa cardinalidade? Frequência?” E assim por diante.
O MongoDB 7.0 adiciona dois novos métodos que retornam métricas muito úteis para avaliar shard keys existentes ou novas shard keys em potencial. Esses métodos são:
1 2 3 4 5 6 7 8 9 10 11 12 |
db.collection.analyzeShardKey() db.adminCommand( { analyzeShardKey: <string>, key: <shardKey>, keyCharacteristics: <bool>, readWriteDistribution: <bool>, sampleRate: <double>, sampleSize: <int> } ) |
A saída fornece três documentos que contêm suas respectivas estatísticas:
- keyCharacteristics: fornece métricas sobre a cardinalidade, frequência e monotonicidade da shard key.
- readDistribution/writeDistribution: fornece métricas sobre os padrões de roteamento de consultas e a intensidade de uso (“hotness”) de intervalos da shard key.
Aqui está um exemplo de uso do analyzeShardKey()
em duas shard keys candidatas.
Como a coleção ainda não foi shardeda, configuramos readWriteDistribution: false
. Você pode usar o novo método para ajudá-lo a avaliar uma shard key já em uso ou uma futura shard key, conforme mostrado abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
db.adminCommand({analyzeShardKey: "percona.employee", key: {badgeId: "hashed"},readWriteDistribution: false }) { keyCharacteristics: { numDocsTotal: Long("13591000"), avgDocSizeBytes: Long("207"), numDocsSampled: Long("10000000"), isUnique: false, numDistinctValues: Long("9988503"), mostCommonValues: [ { value: { badgeId: Long("8472676555298607") }, frequency: Long("3") }, { value: { badgeId: Long("4284333888163027") }, frequency: Long("3") }, { value: { badgeId: Long("3463302535508078") }, frequency: Long("3") }, { value: { badgeId: Long("5807207987841736") }, frequency: Long("3") }, { value: { badgeId: Long("88360867693259") }, frequency: Long("3") } ], monotonicity: { recordIdCorrelationCoefficient: 0, type: 'not monotonic' } }, ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1696960527, i: 1 }), signature: { hash: Binary.createFromBase64("i+DjCSA7WpV3KJAT5HwXhTLb7uw=", 0), keyId: Long("7288022888098037764") } }, operationTime: Timestamp({ t: 1696960524, i: 1 }) } |
Checando para chave {badgeId: "hashed"}
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
> db.adminCommand({analyzeShardKey: "percona.employee", key: {created: 1},readWriteDistribution: false }) { keyCharacteristics: { numDocsTotal: Long("13591000"), avgDocSizeBytes: Long("207"), numDocsSampled: Long("9998918"), isUnique: false, numDistinctValues: Long("955449"), mostCommonValues: [ { value: { created: ISODate("2023-10-10T17:01:02.732Z") }, frequency: Long("21") }, { value: { created: ISODate("2023-10-10T17:14:32.273Z") }, frequency: Long("20") }, { value: { created: ISODate("2023-10-10T17:01:16.426Z") }, frequency: Long("20") }, { value: { created: ISODate("2023-10-10T17:01:03.824Z") }, frequency: Long("20") }, { value: { created: ISODate("2023-10-10T17:02:06.022Z") }, frequency: Long("20") } ], monotonicity: { recordIdCorrelationCoefficient: 0.9999999911, type: 'monotonic' } }, ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1696960030, i: 1 }), signature: { hash: Binary.createFromBase64("LHsIUx9Au7axEpq7eLYcTa0mkmM=", 0), keyId: Long("7288022888098037764") } }, operationTime: Timestamp({ t: 1696960024, i: 1 }) } |
Checando para {created: 1}
.
Com essa saída, você pode entender se sua shard key possui alta cardinalidade verificando o total de documentos dividido pelo número de valores distintos.
O campo mostCommonValues ajuda a compreender a frequência de um valor. Se essa frequência for alta, pode indicar a possibilidade de jumbo chunk no futuro, já que valores frequentemente repetidos podem aumentar o tamanho de um chunk, tornando-o indivisível.
Por fim, mas não menos importante, o método analyzeShardKey()
utiliza uma configuração de amostragem de consultas para calcular as métricas. Como parte das novas funcionalidades, agora é possível configurar a amostragem de consultas usando o método configureQueryAnalyzer()
.
Como podemos ver, o método analyzeShardKey()
é uma implementação extremamente útil, especialmente porque permite uma avaliação mais precisa da shard key, um componente crítico em Sharded Clusters e, muitas vezes, fonte de problemas.
3. Verificador de Metadados do Cluster – checkMetadataConsistency()
Junto com o analisador de shard key, o verificador de metadados é mais um avanço em termos de ferramentas que facilitam a vida dos DBAs.
Sharded Clusters são uma grande solução, mas, ao mesmo tempo, são complexos, especialmente quando começamos a entender como funcionam nos bastidores para garantir aspectos como consistência.
Para quem administra um sharded cluster, é provável que já tenha enfrentado problemas de inconsistência de metadados, como:
- Coleções com diferentes UUIDs, tabelas de roteamento com intervalos sobrepostos e muitos outros problemas desagradáveis.
O grande desafio aqui é que lidar com metadados é uma tarefa complicada, pois modificá-los pode causar uma série de problemas e colocar o cluster em um estado indesejado – algo que queremos evitar a todo custo.
Além disso, a inconsistência de metadados pode estar presente silenciosamente no cluster, e você só descobrirá quando uma operação ou cenário específico ocorrer.
O helper checkMetadataConsistency()
é uma excelente adição para ajudá-lo a rastrear essas inconsistências:
db.checkMetadataConsistency()
– Executa verificações de consistência nos metadados de sharding para o cluster ou banco de dados.db.collection.checkMetadataConsistency()
– Executa verificações de consistência nos metadados de sharding para uma coleção específica.sh.checkMetadataConsistency()
– Executa verificações de consistência nos metadados de sharding para o cluster.
O comando realiza uma série de verificações de consistência (13 no total) nos metadados de sharding e nos índices (não habilitado por padrão), buscando inconsistências.
Esse recurso é especialmente útil após processos de upgrade ou downgrade, quando os metadados são alterados devido a mudanças no Feature Compatibility Version (FCV). Além disso, ele é valioso para verificações regulares, ajudando a evitar surpresas desagradáveis ao realizar uma varredura completa dos componentes delicados do seu cluster.
4. Usuários Linux não podem modificar o taskExecutorPoolSize
para valores diferentes de 1
Se você é um usuário de Mac ou Windows, essa alteração não o afeta. No entanto, para usuários de Linux, não é mais possível modificar o tamanho do pool de conexões no mongos
:
1 2 |
$ /opt/mongo/7.0.1/bin/mongos --logpath /mongo_data/701_1shard/mongos.log --port 7001 --configdb configRepl/localhost:7005 --keyFile /mongo_data/701_1shard/keyfile --setParameter taskExecutorPoolSize=6 --fork -vvv {"t":{"$date":"2023-09-27T15:06:29.476Z"},"s":"F", "c":"-", "id":22865, "ctx":"main","msg":"Error during global initialization","attr":{"error":{"code":2,"codeName":"BadValue","errmsg":"Unknown --setParameter 'taskExecutorPoolSize'"}}} {"t":{"$date":"2023-09-27T15:06:29.476Z"},"s":"I", "c":"CONTROL", "id":23138, "ctx":"main","msg":"Shutting down","attr":{"exitCode":14}} |
O taskExecutorPoolSize
tem como valor padrão 1 desde o MongoDB 4.0. Após o rapid release da versão 6.2 (disponível apenas para usuários do Atlas), não foi mais possível modificar esse parâmetro. Essa mudança foi então incorporada na versão 7.0.
Embora o taskExecutorPoolSize
tenha sido 1 por muito tempo, era possível ajustá-lo. No entanto, essa “otimização” frequentemente trazia mais problemas do que benefícios, pois esse parâmetro não opera isoladamente.
O conceito e o problema por trás disso são simples:
Para encontrar o número máximo de conexões de saída que cada pool de conexões do TaskExecutor
pode abrir para uma instância específica do mongod
, usamos o seguinte cálculo:
1 2 |
shardingTaskExecutorPoolMaxSize * taskExecutorPoolSize 32767 * 1 |
Ou seja, um único pool de tarefas pode abrir até 32.767 conexões com o mongod
.
Agora, suponha que você tenha uma CPU de quatro núcleos e configure o taskExecutorPoolSize=4
:
- 4 * 32767 = 131068.
E, além disso, você possui mais de um mongoS
; suponha que sejam quatro, o que é uma boa prática:
- 4 * 131068 = 524272
No final do dia, seu cluster pode abrir até meio milhão de conexões entre o mongoS
e o mongod
.
Isso é uma fonte comum de problemas, pois, se sua aplicação não impõe nenhuma restrição de conexões e começa a solicitar conexões para executar operações, você pode facilmente acabar em uma “tempestade de conexões”, realizando milhares de operações em um curto período. Isso pode sobrecarregar o cluster e prejudicar significativamente o desempenho.
Não ser capaz de modificar o taskExecutorPoolSize
é uma boa medida para proteger o sistema contra parametrizações superestimadas que podem causar sérios problemas no cluster.
5. Política mais rígida de downgrade
A documentação oficial do MongoDB destaca o seguinte:
A partir do MongoDB 7.0, downgrades binários não são mais suportados para o MongoDB Community Edition.
Antes do MongoDB 7.0, o processo de downgrade era relativamente simples:
- Fazer um backup válido.
- Remover recursos incompatíveis com versões anteriores.
- Ajustar o Feature Compatibility Version (FCV) para a versão anterior.
- Reiniciar o banco de dados, substituindo os binários pela versão mais antiga.
Agora você pode estar pensando:
“O que acontece se eu realizar um downgrade como costumávamos fazer?”
No momento da redação deste artigo, não havia um bloqueio explícito no MongoDB, como um aviso ou uma impossibilidade técnica de seguir com o processo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
replset [direct: primary] admin> db.adminCommand( { setFeatureCompatibilityVersion: "6.0" , confirm: true} ) { ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1695819202, i: 3 }), signature: { hash: Binary.createFromBase64("y9oQXuzpYR0Zedzu6EP2nXSE290=", 0), keyId: Long("7283486964546797575") } }, operationTime: Timestamp({ t: 1695819202, i: 3 }) } replset [direct: primary] admin> replset [direct: primary] admin> db.shutdownServer() MongoNetworkError: connection 2 to 127.0.0.1:7001 closed |
1 2 3 |
/opt/mongo/6.0.6-5/bin/mongod --replSet replset --dbpath /mongo_data/701_rs1/replset/rs1/db --logpath /mongo_data/701_rs1/replset/rs1/mongod.log --port 7001 --fork --keyFile /mongo_data/701_rs1/keyfile --wiredTigerCacheSizeGB 1 about to fork child process, waiting until server is ready for connections. forked process: 1609222 child process started successfully, parent exiting |
No entanto, mesmo que você garanta que todas as incompatibilidades com versões anteriores foram resolvidas, o que pode incluir a remoção de dados incompatíveis, lembre-se de que essa ação será realizada por sua própria conta e risco.
Além disso, o MongoDB 7.0 introduziu outra ação não suportada.
Suponha que você está atualizando para acompanhar as versões mais recentes e seguiu este caminho: 4.4.x -> 5.0.x -> 6.0.x -> 7.0.x
Após a atualização, você identifica um problema e decide fazer um downgrade para 4.4.x ou mesmo para 5.0.x. Para o MongoDB 7.0, isso não é mais suportado, conforme destacado na documentação oficial:
O MongoDB suporta apenas downgrades de versão única. Você não pode fazer o downgrade para uma versão que esteja várias versões atrás da versão atual.
Embora essa prática não seja ideal, não é incomum ver empresas tentando acompanhar as novas versões devido ao fim do suporte (EOL) de versões anteriores, realizando várias atualizações em um curto período. Isso agora pode ser problemático caso seja necessário reverter.
Se você tem uma dúvida semelhante, a resposta também é similar.
No momento da redação deste artigo, não havia um bloqueio explícito no MongoDB, como um aviso ou uma impossibilidade técnica de seguir com o downgrade.
No entanto, mesmo que você tenha garantido que todas as incompatibilidades com versões anteriores foram resolvidas e pareça estar pronto para prosseguir, lembre-se de que essa ação será realizada por sua conta e risco.
Como observação final, seja atualizando ou fazendo downgrade, é sempre recomendado testar o procedimento em um ambiente de desenvolvimento/QA que reflita o ambiente de produção, permitindo que você se familiarize com o processo e resolva quaisquer dúvidas antes de aplicá-lo em produção.
Conclusão
O MongoDB 7.0 trouxe várias mudanças nesta nova versão, e as mencionadas acima são aquelas que acreditamos que possam impactar diretamente o seu uso diário, seja de forma positiva ou negativa, como as novas políticas relacionadas ao downgrade.
Falando nisso, se você não se sente confortável em realizar atualizações/downgrades ou está em busca de suporte especializado para o seu ambiente MongoDB, entre em contato.
Se você tiver dúvidas ou encontrou outras mudanças interessantes no MongoDB 7.0, sinta-se à vontade para compartilhar na seção de comentários.
Até mais!