Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
MongoDBchevron-right

Avaliação de desempenho do esquema no MongoDB usando o PerformanceBench

Graeme Robinson20 min read • Published Jan 18, 2023 • Updated Apr 02, 2024
MongoDBJava
Ícone do FacebookÍcone do Twitterícone do linkedin
Avaliação de desempenho do esquema
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
O MongoDB é frequentemente descrito incorretamente como sem esquema. Embora seja verdade que o MongoDB ofereça um nível de flexibilidade ao trabalhar com projetos de esquemas que os sistemas de bancos de dados relacionais tradicionais não conseguem igualar, como acontece com qualquer sistema de banco de dados, a escolha do projeto de esquema empregado por um aplicativo criado com base no MongoDB ainda determinará, em última análise, se o aplicativo conseguirá atingir seus objetivos de desempenho e SLAs.
Felizmente, existem vários padrões de design (e antipadrão correspondentes) para ajudar a orientar os desenvolvedores de aplicativos a criar esquemas apropriados para seus aplicativos MongoDB . Uma parte significativa de nosso papel como apoiadores do programador dentro da equipe de contas estratégias globais no MongoDB envolve instruir desenvolvedores novos no MongoDB sobre o uso desses padrões de design e como eles diferem daqueles que podem ter usado anteriormente para trabalhar com sistemas de banco de dados relacionais. Meu colega, David Coupal, contribui para um conjunto fantástico de blog posts sobre os padrões e antipadrãomais comuns que vemos trabalhar com o MongoDB.
Embora os padrões de design de esquemas sejam um excelente ponto de partida para orientar nosso processo de design, para muitos aplicativos, pode chegar um ponto em que não fique claro qual dos conjuntos de designs alternativos oferecerá melhor suporte às cargas de trabalho previstas para o aplicativo. Nessas situações, uma citação do Contra-Almirante Grace Hopper, que meu gerente, Rick Houlihan, me fez conhecer, soa verdadeira:"One accurate measurement is worth a thousand expert opinions."
Neste artigo, exploraremos o uso doPerformanceBench, um aplicativo de estrutura Java usado pela minha equipe ao avaliar modelos de dados candidatos para uma carga de trabalho do cliente.

PerformanceBench

O PerformanceBench é uma estrutura Java simples projetada para permitir que os desenvolvedores avaliem o desempenho relativo de diferentes padrões de design de banco de dados no MongoDB.
O PerformanceBench define sua funcionalidade em termos de modelos (os padrões de design que estão sendo avaliados) e medidas (as operações a serem medidas em relação a cada modelo). Como exemplo, um desenvolvedor pode avaliar o desempenho relativo de um design com base em dados distribuídos por várias collections e acessados usando agregações$lookup (join), versus um baseado em um modelo hierárquico em que documentos relacionados são incorporados uns aos outros. Nesse cenário, os modelos podem ser chamados respectivamente de multi-collection e hierárquico, com as "medidas" para cada um sendo operações CRUD: criar, ler, atualizare excluir.
A estrutura permite que sejam criadas classes Java que implementem uma interface definida conhecida como "SchemaTest, " com uma classe para cada modelo a ser testado. Cada classeSchemaTest implementa a funcionalidade para executar as medidas definidas para esse modelo e retorna, como saída, uma array de documentos com os resultados da execução de cada medida — normalmente dados de tempo para a execução da medida, além de quaisquer metadados necessários para identificar posteriormente as parâmetros usados para a execução específica. O PerformanceBench armazena esses documentos retornados em uma collection MongoDB para análise e avaliação posterior.
O PerformanceBench é configurado por meio de um arquivo de configuração no formato JSON que contém uma matriz de documentos - um para cada modelo que está sendo testado. Cada documento de modelo no arquivo de configuração contém um conjunto de campos padrão que são comuns a todos os modelos que estão sendo testados, além de um conjunto de campos personalizados específicos para esse modelo. Os desenvolvedores que implementam as classes de modelo doSchemaTest são livres para incluir quaisquer parâmetros personalizados que o teste de um modelo específico exija.
Quando executado, o PerformanceBench utiliza os dados no arquivo de configuração para identificar a classe de implementação de cada modelo a ser testado e suas medidas associadas. Em seguida, ele instrui as classes de implementação a executar um número específico de iterações de cada medida, opcionalmente usando vários threads para simular ambientes multiusuários/multiclientes.
Detalhes completos da interfaceSchemaTest e do formato do arquivo de configuração PerformanceBench JSON são fornecidos no arquivo GitHub readme para o projeto.
O código-fonte do PerformanceBench no Github foi desenvolvido usando o IntelliJ IDEA 2022.2.3 com o OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7).
O aplicativo compilado foi executado no Amazon Linux usando OpenJDK 17.0.5 (2022-10-18 LTS - Correto).

Projetando classes de modelo SchemaTest: fatores a considerar

Além da exigência de implementar a interface SchemaTest, o PerformanceBench oferece aos desenvolvedores de classes de modelo ampla liberdade para projetar suas classes da maneira que for necessária para atender aos requisitos de seus casos de teste. No entanto, há algumas considerações comuns a serem consideradas.

Entender a intenção dos métodos da interface do SchemaTest

