2017-06-21 15 views
5

Tôi đã viết bản cập nhật mới nhất của mình và sau đó nhận được lỗi sau từ Stack Overflow: "Body được giới hạn ở 30000 ký tự; bạn đã nhập 38676."Hiệu suất truy vấn CosmosDB

Thật công bằng khi nói rằng tôi đã rất tiết lộ tài liệu về cuộc phiêu lưu của mình, vì vậy tôi đã viết lại những gì tôi có ở đây để súc tích hơn.

Tôi đã lưu trữ bài đăng và bản cập nhật gốc của tôi (dài) trên pastebin. Tôi không nghĩ nhiều người sẽ đọc chúng, nhưng tôi đã dành rất nhiều nỗ lực cho họ để có thể không bị mất.


Tôi có bộ sưu tập chứa 100.000 tài liệu để tìm hiểu cách sử dụng CosmosDB và cho những thứ như kiểm tra hiệu suất.

Mỗi tài liệu trong số này có thuộc tính Location là GeoJSON Point.

Theo số documentation, điểm GeoJSON sẽ được lập chỉ mục tự động.

Azure Cosmos DB hỗ trợ trích xuất tự động tại các điểm, đa giác, và LineStrings

Tôi đã kiểm tra các chính sách chỉ mục cho bộ sưu tập của tôi, và nó có mục nhập cho tự động điểm lập chỉ mục:

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

Tôi đã tìm kiếm một cách để liệt kê, hoặc thẩm vấn các chỉ mục đã được tạo, nhưng tôi chưa tìm thấy một điều như vậy, vì vậy tôi đã không thể xác nhận rằng tài sản này chắc chắn đang được lập chỉ mục.

Tôi đã tạo GeoJSON Polygon và sau đó sử dụng để truy vấn tài liệu của mình.

Đây là câu hỏi của tôi:

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

Và sau đó tôi vượt qua đối tượng truy vấn đến các phương pháp sau đây để tôi có thể có được kết quả trong khi theo dõi các đơn vị Yêu cầu sử dụng:

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

Vị trí điểm được chọn ngẫu nhiên từ 10 trong số hàng triệu địa chỉ ở Vương quốc Anh, vì vậy họ nên có một sự lây lan khá thực tế.

Đa giác được tạo thành 16 điểm (với điểm đầu tiên và cuối cùng giống nhau), vì vậy nó không quá phức tạp. Nó bao gồm hầu hết các phần phía nam của Vương quốc Anh, từ London xuống.

Một ví dụ chạy truy vấn này trả lại 8728 tài liệu, sử dụng 3917.92 RU, trong 170717.151 mili giây, chỉ dưới 171 giây hoặc chỉ dưới 3 phút.

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

Tôi hiện có Throughput (RU/s) để thiết lập giá trị thấp nhất, ở mức 400 RU/s.

Đó là sự hiểu biết của tôi rằng đây là cấp độ dành riêng mà bạn được đảm bảo nhận được. Bạn có thể "bùng nổ" ở trên mức đó vào các thời điểm, nhưng làm điều đó quá thường xuyên và bạn sẽ được điều chỉnh trở lại mức dự trữ của bạn.

"Tốc độ truy vấn" của 23 RU/s, rõ ràng, thấp hơn nhiều so với cài đặt Thông lượng 400 RU/s.

Tôi đang chạy ứng dụng khách "cục bộ" tức là trong văn phòng của tôi và không ở trong trung tâm dữ liệu Azure.

Mỗi tài liệu có kích thước khoảng 500 byte (0,5 kb).

Vậy điều gì đang xảy ra?

Tôi có làm gì sai không?

Tôi có hiểu nhầm cách truy vấn của tôi đang được điều chỉnh theo RU/s không?

Đây có phải là tốc độ mà tại đó các chỉ mục GeoSpatial hoạt động và vì vậy hiệu suất tốt nhất tôi sẽ nhận được?

Chỉ mục GeoSpatial không được sử dụng?

Có cách nào tôi có thể xem các chỉ mục được tạo không?

Có cách nào tôi có thể kiểm tra xem chỉ mục có đang được sử dụng không?

Có cách nào để tôi có thể cấu hình truy vấn và nhận chỉ số về thời gian đang được sử dụng không? ví dụ. s đã được sử dụng tìm kiếm tài liệu theo loại của họ, s đã được sử dụng lọc chúng GeoSpatially, và s đã được sử dụng chuyển dữ liệu.

UPDATE 1

Dưới đây là các đa giác tôi đang sử dụng trong truy vấn:

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

Tôi cũng đã cố gắng đảo ngược nó (vì vấn đề định hướng vòng), nhưng truy vấn với các đa giác đảo ngược mất nhiều thời gian hơn (tôi không có thời gian để giao) và trả lại 91272 mục.

Ngoài ra, toạ độ được chỉ định là Kinh độ/Vĩ độ, như this is how GeoJSON expects them (ví dụ: X/Y), thay vì thứ tự truyền thống được sử dụng khi nói về Vĩ độ/Kinh độ.

Đặc tả GeoJSON chỉ định kinh độ thứ nhất và vĩ độ thứ hai.

UPDATE 2

Đây là JSON cho một trong những tài liệu của tôi:

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

CẬP NHẬT 3

Tôi tạo ra một bản tái tạo tối thiểu về vấn đề này, và tôi thấy vấn đề không còn xảy ra nữa.

Điều này cho thấy rằng sự cố thực sự nằm trong mã của riêng tôi.

Tôi đã đặt ra để kiểm tra tất cả sự khác biệt giữa mã gốc và mã sinh sản và cuối cùng nhận thấy rằng thứ gì đó có vẻ khá vô tội đối với tôi là không có tác động lớn.Và may mắn thay, mã đó không cần thiết chút nào, do đó, nó là một sửa chữa dễ dàng để chỉ đơn giản là không sử dụng mã bit đó.

Tại một thời điểm tôi đã sử dụng tùy chỉnh ContractResolver và tôi đã không xóa nó khi không còn cần thiết nữa.

Dưới đây là mã vi phạm sinh sản:

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

Bạn có thể vui lòng chỉnh sửa câu hỏi của mình để hiển thị đa giác bạn đang sử dụng không? –

+0

Đúng, tôi đã bao gồm nó ở dạng mã và thêm thông tin về định hướng vòng. – AndyJ

+0

Bạn có thể cung cấp tài liệu mẫu về bộ sưu tập của mình không? – Amor

Trả lời

1

Tôi đã sử dụng một tùy chỉnh ContractResolver và đã được rõ ràng có ảnh hưởng lớn đến hiệu suất của các lớp DocumentDB từ Net SDK.

Đây là cách tôi đã thiết lập các ContractResolver:

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

Và đây là cách nó đã được thực hiện:

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

Giải pháp là dễ dàng, không đặt JsonConvert.DefaultSettings nên ContractResolver isn' t được sử dụng.

Kết quả:

Tôi có thể thực hiện truy vấn không gian trong 21799.0221 mili giây, tức là 22 giây.

Trước đây, phải mất 170717.151 mili giây, tức là 2 phút 50 giây.

Đó là khoảng 8x nhanh hơn!

Các vấn đề liên quan