2017-06-21 7 views
5

私は最新の更新を書き、スタックオーバーフローから次のエラーを受け取りました: "本体は30000文字に制限されており、38676を入力しました。CosmosDBクエリのパフォーマンス

私は自分の冒険を文書化するのが非常に冗長であると言っても過言ではないので、私がここにあるものをより簡潔に書き直しました。

私は長いオリジナルの投稿と更新をpastebinに保存しました。私は多くの人がそれらを読んでいるとは思わないが、私はそれらに多くの努力を払うので、それらを失わないのはいいだろう。


私は、CosmosDBの使い方やパフォーマンステストなどのための10万のドキュメントを含むコレクションを用意しています。

これらのドキュメントのそれぞれは、GeoJSON PointであるLocationプロパティを持っています。

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

点の位置10億の英国の住所の中から無作為に選ばれているので、かなり現実的な広がりを持つべきです。

ポリゴンは16ポイント(最初と最後のポイントが同じ)で構成されているため、あまり複雑ではありません。ロンドンからロンドンまで、ほとんどの南部のほとんどをカバーしています。

このクエリの実行例では、170717.151 ms、つまりわずか171秒未満またはわずか3分未満で、3917.92 RUを使用して8728のドキュメントが返されました。

3918 RU/171秒= 22.91 RU/sの

私は現在、400 RU/sで、スループット(RU/s)が最小値に設定されています。

これは保証された予約済みのレベルであると私は理解していました。あなたは時々そのレベルの上に "バースト"することができますが、あまりにも頻繁にそれを行うと、あなたは予約レベルに絞られます。

"RU/sのクエリ速度"は、明らかに400 RU/sのスループット設定よりはるかに低いです。

クライアントを「ローカル」に、つまり私のオフィスで実行していますが、Azureデータセンターでは実行していません。

各ドキュメントのサイズは、約500バイト(0.5kb)です。

何が起こっているのですか?

何か間違っていますか?

RU/sに関してクエリが抑制されていると誤解していますか?

これはGeoSpatialのインデックスが動作するスピードですか、それで最高のパフォーマンスが得られますか?

GeoSpatialインデックスは使用されていませんか?

作成したインデックスを表示する方法はありますか?

インデックスが使用されているかどうかを確認する方法はありますか?

時間を費やしている場所に関するクエリをプロファイルして測定基準を取得する方法はありますか?例えばsはタイプ別に文書を検索するために使用され、sはGeoSpatiallyでフィルタリングされ、sはデータの転送に使用されました。

UPDATE 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仕様では、経度1番目と緯度2番目を指定しています。私は、問題の最低限の再生を作成し、そして私が見つけた

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

UPDATE 2

は、ここに私の文書のいずれかのJSONです問題はもはや発生しませんでした。

これは、問題が実際に私自身のコードであることを示しています。

私は、元のコードと再生コードの相違点をすべて調べて、結局私には無害であると思われるものが大きなインパクトを持つことを発見しました。ありがたいことに、そのコードはまったく必要ではなかったので、単純にそのコードを使用しないのは簡単な修正でした。

ある時点で私はカスタムContractResolverを使用していましたが、もう必要がなくなったら削除しませんでした。私はカスタムContractResolverを使用していたし、それは明らかにネットSDKからDocumentDBクラスのパフォーマンスに大きな影響を持っていた

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を設定した方法だった。

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

ソリューションは簡単だった、「そうContractResolver ISNをJsonConvert.DefaultSettingsを設定していません使用される。

結果:

私は22秒である、21799.0221ミリ秒で私の空間的なクエリを実行することができました。

以前は、170717.151 ms(2分50秒)でした。

これは約8倍高速です!

関連する問題