2017-06-21 8 views
5

Napisałem swoją najnowszą aktualizację, a następnie dostałem następujący komunikat o błędzie z Przepełnienia stosu: "Treść jest ograniczona do 30000 znaków, wpisano 38676."Wydajność zapytania CosmosDB

Można śmiało powiedzieć, że bardzo szczegółowo opisałem swoje przygody, więc przepisałem to, co mam tutaj, aby było bardziej zwięzłe.

Mam przechowywany mój (długi) oryginalny post i aktualizacje na pastebin. Nie sądzę, żeby wielu ludzi je przeczytało, ale wkładałem w to dużo wysiłku, więc byłoby miło, gdyby ich nie stracili.


Mam kolekcję, która zawiera 100 000 dokumentów do nauki korzystania z CosmosDB i do testowania wydajności.

Każdy z tych dokumentów ma właściwość Location, która jest GeoJSON Point.

Zgodnie z documentation punkt GeoJSON powinien być automatycznie indeksowany.

Azure Cosmos DB obsługuje automatyczne indeksowanie punktów, wielokątów i dotycząca linii

Sprawdziłem Politykę indeksowania dla mojej kolekcji i posiada wpis do automatycznego punktu indeksowania:

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

Szukałem sposobu na listę lub w inny sposób przesłuchałem indeksy, które zostały utworzone, ale jeszcze nie znalazłem czegoś takiego, więc nie byłem w stanie potwierdzić, że ta nieruchomość zdecydowanie jest zindeksowane.

Utworzono GeoJSON Polygon, a następnie użyłem go do wysłania zapytania do moich dokumentów.

To moje zapytanie:

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

I następnie przekazać ten obiekt zapytanie do następującej metody więc mogę uzyskać wyniki podczas śledzenia jednostek kupna Używane:

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; 
} 

miejscach punktowych są losowo wybierane spośród 10 milionów adresów w Wielkiej Brytanii, więc powinny mieć dość realistyczny rozkład.

Wielokąt składa się z 16 punktów (przy czym pierwszy i ostatni punkt są takie same), więc nie jest zbyt skomplikowany. Obejmuje większość najbardziej wysuniętej na południe części Wielkiej Brytanii, od Londynu w dół.

Przykładowy przebieg tego zapytania zwrócił 8728 dokumentów, używając 3917.92 RU, w 170717.151 ms, czyli niewiele poniżej 171 sekund, lub nieco poniżej 3 minut.

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

Obecnie ma przepustowość (RU/S) ustawiona na najniższą wartość przy 400 RU/s.

To było moje zrozumienie, że jest to poziom zarezerwowany, który masz zagwarantowany. Możesz czasami "wybuchnąć" powyżej tego poziomu, ale rób to zbyt często, a zostaniesz spowolniony do poziomu zarezerwowanego.

"Prędkość zapytania" 23 RU/s jest oczywiście znacznie niższa niż prędkość przepustowości 400 RU/s.

Uruchamiam klienta "lokalnie", tj. W moim biurze, a nie w centrum danych Azure.

Każdy dokument ma grubość około 500 bajtów (0,5 kb).

Co się dzieje?

Czy robię coś nie tak?

Czy nie rozumiem, jak moje zapytanie jest ograniczane w odniesieniu do RU/s?

Czy to jest szybkość, z jaką działają indeksy GeoSpatial, a więc najlepsza wydajność, jaką otrzymam?

Czy nie stosuje się indeksu GeoSpatial?

Czy istnieje sposób, w jaki mogę wyświetlić utworzone indeksy?

Czy istnieje sposób sprawdzenia, czy indeks jest używany?

Czy istnieje sposób, w jaki mogę profilować zapytanie i uzyskiwać dane o tym, gdzie spędza się czas? na przykład s zostało użyte do wyszukiwania dokumentów według ich typu, s zostało użyte do filtrowania ich GeoSpatialnie, a s zostało użyte do przeniesienia danych.

UPDATE 1

Oto wielokąt używam w zapytaniu:

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) 
    }) 
}) 

Próbowałem również odwrócenie go (od kwestiach orientacji pierścień), ale kwerenda z odwróconym wielokąta trwało znacznie dłużej (nie mam czasu) i wróciło 91272 przedmiotów.

Współrzędne są również określane jako długość/szerokość geograficzna, jako this is how GeoJSON expects them (tj. Jako X/Y), zamiast tradycyjnej kolejności używanej podczas mówienia o szerokości/długości geograficznej.

Specyfikacja GeoJSON określa długość geograficzną pierwszą i szerokość geograficzną na sekundę.

UPDATE 2

Oto JSON dla jednego z moich dokumentów:

{ 
    "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

utworzonego minimalny odtworzenie problemu i znalazłem problem już nie występował.

Wskazuje to, że problem był rzeczywiście w moim własnym kodzie.

Postanowiłem sprawdzić wszystkie różnice między oryginałem a kodem reprodukcyjnym i ostatecznie odkryłem, że coś, co wydawało mi się całkiem niewinne, miało duży wpływ.I na szczęście ten kod nie był w ogóle potrzebny, więc łatwo było po prostu nie używać tego fragmentu kodu.

W pewnym momencie użyłem niestandardowego ContractResolver i nie usunąłem go, gdy nie było już potrzebne.

Oto kod reprodukcja wykraczająca:

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

Czy możesz edytować pytanie, aby wyświetlić wielokąt, którego używasz? –

+0

Tak, umieściłem to w formie kodu i dodałem informację o orientacji pierścienia. – AndyJ

+0

Czy możesz podać przykładowy dokument swojej kolekcji? – Amor

Odpowiedz

1

używałem zwyczaj ContractResolver i że najwyraźniej ma duży wpływ na wydajność klas DocumentDB z .NET SDK.

To było jak miałem ustawienie ContractResolver:

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

I tak to był projekt:

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); 
    } 
} 

Rozwiązanie było proste, nie ustawiaj JsonConvert.DefaultSettings więc ISN ContractResolver” t użyte.

Wyniki:

udało mi się wykonać moje zapytanie przestrzenne w 21799.0221 ms, który wynosi 22 sekund. Wcześniej zajęło 170717.151 ms, czyli 2 minuty i 50 sekund.

To około 8 razy szybciej!

Powiązane problemy