A interfaceSchemaTest define os quatro métodos a seguir:
1public void initialize(JSONObject args);
1public String name();
1public void warmup(JSONObject args);
1public Document[] executeMeasure(int opsToTest, String measure, JSONObject args);
1public void cleanup(JSONObject args);
O métodoinitialize destina-se a permitir que as classes de implementação realizem as etapas necessárias antes da execução das medidas. Isso pode incluir, por exemplo, estabelecer e verificar a conexão com o banco de dados, criar ou preparar um conjunto de dados de teste e/ou remover os resultados de execuções anteriores. As chamadas do PerformanceBench são inicializadas imediatamente após a instanciação de uma instância da classe, mas antes que qualquer medida seja executada.
O métodoname deve retornar um nome de string para a classe de implementação. Os implementadores de classe podem definir o valor retornado para qualquer valor que faça sentido para seu caso de uso. Atualmente, o PerformanceBench usa esse método apenas para adicionar contexto às mensagens de registro.
O métodowarmup é chamado pelo PerformanceBench antes da execução de qualquer iteração de qualquer medida. Ele foi projetado para permitir que os implementadores da classe de modelo tentem criar um ambiente que reflita com precisão o estado esperado do banco de dados na vida real. Isso poderia, por exemplo, incluir a realização de consultas projetadas para alimentar o cache do MongoDB com um conjunto de dados de trabalho apropriado.
O métodoexecuteMeasure permite que PerformanceBench instrua uma classe de implementação de modelo a executar um número definido de iterações de uma medida especificada. Normalmente, a implementação do método conterá uma declaração de caso redirecionando a execução para o código de cada medida definida. No entanto, não há nenhum requisito para implementar dessa forma. O retorno desse método deve ser uma array de objetos deDocumentoBSON contendo os resultados de cada iteração de teste. Os implementadores são livres para incluir quaisquer campos necessários nesses documentos para dar suporte às métricas que seu caso de uso exige.
O métodode limpeza é chamado pelo PerformanceBench após todas as iterações de todas as medidas terem sido executadas pela classe de implementação e foi projetado principalmente para permitir que os dados de teste sejam excluídos ou redefinidos antes de futuras execuções de teste. Entretanto, o método também pode ser usado para executar qualquer outra funcionalidade pós-execução de teste necessária para um determinado caso de uso. Isso pode incluir, por exemplo, o cálculo dos tempos de execução médios/medianos/percentuais de uma execução de teste ou a desconexão limpa de um banco de dados.

Executar medidas usando vários conjuntos de dados de teste

Ao avaliar um determinado modelo, é importante medir o desempenho do modelo em relação a conjuntos de dados variados. Por exemplo, todos os itens a seguir podem afetar o desempenho de diferentes operações de pesquisa e manipulação de dados:
  • Tamanhos gerais de banco de dados e coleção
  • Tamanhos individuais de documentos
  • CPU e memória disponíveis nos servidores MongoDB em uso
  • Número total de documentos em coleções individuais.
A execução de uma sequência de medidas utilizando diferentes conjuntos de dados de teste pode ajudar a identificar se existe um limite além do qual um modelo pode ter um desempenho melhor que outro. Também pode ajudar a identificar a quantidade de memória necessária para armazenar o conjunto de dados de trabalho necessário para a carga de trabalho que está sendo testada, para evitar paginação excessiva. As classes de implementação do modelo devem garantir que adicionam metadados suficientes aos documentos de resultados que geram para permitir que as condições do teste sejam identificadas durante análises posteriores.

Garantir que as queries sejam suportadas por índices apropriados

Como acontece com a maioria dos bancos de dados, o desempenho da query no MongoDB depende dos índices apropriados existentes nas collections que estão sendo consultadas. Os implementadores de classes de modelo devem garantir que esses índices necessários para seus casos de teste existam ou sejam criados durante a chamada para o método deinicializaçãode suas classes. O tamanho do índice em comparação com a memória de cache disponível deve ser considerado e, frequentemente, encontrar o ponto em que o desempenho é impactado negativamente pela paginação de índices é um objetivo principal do teste PerformanceBench.

Remover variáveis como latência de rede

Em qualquer regime de teste, um dos objetivos deve ser limitar o número de variáveis que podem afetar as discrepâncias de desempenho entre as execuções de teste, de modo que as diferenças no desempenho medido possam ser atribuídas com confiança às diferenças intencionais nas condições de teste. Os itens que vêm sob este cabeçalho incluem latência de rede entre o servidor que executa o PerformanceBench e os servidores do cluster MongoDB. Ao trabalhar com o MongoDB Atlas em um ambiente de nuvem, por exemplo, especificar servidores dedicados em vez de compartilhados pode ajudar a evitar que a carga de background nos servidores afete o desempenho, enquanto a implantação de todos os servidores na mesma zona/região de disponibilidade pode reduzir os possíveis impactos da variação de latência de rede.

Modele ambientes multiusuários de forma realista

O PerformanceBench permite que as medidas sejam executadas simultaneamente em vários threads para simular um ambiente multiusuário. No entanto, se estiver usando esse recurso, pense em como modelar com precisão o comportamento real do usuário. É raro, por exemplo, que os usuários executem um pipeline de agregação ad-hoc complexo e executem imediatamente outro após sua conclusão. Sua classe de modelo pode, portanto, querer inserir um atraso entre a execução de iterações de medida para tentar modelar um período de tempo realista que você pode esperar entre solicitações de consulta de um usuário individual em um ambiente de produção realista.

APIMonitor: um exemplo de implementação do modelo PerformanceBench

