2017-06-21 106 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倍!