Condividi tramite


Risolvere i problemi di query relativi all'uso di Azure Cosmos DB per MongoDB

SI APPLICA A: MongoDB

Questo articolo illustra un approccio generale consigliato per la risoluzione dei problemi relativi alle query in Azure Cosmos DB. I passaggi descritti in questo articolo non rappresentano una soluzione completa ai potenziali problemi di query, ma offrono suggerimenti per la soluzione degli errori più comuni relativi alle prestazioni. È consigliabile usare questo articolo come punto di partenza per la risoluzione dei problemi relativi a query lente o con costo elevato nell'API Azure Cosmos DB for MongoDB. Se si usa Azure Cosmos DB for NoSQL, vedere la guida alla risoluzione dei problemi delle query dell'API per NoSQL.

L'ottimizzazione delle query in Azure Cosmos DB può essere classificata in queste categorie principali:

  • Ottimizzazioni che consentono di ridurre l'addebito dell'unità richiesta (UR) della query
  • Ottimizzazioni che consentono di ridurre solo la latenza

Se si riduce l'addebito UR di una query, generalmente è possibile ridurre anche la latenza.

Questo articolo fornisce esempi che è possibile riprodurre usando il set di dati nutrition.

Nota

Questo articolo presuppone che si usino account dell'API Azure Cosmos DB for MongoDB versione 3.6 e successive. Alcune query con scarse prestazioni nella versione 3.2 presentano miglioramenti significativi nelle versioni 3.6 e successive. Eseguire l'aggiornamento alla versione 3.6 inviando una richiesta di supporto.

Usare il comando $explain per ottenere le metriche

Quando si ottimizza una query in Azure Cosmos DB, il primo passaggio consiste sempre nell'ottenere l'addebito delle UR per la query. Come linea guida approssimativa, è consigliabile esplorare i modi per ridurre l'addebito delle UR per le query con addebiti superiori a 50 UR.

Oltre a ottenere l'addebito delle UR, è necessario usare il comando $explain per ottenere le metriche di utilizzo delle query e dell'indice. Di seguito è riportato un esempio che esegue una query e usa il comando $explain per visualizzare le metriche di utilizzo della query e degli indici:

Comando $explain:

db.coll.find({foodGroup: "Baby Foods"}).explain({"executionStatistics": true })