O repositório do PerformanceBench do Github inclui exemplos de implementações de classe de modelo para um aplicativo hipotético projetado para relatar as taxas de sucesso e insucesso de chamadas para um conjunto de API monitoradas por software de observabilidade.
Os dados do aplicativo são armazenados em dois tipos de documento em duas collection diferentes.
A collectionAPIDetails contém um documento para cada API monitorada com metadados sobre essa API:
1{
2 "_id": "api#9",
3 "apiDetails": {
4 "appname": "api#9",
5 "platform": "Linux",
6 "language": {
7 "name": "Java",
8 "version": "11.8.202"
9 },
10 "techStack": {
11 "name": "Springboot",
12 "version": "UNCATEGORIZED"
13 },
14 "environment": "PROD"
15 },
16 "deployments": {
17 "region": "UK",
18 "createdAt": {
19 "$date": {
20 "$numberLong": "1669164599000"
21 }
22 }
23 }
24}
A segunda collection, API, foi projetada para representar a saída do software de monitoramento com um documento gerado para cada API em intervalos 15minutos, fornecendo o número total de chamadas para a API, o número que foram bem-sucedidas e o número que falhou:
1{
2 "_id": "api#1#S#2",
3 "appname": "api#1",
4 "creationDate": {
5 "$date": {
6 "$numberLong": "1666909520000"
7 }
8 },
9 "transactionVolume": 54682,
10 "errorCount": 33302,
11 "successCount": 21380,
12 "region": "TK",
13 "year": 2022,
14 "monthOfYear": 10,
15 "dayOfMonth": 27,
16 "dayOfYear": 300
17}
Os documentos incluem um valor de região de implantação para cada API (um de "Tokyo, "Hong Kong, "India, " ou "UK "). As classes de modelo de amostra no repositório foram projetadas para comparar o desempenho das opções para executar pipelines de agregação que calculam o número total de chamadas, a taxa geral de sucesso e a taxa de falha correspondente para todas as API em uma determinada região, em um determinado período de tempo.
São avaliadas quatro abordagens:
  1. Realização de um pipeline de agregação na collectionAPIDetails que inclui um estágio$lookup para realizar uma união e um resumo dos dados relevantes na collectionAPIMetrics.
  2. Realizar uma query inicial na collectionAPIDetails para produzir uma lista dos ids de API para uma determinada região e usar essa lista como entrada para uma cláusula $in como parte de um estágio$match em um pipeline de agregação separado na collection APIMetrics para resumir os dados de monitoramento relevantes.
  3. Uma terceira abordagem que usa uma cláusula de igualdade nas informações da região em cada documento como parte do estágioinicial de $match de um pipeline na collection APIMetrics para resumir os dados de monitoramento relevantes. Essa abordagem foi projetada para testar se uma correspondência de igualdade em relação a um único valor tem um desempenho melhor do que uma usando uma cláusula$in com um grande número de valores possíveis, conforme usado na segunda abordagem. Duas medidas são implementadas neste modelo: uma que consulta as duas collections sequencialmente usando o driver Java padrão do MongoDB e outra que consulta as duas collections em paralelo usando o driver MongoDB Java Reactive Streams.
  4. Uma quarta abordagem que adiciona uma terceira collection chamada APIPreCalc que armazena documentos com total de chamadas pré-calculadas, total de chamadas com falha e total de chamadas bem-sucedidas para cada API para cada dia, mês e ano completos no conjunto de dados, com o objetivo de reduzir o número de documentos e o tamanho dos cálculos que o aggregation pipeline deve executar. Esse modelo é um exemplo de implementação do padrão dedesign de esquema computado e também usa o driverJava Reactive Streamsdo MongoDB para consultar as collections em paralelo.
Para a quarta abordagem, os documentos pré-computados na coleçãoAPIPreCalc têm a seguinte aparência:
1{
2 "_id": "api#379#Y#2022",
3 "transactionVolume": 166912052,
4 "errorCount": 84911780,
5 "successCount": 82000272,
6 "region": "UK",
7 "appname": "api#379",
8 "metricsCount": {
9 "$numberLong": "3358"
10 },
11 "year": 2022,
12 "type": "year_precalc",
13 "dateTag": "2022"
14},
15{
16 "_id": "api#379#Y#2022#M#11",
17 "transactionVolume": 61494167,
18 "errorCount": 31247475,
19 "successCount": 30246692,
20 "region": "UK",
21 "appname": "api#379",
22 "metricsCount": {
23 "$numberLong": "1270"
24 },
25 "year": 2022,
26 "monthOfYear": 11,
27 "type": "month_precalc",
28 "dateTag": "2022-11"
29},
30{
31 "_id": "api#379#Y#2022#M#11#D#19",
32 "transactionVolume": 4462897,
33 "errorCount": 2286438,
34 "successCount": 2176459,
35 "region": "UK",
36 "appname": "api#379",
37 "metricsCount": {
38 "$numberLong": "96"
39 },
40 "year": 2022,
41 "monthOfYear": 11,
42 "dayOfMonth": 19,
43 "type": "dom_precalc",
44 "dateTag": "2022-11-19"
45}
Observe o campo detipo nos documentos usados para diferenciar os totais de um ano, mês ou dia do mês.
Para mostrar como o PerformanceBench organiza modelos e medidas, no repositório do PerformanceBench GitHub, a primeira e a segunda abordagens são implementadas como duas classes de modeloSchemaTestseparadas, cada uma com uma única medida, enquanto a terceira e quarta abordagens são implementadas em um terceiro SchemaTest classe de modelo com duas medidas - uma para cada abordagem.

Classe APIMonitorLookupTest

