2017-06-21 3 views
5

Ich schrieb mein neustes Update und bekam dann den folgenden Fehler von Stack Overflow: "Körper ist auf 30000 Zeichen beschränkt; Sie haben 38676 eingegeben."CosmosDB Query Performance

Es ist fair zu sagen, dass ich bei der Dokumentation meiner Abenteuer sehr ausführlich war, also habe ich neu geschrieben, was ich hier habe, um präziser zu sein.

Ich habe meine (lange) Original-Post und Updates auf pastebin gespeichert. Ich glaube nicht, dass viele Leute sie lesen werden, aber ich gebe ihnen viel Mühe, also wäre es schön, sie nicht verloren zu haben.


Ich habe eine Sammlung, die für das Lernen 100.000 Dokumente enthält, wie CosmosDB zu verwenden und für Dinge wie Leistungstests.

Jedes dieser Dokumente hat eine Location Eigenschaft, die ein GeoJSON Point ist.

Gemäß der documentation sollte ein GeoJSON-Punkt automatisch indiziert werden.

Azure Cosmos DB unterstützt die automatische Indizierung von Punkten, Polygonen und Linestrings

ich den Indexrichtlinien für meine Sammlung überprüft haben, und es hat den Eintrag für die automatische Indizierung Punkt:

{ 
    "automatic":true, 
    "indexingMode":"Consistent", 
    "includedPaths":[ 
     { 
     "path":"/*", 
     "indexes":[ 
      ... 
      { 
       "kind":"Spatial", 
       "dataType":"Point" 
      }, 
      ...     
     ] 
     } 
    ], 
    "excludedPaths":[ ] 
} 

Ich habe nach einem Weg gesucht, um die Indizes, die erstellt wurden, aufzulisten oder anderweitig zu befragen, aber ich habe so etwas noch nicht gefunden, also konnte ich nicht bestätigen, dass diese Eigenschaft definitiv ist indexiert.

Ich habe ein GeoJSON Polygon erstellt und dann verwendet, um meine Dokumente abzufragen.

Dies ist meine Frage:

var query = client 
    .CreateDocumentQuery<TestDocument>(documentCollectionUri) 
    .Where(document => document.Type == this.documentType && document.Location.Intersects(target.Area)); 

Und ich dann auf die folgende Methode, die Abfrage-Objekt übergeben, damit ich die Ergebnisse erhalten können, während die Anfrage Einheiten Tracking verwendet:

protected async Task<IEnumerable<T>> QueryTrackingUsedRUsAsync(IQueryable<T> query) 
{ 
    var documentQuery = query.AsDocumentQuery(); 
    var documents = new List<T>(); 

    while (documentQuery.HasMoreResults) 
    { 
     var response = await documentQuery.ExecuteNextAsync<T>(); 

     this.AddUsedRUs(response.RequestCharge); 

     documents.AddRange(response); 
    } 

    return documents; 
} 

Die Punktstellen werden zufällig aus 10 Millionen von britischen Adressen ausgewählt, so dass sie eine ziemlich realistische Verbreitung haben sollten.

Das Polygon besteht aus 16 Punkten (wobei der erste und der letzte Punkt identisch sind), also ist es nicht sehr komplex. Es umfasst die meisten der südlichsten Teil des Vereinigten Königreichs, von London nach unten.

Ein Beispiel dieser Abfrage ergab 8728 Dokumente mit 3917,92 RU, in 170717.151 ms, die knapp 171 Sekunden oder knapp 3 Minuten ist.

3918 RU/171 s = 22,91 RU/s

Ich habe derzeit den Durchsatz (RU/s) auf den niedrigsten Wert, bei 400 RU/s.

Es war mein Verständnis, dass dies die reservierte Ebene ist, die Sie garantiert bekommen. Du kannst manchmal über diesen Level "platzen", aber tu das zu oft und du wirst zurück auf deinen reservierten Level gedrosselt.

Die "Abfragegeschwindigkeit" von 23 RU/s ist offensichtlich viel viel niedriger als die Durchsatzeinstellung von 400 RU/s.

Ich verwende den Client lokal, d. H. In meinem Büro und nicht im Azure-Rechenzentrum.

Jedes Dokument ist ungefähr 500 Bytes (0,5 kb) groß.

Was passiert also?

Mache ich etwas falsch?

Bin ich falsch verstanden, wie meine Abfrage in Bezug auf RU/s gedrosselt wird?

Ist dies die Geschwindigkeit, mit der die GeoSpatial-Indizes arbeiten, und so die beste Leistung, die ich bekommen werde?

