2017-06-21 102 views
5

我寫了我的最新更新,然後從堆棧溢出中得到以下錯誤:「正文限於30000個字符;您輸入了38676。」CosmosDB查詢性能

這很公平地說,我一直在記錄我的冒險非常詳細,所以我重寫了我在這裏更加簡潔。

我已經在pastebin上存儲了我的(長)原始帖子和更新。我不認爲很多人會閱讀他們,但我會付出很多努力,所以不會讓他們迷失。


我有一個集合,其中包含100,000個文檔,用於學習如何使用CosmosDB以及諸如性能測試等內容。

這些文檔中的每一個都有一個Location屬性,它是GeoJSON Point

根據​​3210,一個GeoJSON點應該被自動索引。

Azure的宇宙DB支持點,多邊形的自動索引,路線]

我檢查索引策略我的收藏,而且它具有自動點索引條目:

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

我一直在尋找一種方法來列出或以其他方式詢問已經創建的索引,但是我還沒有找到這樣的東西,所以我一直無法確認這個屬性肯定是存在的索引。

我創建了一個GeoJSON Polygon,然後用它來查詢我的文檔。

這是我的查詢:

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

然後我傳遞查詢對象以下述的方法,所以我可以得到的結果,同時跟蹤使用的請求單位:

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

點位置是從數百萬英國地址中隨機選取的,因此它們應該有相當實際的傳播。

多邊形由16個點組成(第一個和最後一個點是相同的),所以它不是很複雜。它覆蓋了英國最南部的大部分地區,從倫敦下來。

這個查詢的一個示例運行返回了8728個文檔,使用3917.92 RU,在170717.151毫秒內,僅171秒,也就是不到3分鐘。

3918 RU/171 S = 22.91 RU/s的

我現在有吞吐量(RU/S)設定爲最低值,在400 RU /秒。

這是我的理解,這是你保證得到的保留級別。有時你可以「突破」這個水平,但是頻繁地這樣做,你會被限制回到你的保留水平。

23 RU/s的「查詢速度」顯然遠低於吞吐量設置400 RU/s。

我在本地運行客戶端,即在我的辦公室,而不是在Azure數據中心。

每個文檔大小約爲500字節(0.5 kb)。

那麼發生了什麼?

我做錯了什麼?

我誤解我的查詢是如何限制RU/s?

這是GeoSpatial指數運行的速度,所以我會得到最好的性能?

是否未使用GeoSpatial指數?

有沒有辦法可以查看創建的索引?

有沒有一種方法可以檢查索引是否被使用?

有沒有一種方法可以剖析查詢並獲取有關何時花費的指標?例如s被用於按照類型查找文檔,s被用於對地理空間進行過濾,而s則用於傳輸數據。

更新1

下面是我使用的查詢多邊形:

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

我自己也嘗試扭轉它(因爲環方向事項),但在顛倒多邊形查詢花了很長時間(我沒有時間),並返回了91272件物品。

此外,座標被指定爲經度/緯度,如this is how GeoJSON expects them(即,作爲X/Y),而不是在講經度/緯度時使用的傳統順序。

GeoJSON規範指定經度的第一個緯度和第二個緯度。

更新2

下面是我的文件之一的JSON:

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

更新3

我創造了這個問題的最小再現,而且我發現該問題不再發生。

這表明問題確實存在於我自己的代碼中。

我着手檢查原始代碼和複製代碼之間的所有差異,並最終發現對我來說看起來相當無辜的事實上已經產生了很大的影響。幸運的是,該代碼根本不需要,所以簡單的修復就是不使用那些代碼。

有一次,我使用自定義ContractResolver,一旦不再需要它時,我還沒有移除它。

這裏是有問題的再現代碼:

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

你能編輯你的問題來顯示你正在使用的多邊形嗎? –

+0

是的,我已經將它包含在代碼形式中,並添加了有關戒指方向的信息。 – AndyJ

+0

您能否提供一份您收藏的樣本文件? – Amor

回答

1

我使用的是自定義的ContractResolver,那是有明顯的DocumentDB類從.NET SDK的性能有很大的影響。

這就是我是如何設置ContractResolver

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

,這是它是如何實現的:

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

的解決方案是容易的,不設置JsonConvert.DefaultSettings所以ContractResolver ISN」使用。

結果:

我能夠在21799.0221毫秒,這22秒鐘內執行我的空間查詢。

以前花了170717.151毫秒,這是2分50秒。

這大約快了8倍!