O primeiro modelo, implementando a abordagem $lookup, é implementado no pacote com.mongodb.devrel.pods.performancebench.models.apimonitor_lookup em uma classe chamada APIMonitorLookupTest.
O pipeline de agregação implementado por essa abordagem é:
1[
2 {
3 $match: {
4 "deployments.region": "HK",
5 },
6 },
7 {
8 $lookup: {
9 from: "APIMetrics",
10 let: {
11 apiName: "$apiDetails.appname",
12 },
13 pipeline: [
14 {
15 $match: {
16 $expr: {
17 $and: [
18 {
19 $eq: ["$apiDetails.appname", "$$apiName"],
20 },
21 {
22 $gte: [
23 "$creationDate", ISODate("2022-11-01"),
24 ],
25 },
26 ],
27 },
28 },
29 },
30 {
31 $group: {
32 _id: "apiDetails.appName",
33 totalVolume: {
34 $sum: "$transactionVolume",
35 },
36 totalError: {
37 $sum: "$errorCount",
38 },
39 totalSuccess: {
40 $sum: "$successCount",
41 },
42 },
43 },
44 {
45 $project: {
46 aggregatedResponse: {
47 totalTransactionVolume: "$totalVolume",
48 errorRate: {
49 $cond: [
50 {
51 $eq: ["$totalVolume", 0],
52 },
53 0,
54 {
55 $multiply: [
56 {
57 $divide: [
58 "$totalError",
59 "$totalVolume",
60 ],
61 },
62 100,
63 ],
64 },
65 ],
66 },
67 successRate: {
68 $cond: [
69 {
70 $eq: ["$totalVolume", 0],
71 },
72 0,
73 {
74 $multiply: [
75 {
76 $divide: [
77 "$totalSuccess",
78 "$totalVolume",
79 ],
80 },
81 100,
82 ],
83 },
84 ],
85 },
86 },
87 _id: 0,
88 },
89 },
90 ],
91 as: "results",
92 },
93 },
94]
O pipeline é executado na coleçãoAPIDetails e é executado uma vez para cada uma das quatro regiões geográficas. O estágio$lookup do pipeline contém seu próprio subpipeline que é executado na collectionAPIMetrics uma vez para cada API pertencente a cada região.
Isso resulta em documentos com a seguinte aparência sendo produzidos:
1{
2 "_id": "api#100",
3 "apiDetails": {
4 "appname": "api#100",
5 "platform": "Linux",
6 "language": {
7 "name": "Java",
8 "version": "11.8.202"
9 },
10 "techStack": {
11 "name": "Springboot",
12 "version": "UNCATEGORIZED"
13 },
14 "environment": "PROD"
15 },
16 "deployments": [
17 {
18 "region": "HK",
19 "createdAt": {
20 "$date": {
21 "$numberLong": "1649399685000"
22 }
23 }
24 }
25 ],
26 "results": [
27 {
28 "aggregatedResponse": {
29 "totalTransactionVolume": 43585837,
30 "errorRate": 50.961542851637795,
31 "successRate": 49.038457148362205
32 }
33 }
34 ]
35}
Um documento será produzido para cada API em cada região. A implementação do modelo registra o tempo total gasto (em milissegundos) para gerar todos os documentos para uma determinada região e o retorna em um documento de resultados para o PerformanceBench. Os documentos de resultados são apresentados assim:
1{
2 "_id": {
3 "$oid": "6389b6581a3cd92944057c6c"
4 },
5 "startTime": {
6 "$numberLong": "1669962059685"
7 },
8 "duration": {
9 "$numberLong": "1617"
10 },
11 "model": "APIMonitorLookupTest",
12 "measure": "USEPIPELINE",
13 "region": "HK",
14 "baseDate": {
15 "$date": {
16 "$numberLong": "1667260800000"
17 }
18 },
19 "apiCount": 189,
20 "metricsCount": 189,
21 "threads": 3,
22 "iterations": 1000,
23 "clusterTier": "M10",
24 "endTime": {
25 "$numberLong": "1669962061302"
26 }
27}
Como pode ser visto, bem como a região, a hora de início, a hora de término e a duração da execução da execução, os documentos de resultado também incluem:
  • O nome do modelo e a medida executada (neste caso, 'USEPIPELINE').
  • O número de APIs (apiCount) encontradas para essa região e o número de APIs para as quais as métricas foram geradas (metricsCount). Esses números devem sempre corresponder e são incluídos como uma verificação de sanidade de que os dados foram gerados corretamente pela medida.
  • O número de threads e iterações usadas para a execução da medida. O PerformanceBench permite que as medidas sejam executadas um número definido de vezes (iterações) para permitir que uma boa média seja determinada. As execuções também podem ser executadas em um ou mais threads simultâneos para simular ambientes multiusuário/multicliente. No exemplo acima, três threads executaram simultaneamente 1,000 iterações da medida (3,000 iterações totais).
  • A camada do cluster MongoDB Atlas na qual as medidas foram executadas. Isso é usado simplesmente para fins de rastreamento ao analisar os resultados e pode ser definido como qualquer valor pelo desenvolvedor da classe. Nas implementações de classe de exemplo, o valor é definido para corresponder a um valor correspondente no arquivo de configuração do PerformanceBench. É importante ressaltar que continua sendo responsabilidade do usuário garantir que a camada de cluster usada corresponda ao que está escrito nos documentos de resultados.
  • baseDate indica o período de datas para o qual os dados de monitoramento foram resumidos. Para um determinado baseDate, o período resumido é sempre baseDate para a data atual (inclusive). Uma baseDateanterior, portanto, resultará no resumo de mais dados.