Wird der GeoSpatial-Index nicht verwendet?

Gibt es eine Möglichkeit, die erstellten Indizes anzuzeigen?

Gibt es eine Möglichkeit, dass ich überprüfen kann, ob der Index verwendet wird?

Gibt es eine Möglichkeit, die Abfrage zu profilieren und Metriken zu erhalten, wo Zeit verbracht wird? z.B. s wurde verwendet, um Dokumente nach ihrem Typ zu suchen, s wurde verwendet, um sie GeoSpatial zu filtern, und s wurde verwendet, um die Daten zu übertragen.

UPDATE 1

Hier ist das Polygon ich in der Abfrage verwenden bin:

Area = new Polygon(new List<LinearRing>() 
{ 
    new LinearRing(new List<Position>() 
    { 
     new Position(1.8567 ,51.3814), 

     new Position(0.5329 ,51.4618), 
     new Position(0.2477 ,51.2588), 
     new Position(-0.5329 ,51.2579), 
     new Position(-1.17 ,51.2173), 
     new Position(-1.9062 ,51.1958), 
     new Position(-2.5434 ,51.1614), 
     new Position(-3.8672 ,51.139), 
     new Position(-4.1578 ,50.9137), 
     new Position(-4.5373 ,50.694), 
     new Position(-5.1496 ,50.3282), 
     new Position(-5.2212 ,49.9586), 
     new Position(-3.7049 ,50.142), 
     new Position(-2.1698 ,50.314), 
     new Position(0.4669 ,50.6976), 

     new Position(1.8567 ,51.3814) 
    }) 
}) 

Ich habe auch versucht, es umzukehren (da Ring Orientierung Angelegenheiten), aber die Abfrage mit dem umgekehrten Polygon nahm wesentlich länger (ich habe nicht die Zeit zur Hand) und gab 91272 Artikel zurück.

Auch die Koordinaten werden als Längengrad/Breitengrad, wie this is how GeoJSON expects them (d. H. Als X/Y) angegeben, anstatt die traditionelle Reihenfolge, die verwendet wird, wenn wir von Breiten-/Längengrad sprechen.

Die GeoJSON-Spezifikation spezifiziert Längengrad zuerst und Breitengrad Sekunde.

UPDATE 2

Hier ist die JSON für einen meiner Dokumente:

{ 
    "GeoTrigger": null, 
    "SeverityTrigger": -1, 
    "TypeTrigger": -1, 
    "Name": "13, LONSDALE SQUARE, LONDON, N1 1EN", 
    "IsEnabled": true, 
    "Type": 2, 
    "Location": { 
     "$type": "Microsoft.Azure.Documents.Spatial.Point, Microsoft.Azure.Documents.Client", 
     "type": "Point", 
     "coordinates": [ 
      -0.1076407397346815, 
      51.53970315059827 
     ] 
    }, 
    "id": "0dc2c03e-082b-4aea-93a8-79d89546c12b", 
    "_rid": "EQttAMGhSQDWPwAAAAAAAA==", 
    "_self": "dbs/EQttAA==/colls/EQttAMGhSQA=/docs/EQttAMGhSQDWPwAAAAAAAA==/", 
    "_etag": "\"42001028-0000-0000-0000-594943fe0000\"", 
    "_attachments": "attachments/", 
    "_ts": 1497973747 
} 

UPDATE 3

ich eine minimale Wiedergabe der Ausgabe erstellt, und ich fand Das Problem ist nicht mehr aufgetreten.

Dies zeigte, dass das Problem tatsächlich in meinem eigenen Code war.

Ich machte mich daran, alle Unterschiede zwischen dem Original und dem Reproduktionscode zu überprüfen und fand schließlich heraus, dass etwas, das mir ziemlich unschuldig erschien, tatsächlich eine große Wirkung hatte.Und glücklicherweise wurde dieser Code überhaupt nicht benötigt, also war es eine einfache Lösung, diesen Code einfach nicht zu verwenden.

An einem Punkt verwendete ich eine benutzerdefinierte ContractResolver und ich hatte es nicht entfernt, sobald es nicht mehr benötigt wurde.

Hier ist der säumige Wiedergabe Code:

using System; 
using System.Collections.Generic; 
using System.Configuration; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Threading; 
using System.Threading.Tasks; 
using Microsoft.Azure.Documents; 
using Microsoft.Azure.Documents.Client; 
using Microsoft.Azure.Documents.Spatial; 
using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 