Output:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 905.2888,
            "timeExclusiveMS" : 905.2888,
            "in" : 362,
            "out" : 362,
            "details" : {
                "database" : "db-test",
                "collection" : "collection-test",
                "query" : {
                    "foodGroup" : {
                        "$eq" : "Baby Foods"
                    }
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "e68e6bdd-5e89-4ec5-b053-3dbbc2428140",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 788.5867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 362,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 362,
                    "outputDocumentSizeBytes" : 2553535,
                    "indexHitRatio" : 0.0016802042237178,
                    "totalQueryExecutionTimeMS" : 777.72,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.19,
                        "logicalPlanBuildTimeMS" : 0.14,
                        "physicalPlanBuildTimeMS" : 0.09,
                        "queryOptimizationTimeMS" : 0.03
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 687.22,
                    "vmExecutionTimeMS" : 774.09,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 37.45,
                        "systemFunctionExecutionTimeMS" : 10.82,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 49.42
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

L'output del comando $explain è esteso e contiene informazioni dettagliate sull'esecuzione della query. In generale, tuttavia, esistono alcune sezioni su cui è consigliabile concentrarsi quando si ottimizzano le prestazioni delle query:

Metrico Descrizione
timeInclusiveMS Latenza di query back-end
pathsIndexed Mostra gli indici usati dalla query
pathsNotIndexed Mostra gli indici che la query avrebbe potuto usare, se disponibili
shardInformation Riepilogo delle prestazioni della query per una particolare partizione fisica
retrievedDocumentCount Numero di documenti caricati dal motore di query
outputDocumentCount Numero di documenti restituiti nei risultati della query
estimatedDelayFromRateLimitingInMilliseconds Latenza aggiuntiva stimata per la query a causa della limitazione della frequenza

Dopo aver recuperate le metriche di query, confrontare il valore di retrievedDocumentCount con quello di outputDocumentCount per la query. Usare questo confronto per identificare le sezioni rilevanti da esaminare in questo articolo. Il valore di retrievedDocumentCount corrisponde al numero di documenti che il motore di query deve caricare. Il valore di outputDocumentCount corrisponde al numero di documenti che erano necessari per i risultati della query. Se il valore di retrievedDocumentCount è notevolmente superiore a quello di outputDocumentCount, significa che almeno una parte della query non ha potuto usare un indice e ha dovuto eseguire un'analisi.

Per informazioni sull'ottimizzazione delle query relative a ogni scenario, vedere le sezioni che seguono.

L'addebito UR della query è troppo elevato

Il conteggio dei documenti recuperati è significativamente superiore al conteggio dei documenti di output

Il conteggio documenti recuperati è approssimativamente uguale al conteggio documenti di output

L'addebito UR della query è accettabile, ma la latenza è ancora troppo elevata

Query in cui il conteggio dei documenti recuperati supera il conteggio documenti di output

Il valore di retrievedDocumentCount corrisponde al numero di documenti che il motore di query doveva caricare. Il valore di outputDocumentCount corrisponde al numero di documenti restituiti dalla query. Se il valore di retrievedDocumentCount è notevolmente superiore a quello di outputDocumentCount, significa che almeno una parte della query non ha potuto usare un indice e ha dovuto eseguire un'analisi.

Di seguito è riportato un esempio di una query di analisi che non è stata interamente gestita dall'indice:

Comando $explain:

db.coll.find(
  {
    $and : [
            { "foodGroup" : "Cereal Grains and Pasta"}, 
            { "description" : "Oat bran, cooked"}
        ]
  }
).explain({"executionStatistics": true })

Output:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 436.5716,
            "timeExclusiveMS" : 436.5716,
            "in" : 1,
            "out" : 1,
            "details" : {
                "database" : "db-test",
                "collection" : "indexing-test",
                "query" : {
                    "$and" : [ 
                        {
                            "foodGroup" : {
                                "$eq" : "Cereal Grains and Pasta"
                            }
                        }, 
                        {
                            "description" : {
                                "$eq" : "Oat bran, cooked"
                            }
                        }
                    ]
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "13a5977e-a10a-4329-b68e-87e4f0081cac",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 435.4867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 1,
                    "outputDocumentSizeBytes" : 6064,
                    "indexHitRatio" : 0.0,
                    "totalQueryExecutionTimeMS" : 433.64,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.12,
                        "logicalPlanBuildTimeMS" : 0.09,
                        "physicalPlanBuildTimeMS" : 0.1,
                        "queryOptimizationTimeMS" : 0.02
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 387.44,
                    "vmExecutionTimeMS" : 432.93,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 45.36,
                        "systemFunctionExecutionTimeMS" : 16.86,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 0.13
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

Il valore di retrievedDocumentCount (8618) è notevolmente superiore a quello di outputDocumentCount (1), il che significa che è stata necessaria un'analisi dei documenti.

Includere gli indici necessari

È necessario controllare la matrice pathsNotIndexed e aggiungere questi indici. In questo esempio i percorsi foodGroup e description devono essere indicizzati.

"pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ]

Le procedure consigliate per l'indicizzazione nell'API Azure Cosmos DB for MongoDB sono diverse da quelle di MongoDB. Nell'API Azure Cosmos DB for MongoDB, gli indici composti vengono usati solo nelle query che devono essere ordinate in modo efficiente in base a più proprietà. Se si hanno query con filtri basati su più proprietà, è necessario creare indici a campo singolo per ognuna delle proprietà. I predicati di query possono usare più indici a campo singolo.

Gli indici con caratteri jolly possono semplificare l'indicizzazione. A differenza di MongoDB, gli indici con caratteri jolly possono supportare più campi nei predicati di query. Non ci sarà alcuna differenza nelle prestazioni delle query se si usa un singolo indice con caratteri jolly anziché creare un indice separato per ogni proprietà. L'aggiunta di un indice con caratteri jolly per tutte le proprietà è il modo più semplice per ottimizzare tutte le query.

È possibile aggiungere nuovi indici in qualsiasi momento, senza alcun effetto sulla disponibilità di scrittura o lettura. È possibile tenere traccia dell'avanzamento della trasformazione dell'indice.

Comprendere quali operazioni di aggregazione usano l'indice

Nella maggior parte dei casi, le operazioni di aggregazione nell'API Azure Cosmos DB for MongoDB usano gli indici solo parzialmente. In genere il motore di query applica prima i filtri di uguaglianza e di intervallo e dopo usa gli indici. Dopo aver applicato questi filtri, il motore di query può valutare filtri aggiuntivi e ricorrere al caricamento dei documenti rimanenti per calcolare l'aggregazione, se necessario.

Ecco un esempio:

db.coll.aggregate( [
   { $match: { foodGroup: 'Fruits and Fruit Juices' } },
   {
     $group: {
        _id: "$foodGroup",
        total: { $max: "$version" }
     }
   }
] )

In questo caso, gli indici possono ottimizzare la fase $match. L'aggiunta di un indice per foodGroup migliorerà in modo significativo le prestazioni della query. Come in MongoDB, è consigliabile posizionare $match il più possibile all'inizio della pipeline di aggregazione per ottimizzare l'utilizzo degli indici.

Nell'API Azure Cosmos DB for MongoDB gli indici non vengono usati per l'aggregazione effettiva, che in questo caso è $max. L'aggiunta di un indice in version non migliorerà le prestazioni della query.

Query in cui il conteggio dei documenti recuperati è uguale al conteggio dei documenti di output

Se il valore di retrievedDocumentCount è approssimativamente uguale a quello di outputDocumentCount, significa che il motore di query non ha dovuto analizzare molti documenti non necessari.

Ridurre al minimo le query su più partizioni

Azure Cosmos DB usa il partizionamento per la scalabilità dei singoli contenitori quando si riscontra un aumento dell'UR e delle risorse di archiviazione dati necessarie. Ogni partizione fisica dispone di un indice distinto e indipendente. Se la query include un filtro di uguaglianza che corrisponde alla chiave di partizione del contenitore, è necessario controllare solo l'indice della partizione pertinente. Questa ottimizzazione consente di ridurre il numero totale di UR necessarie per la query. Altre informazioni sulle differenze tra query in partizione e query tra partizioni.

Se è presente un numero elevato di UR di cui è stato effettuato il provisioning (più di 30.000) o di una quantità elevata di dati archiviati (circa oltre 100 GB), è probabile che si disponga di un contenitore sufficientemente grande da consentire una riduzione significativa degli addebiti UR.

È possibile controllare la matrice shardInformation per comprendere le metriche di query per ogni singola partizione fisica. Il numero di valori univoci di shardKeyRangeId è il numero di partizioni fisiche in cui deve essere eseguita la query. In questo esempio la query è stata eseguita su quattro partizioni fisiche. È importante comprendere che l'esecuzione è completamente indipendente dall'utilizzo degli indici. In altre parole, le query tra partizioni possono comunque usare indici.

  "shardInformation" : [ 
                    {
                        "activityId" : "42f670a8-a201-4c58-8023-363ac18d9e18",
                        "shardKeyRangeId" : "5",
                        "durationMS" : 24.3859,
                        "preemptions" : 1,
                        "outputDocumentCount" : 463,
                        "retrievedDocumentCount" : 463
                    }, 
                    {
                        "activityId" : "a8bf762a-37b9-4c07-8ed4-ae49961373c0",
                        "shardKeyRangeId" : "2",
                        "durationMS" : 35.8328,
                        "preemptions" : 1,
                        "outputDocumentCount" : 905,
                        "retrievedDocumentCount" : 905
                    }, 
                    {
                        "activityId" : "3754e36b-4258-49a6-8d4d-010555628395",
                        "shardKeyRangeId" : "1",
                        "durationMS" : 67.3969,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1479,
                        "retrievedDocumentCount" : 1479
                    }, 
                    {
                        "activityId" : "a69a44ee-db97-4fe9-b489-3791f3d52878",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 185.1523,
                        "preemptions" : 1,
                        "outputDocumentCount" : 867,
                        "retrievedDocumentCount" : 867
                    }
                ]

Ottimizzazioni che consentono di ridurre la latenza della query

In molti casi, l'addebito UR potrebbe essere accettabile quando la latenza della query è ancora troppo elevata. Le sezioni seguenti forniscono una panoramica dei suggerimenti per la riduzione della latenza delle query. Se la stessa query viene eseguita più volte nello stesso set di dati, lo stesso addebito UR viene generalmente applicato ogni volta. La latenza delle query potrebbe tuttavia variare tra le esecuzioni della query.

Migliorare la prossimità

Le query eseguite da un'area diversa da quella dell'account di Azure Cosmos DB avranno una latenza superiore rispetto a quando vengono eseguite all'interno della stessa area. Se, ad esempio, se si esegue il codice sul computer desktop, è prevedibile che la latenza sia superiore di decine o centinaia di millisecondi (o più) rispetto a una query eseguita su una macchina virtuale all'interno della stessa area di Azure come Azure Cosmos DB. La distribuzione a livello globale dei dati in Azure Cosmos DB è un'operazione semplice che consente di migliorare la prossimità dei dati all'app.

Aumentare la velocità effettiva sottoposta a provisioning

In Azure Cosmos DB la velocità effettiva con provisioning viene misurata in Unità Richieste (UR). Si supponga di disporre di una query che utilizza 5 UR di velocità effettiva. Se, ad esempio, si esegue il provisioning di 1.000 UR, sarà possibile eseguire la query 200 volte al secondo. Se si prova a eseguire la query quando la velocità effettiva disponibile non è sufficiente, Azure Cosmos DB limiterà la velocità delle richieste. L'API Azure Cosmos DB per MongoDB eseguirà automaticamente nuovi tentativi per questa query dopo un breve periodo di attesa. Le richieste limitate richiedono più tempo, quindi l'aumento della velocità effettiva con provisioning può diminuire la latenza delle query.

Il valore di estimatedDelayFromRateLimitingInMilliseconds dà un'idea dei potenziali vantaggi in termini di latenza se si aumenta la velocità effettiva.

Passaggi successivi