Com uma única medida definida para o modelo e com três threads cada realizando 1,000 iterações da medida, uma array de 3,000 documentos de resultados será retornada pela classe de modelo para o PerformanceBench. O PerformanceBench então grava esses documentos em uma collection para análise posterior.
Para dar suporte ao aggregation pipeline, a implementação do modelo cria os seguintes índices na implementação do método deinicialização :
APIDetails: {"deployments.region": 1} APIMetrics: {"appname": 1, "creationDate": 1}
O modelo elimina temporariamente todos os índices existentes na coleção para evitar a disputa por espaço no cache da memória. Os índices acima são descartados posteriormente na implementação do método delimpezado modelo, e todos os índices originais são restaurados.

Classe APIMonitorMultiQueryTest

O segundo modelo realiza uma query inicial na collectionAPIDetails para produzir uma lista dos ids de API para uma determinada região e, em seguida, usa essa lista como entrada para uma cláusula $in como parte de um estágio$match em um pipeline de agregação contra as métricas API collection. Ele é implementado no pacote com.mongodb.devrel.pods.performancebench.models.apimonitor_multiquery em uma classe chamada APIMonitorMultiQueryTest.
A query inicial, realizada em relação à collectionAPIDetails, parece:
1db.APIDetails.find("deployments.region": "HK")
Essa query é realizada para cada uma das quatro regiões alternadamente e, a partir dos documentos retornados, é gerada uma lista das APIs pertencentes a cada região. A lista gerada é então usada como entrada para uma cláusula $in no estágio$match da seguinte pipeline de agregação executada em relação à coleção APIMetrics:
1[
2 {
3 $match: {
4 "apiDetails.appname": {$in: ["api#1", "api#2", "api#3"]},
5 creationDate: {
6 $gte: ISODate("2022-11-01"),
7 },
8 },
9 },
10 {
11 $group: {
12 _id: "$apiDetails.appname",
13 totalVolume: {
14 $sum: "$transactionVolume",
15 },
16 totalError: {
17 $sum: "$errorCount",
18 },
19 totalSuccess: {
20 $sum: "$successCount",
21 },
22 },
23 },
24 {
25 $project: {
26 aggregatedResponse: {
27 totalTransactionVolume: "$totalVolume",
28 errorRate: {
29 $cond: [
30 {
31 $eq: ["$totalVolume", 0],
32 },
33 0,
34 {
35 $multiply: [
36 {
37 $divide: ["$totalError", "$totalVolume"],
38 },
39 100,
40 ],
41 },
42 ],
43 },
44 successRate: {
45 $cond: [
46 {
47 $eq: ["$totalVolume", 0],
48 },
49 0,
50 {
51 $multiply: [
52 {
53 $divide: [
54 "$totalSuccess",
55 "$totalVolume",
56 ],
57 },
58 100,
59 ],
60 },
61 ],
62 },
63 },
64 },
65 },
66]
Esse pipeline é essencialmente o mesmo que o subpipeline no estágio$lookup da agregação usada pela classeAPIMonitorLookupTest , a principal diferença é que esse pipeline retorna os documentos resumidos para todas as APIs em uma região usando uma única execução, enquanto o sub -pipeline é executado uma vez por API como parte do estágio $lookup na classeAPIMonitorLookupTest . Observe que o pipeline mostrado acima tem apenas três valores de API listados em sua cláusula $in . Na verdade, a lista gerada durante o teste tinha entre duzentos e três mil itens para cada região.
Quando os documentos são retornados do pipeline, eles são mesclados com os documentos de detalhes da API correspondentes recuperados da consulta inicial para criar um conjunto de documentos equivalentes aos criados pelo pipeline na classeAPIMonitorLookupTest. A partir daí, a implementação do modelo cria os mesmos documentos de resumo a serem retornados e salvos pelo PerformanceBench.
Para dar suporte ao pipeline, a implementação do modelo cria os seguintes índices em sua implementação do métodoinitialize:
APIDetails: {"deployments.region": 1} APIMetrics: {"appname": 1, "creationDate": 1}
Assim como na classeAPIMonitorLookupTest, esse modelo elimina temporariamente todos os índices existentes nas collections para evitar a contenção de espaço no cache de memória. Os índices acima são posteriormente descartados na implementação do método de limpezado modelo e todos os índices originais são restaurados.

Classe APIMonitorRegionTest

A terceira classe de modelo, com.mongodb.devrel.pods.performancebench.models.apibamonitor_regionquery.APIMonitorRegionTest , implementa duas medidas, ambas semelhantes à medida emAPIMonitorMultiQueryTest , mas em que a cláusula$in no estágio $match é substituída por uma verificação de equivalência no campo "region" . O objetivo dessas medidas é avaliar se uma verificação de equivalência em relação ao campo região fornece algum benefício de desempenho em relação a umacláusula $in em que a lista de valores correspondentes pode ter várias centenas de itens. A diferença entre as duas medidas neste modelo, chamadas" QUERYSYNC" e QUERYASYNC" " respectivamente, é que a primeira executa a query de localização inicial na collectionAPIDetails e, em seguida, o pipeline de agregação na collectionAPIMetrics em sequência, enquanto o segundo modelo usa o Driver MongoDB Reactive Streams para realizar as duas operações em paralelo e avaliar se isso oferece algum benefício de desempenho.
Com essas alterações, o estágio de correspondência do pipeline de agregação para esse modelo fica assim:
1 {
2 $match: {
3 "deployments.region": "HK",
4 creationDate: {
5 $gte: ISODate("2022-11-01"),
6 },
7 },
8 }
Em todos os outros aspectos, o pipeline e os processos subsequentes para a criação de documentos de resumo a serem repassados ao PerformanceBench são os mesmos usados no APIMonitorMultiQueryTest.