namespace Repro.Cli 
{ 
    public class Program 
    { 
     static void Main(string[] args) 
     { 
      JsonConvert.DefaultSettings =() => 
      { 
       return new JsonSerializerSettings 
       { 
        ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>() 
        { 
         { "ID", "id" } 
        }) 
       }; 
      }; 

      //AJ: Init logging 
      Trace.AutoFlush = true; 
      Trace.Listeners.Add(new ConsoleTraceListener()); 
      Trace.Listeners.Add(new TextWriterTraceListener("trace.log")); 

      //AJ: Increase availible threads 
      //AJ: https://docs.microsoft.com/en-us/azure/storage/storage-performance-checklist#subheading10 
      //AJ: https://github.com/Azure/azure-documentdb-dotnet/blob/master/samples/documentdb-benchmark/Program.cs 
      var minThreadPoolSize = 100; 
      ThreadPool.SetMinThreads(minThreadPoolSize, minThreadPoolSize); 

      //AJ: https://docs.microsoft.com/en-us/azure/cosmos-db/performance-tips 
      //AJ: gcServer enabled in app.config 
      //AJ: Prefer 32-bit disabled in project properties 

      //AJ: DO IT 
      var program = new Program(); 

      Trace.TraceInformation($"Starting @ {DateTime.UtcNow}"); 
      program.RunAsync().Wait(); 
      Trace.TraceInformation($"Finished @ {DateTime.UtcNow}"); 

      //AJ: Wait for user to exit 
      Console.WriteLine(); 
      Console.WriteLine("Hit enter to exit..."); 
      Console.ReadLine(); 
     } 

     public async Task RunAsync() 
     { 
      using (new CodeTimer()) 
      { 
       var client = await this.GetDocumentClientAsync(); 
       var documentCollectionUri = UriFactory.CreateDocumentCollectionUri(ConfigurationManager.AppSettings["databaseID"], ConfigurationManager.AppSettings["collectionID"]); 

       //AJ: Prepare Test Documents 
       var documentCount = 10000; //AJ: 10,000 
       var documentsForUpsert = this.GetDocuments(documentCount); 
       await this.UpsertDocumentsAsync(client, documentCollectionUri, documentsForUpsert); 

       var allDocuments = this.GetAllDocuments(client, documentCollectionUri); 

       var area = this.GetArea(); 
       var documentsInArea = this.GetDocumentsInArea(client, documentCollectionUri, area); 
      } 
     } 

     private async Task<DocumentClient> GetDocumentClientAsync() 
     { 
      using (new CodeTimer()) 
      { 
       var serviceEndpointUri = new Uri(ConfigurationManager.AppSettings["serviceEndpoint"]); 
       var authKey = ConfigurationManager.AppSettings["authKey"]; 

       var connectionPolicy = new ConnectionPolicy 
       { 
        ConnectionMode = ConnectionMode.Direct, 
        ConnectionProtocol = Protocol.Tcp, 
        RequestTimeout = new TimeSpan(1, 0, 0), 
        RetryOptions = new RetryOptions 
        { 
         MaxRetryAttemptsOnThrottledRequests = 10, 
         MaxRetryWaitTimeInSeconds = 60 
        } 
       }; 

       var client = new DocumentClient(serviceEndpointUri, authKey, connectionPolicy); 

       await client.OpenAsync(); 

       return client; 
      } 
     } 

     private List<TestDocument> GetDocuments(int count) 
     { 
      using (new CodeTimer()) 
      { 
       return External.CreateDocuments(count); 
      } 
     } 

     private async Task UpsertDocumentsAsync(DocumentClient client, Uri documentCollectionUri, List<TestDocument> documents) 
     { 
      using (new CodeTimer()) 
      { 
       //TODO: AJ: Parallelise 
       foreach (var document in documents) 
       { 
        await client.UpsertDocumentAsync(documentCollectionUri, document); 
       } 
      } 
     } 