Classe APIMonitorPrecomputeTest

A quarta classe de modelo, com.mongodb.devrel.pods.performancebench.models.apibamonitor_precompute.APIMonitorPrecomputeTest, implementa uma única medida chamada "PRECOMPUTE ". Essa medida usa uma terceira coleção chamada APIPreCalc que contém dados resumidos pré-calculados para cada API para cada dia, mês e ano completos no conjunto de dados. A intenção com essa medida é avaliar qual, se houver, ganho de desempenho pode ser obtido reduzindo o número de documentos e cálculos resultantes que o pipeline de agregação é necessário realizar.
A medida calcula dias, meses e anos completos entre o baseDate especificado no arquivo de configuração e a data atual. O número total de chamadas, chamadas com falha e chamadas bem-sucedidas para cada API para cada dia, mês ou ano completo é recuperado de APIPreCalc. Um estágio$unionWith no pipeline é então usado para combinar esses valores com as métricas para os dias parciais em cada final do período (a data baseada e a data atual) recuperado de APIMetrics.
O pipeline usado para essa medida se parece com:
1[
2 {
3 "$match": {
4 "region": "UK",
5 "dateTag": {
6 "$in": [
7 "2022-12",
8 "2022-11-2",
9 "2022-11-3",
10 "2022-11-4",
11 "2022-11-5",
12 "2022-11-6",
13 "2022-11-7",
14 "2022-11-8",
15 "2022-11-9",
16 "2022-11-10"
17 ]
18 }
19 }
20 },
21 {
22 "$unionWith": {
23 "coll": "APIMetrics",
24 "pipeline": [
25 {
26 "$match": {
27 "$expr": {
28 "$or": [
29 {
30 "$and": [
31 {
32 "$eq": [
33 "$region",
34 "UK"
35 ]
36 },
37 {
38 "$eq": [
39 "$year", 2022
40 ]
41 },
42 {
43 "$eq": [
44 "$dayOfYear",
45 305
46 ]
47 },
48 {
49 "$gte": [
50 "$creationDate",
51 {
52 "$date": "2022-11-01T00:00:00Z"
53 }
54 ]
55 }
56 ]
57 },
58 {
59 "$and": [
60 {
61 "$eq": [
62 "$region",
63 "UK"
64 ]
65 },
66 {
67 "$eq": [
68 "$year",
69 2022
70 ]
71 },
72 {
73 "$eq": [
74 "$dayOfYear",
75 315
76 ]
77 },
78 {
79 "$lte": [
80 "$creationDate",
81 {
82 "$date": "2022-11-11T01:00:44.774Z"
83 }
84 ]
85 }
86 ]
87 }
88 ]
89 }
90 }
91 }
92 ]
93 }
94 },
95 {
96 "$group": {
97
98 }
99 },
100 {
101 "$project": {
102
103 }
104 }
105 ]
Os estágios$group e $project são idênticos aos modelos anteriores e não são mostrados acima.
Para suportar as consultas realizadas pelo pipeline, o modelo cria os seguintes índices na implementação do método deinicialização :
APIDetails: {"deployments.region": 1} APIMetrics: {"region": 1, "year": 1, "dayOfYear": 1, "creationDate": 1} APIPreCalc: {"region": 1, "dateTag": 1}

Controlando a execução do PerformanceBench — config.json

A execução do PerformanceBench é controlada por um arquivo de configuração no formato JSON. O nome e o caminho para esse arquivo são passados como um argumento de linha de comando usando o sinalizador-c . No repositório PerformanceBench GitHub, o arquivo é chamado config.json:
1{
2 "models": [
3 {
4 "namespace": "com.mongodb.devrel.pods.performancebench.models.apimonitor_lookup",
5 "className": "APIMonitorLookupTest",
6 "measures": ["USEPIPELINE"],
7 "threads": 2,
8 "iterations": 500,
9 "resultsuri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
10 "resultsCollectionName": "apimonitor_results",
11 "resultsDBName": "performancebenchresults",
12 "custom": {
13 "uri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
14 "apiCollectionName": "APIDetails",
15 "metricsCollectionName": "APIMetrics",
16 "precomputeCollectionName": "APIPreCalc",
17 "dbname": "APIMonitor",
18 "regions": ["UK", "TK", "HK", "IN" ],
19 "baseDate": "2022-11-01T00:00:00.000Z",
20 "clusterTier": "M40",
21 "rebuildData": false,
22 "apiCount": 1000
23 }
24 },
25 {
26 "namespace": "com.mongodb.devrel.pods.performancebench.models.apimonitor_multiquery",
27 "className": "APIMonitorMultiQueryTest",
28 "measures": ["USEINQUERY"],
29 "threads": 2,
30 "iterations": 500,
31 "resultsuri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
32 "resultsCollectionName": "apimonitor_results",
33 "resultsDBName": "performancebenchresults",
34 "custom": {
35 "uri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
36 "apiCollectionName": "APIDetails",
37 "metricsCollectionName": "APIMetrics",
38 "precomputeCollectionName": "APIPreCalc",
39 "dbname": "APIMonitor",
40 "regions": ["UK", "TK", "HK", "IN" ],
41 "baseDate": "2022-11-01T00:00:00.000Z",
42 "clusterTier": "M40",
43 "rebuildData": false,
44 "apiCount": 1000
45 }
46 },
47 {
48 "namespace": "com.mongodb.devrel.pods.performancebench.models.apimonitor_regionquery",
49 "className": "APIMonitorRegionQueryTest",
50 "measures": ["QUERYSYNC","QUERYASYNC"],
51 "threads": 2,
52 "iterations": 500,
53 "resultsuri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
54 "resultsCollectionName": "apimonitor_results",
55 "resultsDBName": "performancebenchresults",
56 "custom": {
57 "uri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
58 "apiCollectionName": "APIDetails",
59 "metricsCollectionName": "APIMetrics",
60 "precomputeCollectionName": "APIPreCalc",
61 "dbname": "APIMonitor",
62 "regions": ["UK", "TK", "HK", "IN" ],
63 "baseDate": "2022-11-01T00:00:00.000Z",
64 "clusterTier": "M40",
65 "rebuildData": false,
66 "apiCount": 1000
67 }
68 },
69 {
70 "namespace": "com.mongodb.devrel.pods.performancebench.models.apimonitor_precompute",
71 "className": "APIMonitorPrecomputeTest",
72 "measures": ["PRECOMPUTE"],
73 "threads": 2,
74 "iterations": 500,
75 "resultsuri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
76 "resultsCollectionName": "apimonitor_results",
77 "resultsDBName": "performancebenchresults",
78 "custom": {
79 "uri": "mongodb+srv://myuser:mypass@my_atlas_instance.mongodb.net/?retryWrites=true&w=majority",
80 "apiCollectionName": "APIDetails",
81 "metricsCollectionName": "APIMetrics",
82 "precomputeCollectionName": "APIPreCalc",
83 "dbname": "APIMonitor",
84 "regions": ["UK", "TK", "HK", "IN" ],
85 "baseDate": "2022-11-01T00:00:00.000Z",
86 "clusterTier": "M40",
87 "rebuildData": false,
88 "apiCount": 1000
89 }
90 }
91 ]
92}
O documento contém um único campo de nível superior chamado “models,”, cujo valor é uma matriz de subdocumentos, cada um dos quais descreve um modelo e suas medidas correspondentes a serem executadas. O PerformanceBench tenta executar os modelos e medidas na ordem em que aparecem no arquivo.
Para cada modelo, o arquivo de configuração define a Java class que implementa o modelo e suas medidas, o número de threads simultâneos que devem estar executando cada medida, o número de iterações de cada medida que cada thread deve executar, uma array listando os nomes das medidas a ser executado e o URI de conexão, nome do banco de dados e nome da collection onde o PerformanceBench deve escrever documentos de resultados.
Além disso, há um subdocumento "custom" para cada modelo onde os implementadores de classe de modelo podem adicionar quaisquer parâmetros específicos para suas implementações de modelo. No caso das implementações da classeAPIMonitor, isso inclui o URI de conexão, o nome do banco de dados e os nomes da coleção em que os dados de teste residem, uma matriz de acrônimos para as regiões geográficas, a data base a partir da qual os dados de monitoramento devem ser resumidos (os resumos são baseados em valores de baseDate até a data atual, inclusive) e a camada de cluster do Atlas na qual os testes foram executados (isso é incluído nos documentos de resultados para permitir a comparação de desempenho de diferentes níveis). Os parâmetros personalizados também incluem um sinalizador que indica se o conjunto de dados de teste deve ser recriado antes que qualquer uma das medidas de um modelo seja executada e, em caso afirmativo, para quantas APIs os dados devem ser criados. O código de recompilação de dados incluído nas implementações do modelo de exemplo cria dados para o número determinado de APIs com os dados de cada API a partir de uma data aleatória nos últimos 90 dias.

Resumindo os resultados dos testes do APIMonitor

Como o PerformanceBench salva os resultados de cada teste em uma collection do MongoDB, podemos realizar a análise dos resultados de diversas maneiras. A framework de aggregation do MongoDB inclui mais 20 estágios diferentes disponíveis e mais 150 expressões disponíveis, permitindo uma flexibilidade enorme na realização de análises e, se você estiver usando o MongoDB Atlas, terá acesso a Atlas Charts, permitindo que você exiba visualmente e de forma rápida e fácil e analisar os dados em uma variedade de formatos de gráficos.
Para analisar conjuntos de dados maiores, o MongoDB Python ou connector Apache Spark pode ser considerado.
A saída de uma execução de teste simulada gerou os seguintes resultados:

Teste de configuração

teste de configuração
Observe que o servidor AWS EC2 usado para executar o PerformanceBench estava localizado na mesma zona de disponibilidade da AWS do cluster MongoDB Atlas para minimizar as variações nas medições devido à latência variável da rede.
As condições acima resultaram em um total de 20,000 documentos de resultados sendo escritos pelo PerformanceBench para o MongoDB (cinco medidas, executadas 500 vezes para cada uma de quatro regiões geográficas, por dois threads). Atlas Charts foi utilizado para exibir os resultados:
Representação em Atlas Charts dos Resultados da Medição
Em seguida, um pipeline de agregação adicional foi executado nos resultados para encontrar, para cada medida, executado por cada modelo:
  • O menor tempo de execução da iteração
  • O maior tempo de execução da iteração
  • O tempo médio de execução da iteração
  • O tempo de execução do percentil 95
  • O número de iterações concluídas por segundo.