     private List<TestDocument> GetAllDocuments(DocumentClient client, Uri documentCollectionUri) 
     { 
      using (new CodeTimer()) 
      { 
       var query = client 
        .CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions() 
        { 
         MaxItemCount = 1000 
        }); 

       var documents = query.ToList(); 

       return documents; 
      } 
     } 

     private Polygon GetArea() 
     { 
      //AJ: Longitude,Latitude i.e. X/Y 
      //AJ: Ring orientation matters 
      return new Polygon(new List<LinearRing>() 
      { 
       new LinearRing(new List<Position>() 
       { 
        new Position(1.8567 ,51.3814), 

        new Position(0.5329 ,51.4618), 
        new Position(0.2477 ,51.2588), 
        new Position(-0.5329 ,51.2579), 
        new Position(-1.17 ,51.2173), 
        new Position(-1.9062 ,51.1958), 
        new Position(-2.5434 ,51.1614), 
        new Position(-3.8672 ,51.139), 
        new Position(-4.1578 ,50.9137), 
        new Position(-4.5373 ,50.694), 
        new Position(-5.1496 ,50.3282), 
        new Position(-5.2212 ,49.9586), 
        new Position(-3.7049 ,50.142), 
        new Position(-2.1698 ,50.314), 
        new Position(0.4669 ,50.6976), 

        //AJ: Last point must be the same as first point 
        new Position(1.8567 ,51.3814) 
       }) 
      }); 
     } 

     private List<TestDocument> GetDocumentsInArea(DocumentClient client, Uri documentCollectionUri, Polygon area) 
     { 
      using (new CodeTimer()) 
      { 
       var query = client 
        .CreateDocumentQuery<TestDocument>(documentCollectionUri, new FeedOptions() 
        { 
         MaxItemCount = 1000 
        }) 
        .Where(document => document.Location.Intersects(area)); 

       var documents = query.ToList(); 

       return documents; 
      } 
     } 
    } 

    public class TestDocument : Resource 
    { 
     public string Name { get; set; } 
     public Point Location { get; set; } //AJ: Longitude,Latitude i.e. X/Y 

     public TestDocument() 
     { 
      this.Id = Guid.NewGuid().ToString("N"); 
     } 
    } 

    //AJ: This should be "good enough". The times being recorded are seconds or minutes. 
    public class CodeTimer : IDisposable 
    { 
     private Action<TimeSpan> reportFunction; 
     private Stopwatch stopwatch = new Stopwatch(); 

     public CodeTimer([CallerMemberName]string name = "") 
      : this((ellapsed) => 
      { 
       Trace.TraceInformation($"{name} took {ellapsed}, or {ellapsed.TotalMilliseconds} ms."); 
      }) 
     { } 

     public CodeTimer(Action<TimeSpan> report) 
     { 
      this.reportFunction = report; 
      this.stopwatch.Start(); 
     } 

     public void Dispose() 
     { 
      this.stopwatch.Stop(); 
      this.reportFunction(this.stopwatch.Elapsed); 
     } 
    } 

    public class PropertyNameMapContractResolver : DefaultContractResolver 
    { 
     private Dictionary<string, string> propertyNameMap; 

     public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap) 
     { 
      this.propertyNameMap = propertyNameMap; 
     } 

     protected override string ResolvePropertyName(string propertyName) 
     { 
      if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName)) 
       return resolvedName; 

      return base.ResolvePropertyName(propertyName); 
     } 
    } 
} 
+0

Können Sie Ihre Frage bearbeiten, um das Polygon anzuzeigen, das Sie verwenden? –

+0

Ja, ich habe es in Codeform eingefügt und Informationen zur Ringausrichtung hinzugefügt. – AndyJ

+0

Könnten Sie bitte ein Musterdokument Ihrer Sammlung zur Verfügung stellen? – Amor

Antwort

1

ich eine benutzerdefinierte ContractResolver verwendet und das war offenbar einen großen Einfluss auf die Leistung der DocumentDB Klassen aus dem .NET SDK ist.

Das war, wie ich die ContractResolver einstellen:

JsonConvert.DefaultSettings =() => 
{ 
    return new JsonSerializerSettings 
    { 
     ContractResolver = new PropertyNameMapContractResolver(new Dictionary<string, string>() 
     { 
      { "ID", "id" } 
     }) 
    }; 
}; 

Und das ist, wie es durchgeführt wurde:

public class PropertyNameMapContractResolver : DefaultContractResolver 
{ 
    private Dictionary<string, string> propertyNameMap; 

    public PropertyNameMapContractResolver(Dictionary<string, string> propertyNameMap) 
    { 
     this.propertyNameMap = propertyNameMap; 
    } 

    protected override string ResolvePropertyName(string propertyName) 
    { 
     if (this.propertyNameMap.TryGetValue(propertyName, out string resolvedName)) 
      return resolvedName; 

     return base.ResolvePropertyName(propertyName); 
    } 
} 

Die Lösung war einfach, nicht setzen JsonConvert.DefaultSettings so die ContractResolver isn‘ t benutzt.

Ergebnisse:

konnte ich meine räumliche Abfrage in 21799,0221 ms durchführen, die 22 Sekunden ist.

Bisher dauerte es 170717.151 ms, was 2 Minuten 50 Sekunden ist.

Das ist etwa 8x schneller!