O pipeline usado foi:
1[
2 {
3 $group: {
4 _id: {
5 model: "$model",
6 measure: "$measure",
7 region: "$region",
8 baseDate: "$baseDate",
9 threads: "$threads",
10 iterations: "$iterations",
11 clusterTier: "$clusterTier",
12 },
13 max: {
14 $max: "$duration",
15 },
16 min: {
17 $min: "$duration",
18 },
19 mean: {
20 $avg: "$duration",
21 },
22 stddev: {
23 $stdDevPop: "$duration",
24 }
25 },
26 },
27 {
28 $project: {
29 model: "$_id.model",
30 measure: "$_id.measure",
31 region: "$_id.region",
32 baseDate: "$_id.baseDate",
33 threads: "$_id.threads",
34 iterations: "$_id.iterations",
35 clusterTier: "$_id.clusterTier",
36 max: 1,
37 min: 1,
38 mean: {
39 $round: ["$mean"],
40 },
41 "95th_Centile": {
42 $round: [
43 {
44 $sum: [
45 "$mean",
46 {
47 $multiply: ["$stddev", 2],
48 },
49 ],
50 },
51 ],
52 },
53 throuput: {
54 $round: [
55 {
56 $divide: [
57 "$count",
58 {
59 $divide: [
60 {
61 $subtract: ["$end", "$start"],
62 },
63 1000,
64 ],
65 },
66 ],
67 },
68 2,
69 ],
70 },
71 _id: 0,
72 },
73 },
74]
Isso produziu os seguintes resultados:
Tabela de resultados resumidos
Como pode ser visto, os pipelines que usam o estágio$lookup e as pesquisas de igualdade nos valores deregião em APIMetrics tiveram um desempenho significativamente mais lento do que as outras abordagens. No caso do pipeline baseado em$lookup, isso provavelmente ocorreu devido à sobrecarga de organizar uma chamada para o subpipeline dentro da pesquisa para cada API (1,000 chamadas totais para o subpipeline para cada iteração), em vez de uma chamada por região geográfica (4 chamadas no total para cada iteração) nas outras abordagens. Com dois threads cada executando 500 iterações de cada medida, isso significaria ordenar 1,000,000 chamadas para o subpipeline com a abordagem$lookup em vez de 4,000 chamadas para as outras medidas.
Se a verificação dos resultados indicasse que eles eram precisos, isso seria um bom indicador de que uma abordagem que evitasse o uso de um estágio de agregação$lookup forneceria melhor desempenho de query para esse caso de uso específico. No caso dos pipelines com a cláusula de igualdade no campo de região (QUERYSYNC e QUERYASYNC), seu desempenho provavelmente foi impactado pela necessidade de classificar um grande número de documentos por APIID no estágio$group de seu pipeline. Por outro lado, o pipeline que usa a cláusula$in(USEINQUERY) utiliza um índice no campoAPID, o que significa que os documentos são devolvidos ao pipeline já classificados pelo appID — isso provavelmente lhe rendeu uma vantagem suficiente durante o estágio$group do pipeline para para concluir o estágio consistentemente mais rápido. Investigações adicionais e refinamento dos índices usados pelas medidasQUERYSYNC e QUERYASYNC podem reduzir seu comprometimento de desempenho.
Também é perceptível que o modelo de pré-computação foi entre 25 e 40 vezes mais rápido do que as outras abordagens. Ao usar os valores pré-computados para cada API, o número de documentos que o pipeline necessário para agregar foi reduzido de 96,000 para, no máximo, 1,000 para cada dia inteiro sendo medido , e de até 2,976,000 e, no máximo, 1,000 para cada mês completo sendo medido. Isso tem um impacto significativo na taxa de transferência e é a base do valor do padrão de design do esquema computado.

Considerações finais

O PerformanceBench fornece uma maneira rápida de organizar, criar, executar e registrar os resultados dos testes para medir o desempenho de diferentes designs de esquema ao executar diferentes cargas de trabalho. No entanto, é importante lembrar que a precisão dos resultados dependerá de quão bem as classes de modelo implementadas simulam os padrões de acesso reais e as cargas de trabalho que se pretende modelar.
Garantir que os modelos representem com precisão as cargas de trabalho e os esquemas que estão sendo medidos é o trabalho dos desenvolvedores implementadores, e o PerformanceBench só pode fornecer a estrutura para executar esses modelos. Ele não pode melhorar ou fornecer qualquer garantia de que os resultados registrados sejam uma predição precisa do desempenho no mundo real de um aplicativo.
Finalmente, é importante entender que o PerformanceBench, embora gratuito para download e uso, não é de forma alguma endossado ou suportado pelo MongoDB.
O repositório do PerformanceBench pode ser encontrado no Github. O projeto foi criado no IntelliJ IDEA usando Gradle.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Artigo

TableCheck: restaurantes habilitados com as melhores ferramentas de reserva da classe, desenvolvido pela MongoDB


Aug 28, 2024 | 4 min read
Início rápido

Armazene dados confidenciais com a criptografia em nível de campo do lado do cliente do Python & MongoDB


Sep 23, 2022 | 11 min read
Artigo

Validação de documentos para coleções polimórficas


Oct 01, 2024 | 6 min read
Artigo

Usando MongoDB com Rust Web Development Framework


Aug 29, 2024 | 1 min read
Sumário