Azure Cosmos DB Query コスト


## リソース情報
Performance Tips
https://docs.microsoft.com/ja-jp/azure/cosmos-db/performance-tips

Azure Cosmos DB を使用したクエリ パフォーマンスのチューニング
https://docs.microsoft.com/ja-jp/azure/cosmos-db/sql-api-sql-query-metrics

Cosmos DB Code Sample
https://github.com/Azure/azure-documentdb-dotnet/tree/master/samples/code-samples

David Makogon GitHub
https://github.com/dmakogon


## 検証環境
- Windows 10
- Visual Studio 2017/.NET Framework 4.7
- Microsoft.Azure.DocumentDB 1.22.0



## 複数パターンでクエリ性能を測定
David Makogonのコードを少し書き換えてクエリのコストを測定をしてみました。
![image](https://qiita-image-store.s3.amazonaws.com/0/33998/b3498982-074e-8850-3204-5876dab934df.png)
RUは10000を指定、データは適当ですがprop1をPartition Keyに設定してデータを何件か保存。

```json
{
    "id": "aaa1c53a-a7e1-42c0-966b-0369d1752bf2",
    "prop1": "ec14c55e-e59f-4479-870d-8ba21933bfb7",
    "prop2": 42,
    "prop3": "53b13e52-2315-4a63-bab5-cfc6de0495d1",
    "prop4": 90,
    "prop5": "5:51:23",
    "prop6": 5,
    "prop7": "3a2d9523-f51b-4fde-9c2f-0bfef4dc2ff2",
    "prop8": 18,
    "prop9": "124a1b1d-aa28-45ef-9183-733006d043a7",
    "prop10": 43,
    "prop11": "002efaae-af13-4c1d-ab7b-a1369c965305",
    "prop12": 96,
    "prop13": "3cbb7932-79fb-4f35-b944-8ccecd8aef89",
    "prop14": 23,
    "prop15": "d93fde6f-0043-48a4-aca6-69321a76fec5",
    "prop16": 70,
    "_rid": "KDxQANCm7QACAAAAAAAACA==",
    "_self": "dbs/KDxQAA==/colls/KDxQANCm7QA=/docs/KDxQANCm7QACAAAAAAAACA==/",
    "_etag": "\"0f00af19-0000-0000-0000-5b22025b0000\"",
    "_attachments": "attachments/",
    "_ts": 1528955483
}
```

Partition Keyを指定しないでQueryを実行した場合のコード。クエリ統計を確認したかったのでPopulateQueryMetrics=trueを指定。


```csharp
private async Task<double> QueryDocument(string id)
{
    var RU = 0.0;
    var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
    FeedOptions queryOptions = new FeedOptions { EnableCrossPartitionQuery = true, PopulateQueryMetrics=true };

    var queryable = client.CreateDocumentQuery(collectionUri,
            String.Format("SELECT * FROM docs WHERE docs.id = '{0}'", id), queryOptions).AsDocumentQuery();

    while (queryable.HasMoreResults)
    {
        FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
        foreach (var res in queryResponse.QueryMetrics)
        {
            var queryMetrix = JsonConvert.SerializeObject(res.Value, Formatting.Indented);
            Console.WriteLine(queryMetrix);
        }
        RU += queryResponse.RequestCharge;
    }
    return RU;
}
```


SDK内部では実データを取得する前にPartitionKeyRangesを取得。ちなみにここでは5つのレンジが取得できた。

```json
{"_rid":"sHcUAIljNgg=","PartitionKeyRanges":[
{"_rid":"sHcUAIljNggCAAAAAAAAUA==","id":"0","_etag":"\"0000a502-0000-0000-0000-5b2096e20000\"","minInclusive":"","maxExclusive":"05C1C9CD673398","ridPrefix":0,"_self":"dbs\/sHcUAA==\/colls\/sHcUAIljNgg=\/pkranges\/sHcUAIljNggCAAAAAAAAUA==\/","throughputFraction":0.2,"status":"online","parents":[],"_ts":1528862434,"_lsn":82},
{"_rid":"sHcUAIljNggDAAAAAAAAUA==","id":"1","_etag":"\"0000a602-0000-0000-0000-5b2096e20000\"","minInclusive":"05C1C9CD673398","maxExclusive":"05C1D9CD673398","ridPrefix":1,"_self":"dbs\/sHcUAA==\/colls\/sHcUAIljNgg=\/pkranges\/sHcUAIljNggDAAAAAAAAUA==\/","throughputFraction":0.2,"status":"online","parents":[],"_ts":1528862434,"_lsn":83},
{"_rid":"sHcUAIljNggEAAAAAAAAUA==","id":"2","_etag":"\"0000a702-0000-0000-0000-5b2096e20000\"","minInclusive":"05C1D9CD673398","maxExclusive":"05C1E399CD6732","ridPrefix":2,"_self":"dbs\/sHcUAA==\/colls\/sHcUAIljNgg=\/pkranges\/sHcUAIljNggEAAAAAAAAUA==\/","throughputFraction":0.2,"status":"online","parents":[],"_ts":1528862434,"_lsn":84},
{"_rid":"sHcUAIljNggFAAAAAAAAUA==","id":"3","_etag":"\"0000a802-0000-0000-0000-5b2096e20000\"","minInclusive":"05C1E399CD6732","maxExclusive":"05C1E9CD673398","ridPrefix":3,"_self":"dbs\/sHcUAA==\/colls\/sHcUAIljNgg=\/pkranges\/sHcUAIljNggFAAAAAAAAUA==\/","throughputFraction":0.2,"status":"online","parents":[],"_ts":1528862434,"_lsn":85},
{"_rid":"sHcUAIljNggGAAAAAAAAUA==","id":"4","_etag":"\"0000a902-0000-0000-0000-5b2096e20000\"","minInclusive":"05C1E9CD673398","maxExclusive":"FF","ridPrefix":4,"_self":"dbs\/sHcUAA==\/colls\/sHcUAIljNgg=\/pkranges\/sHcUAIljNggGAAAAAAAAUA==\/","throughputFraction":0.2,"status":"online","parents":[],"_ts":1528862434,"_lsn":86}],"_count":5}

```

SDKから送信しているメッセージはこちら。Headerには設定可能な様々なパラメーター、Bodyにはクエリが含まれる。x-ms-continuationでPartitionKeyRangesを指定しているのもわかる。同様のメッセージが5回送信される。

```html
POST https://xxxxxxxxxxxx-japaneast.documents.azure.com/dbs/dbid/colls/colId/docs HTTP/1.1
x-ms-continuation: {"token":null,"range":{"min":"05C1C9CD673398","max":"05C1D9CD673398"}}
x-ms-documentdb-isquery: True
x-ms-documentdb-query-enablecrosspartition: True
x-ms-documentdb-query-iscontinuationexpected: True
x-ms-documentdb-populatequerymetrics: True
x-ms-date: Tue, 26 Jun 2018 11:40:06 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig%3dtyNM9D1TCpEjGhJi8rtokfKvnX9nhG4Ep%2bTV0iWXzS4%3d
x-ms-session-token: 1:16,0:17
Cache-Control: no-cache
x-ms-consistency-level: Session
User-Agent: documentdb-dotnet-sdk/1.22.0 Host/32-bit MicrosoftWindowsNT/6.2.9200.0
x-ms-version: 2017-11-15
Accept: application/json
Content-Type: application/query+json
Host: dskcosmosdb-japaneast.documents.azure.com
Content-Length: 85
Expect: 100-continue

{"query":"SELECT * FROM docs WHERE docs.id = '67b56674-bfcd-491c-80ef-6fc0b08fb117'"}
```

レスポンスはこちら。データ取得できた場合はBodyに値がはいる。

```html
HTTP/1.1 200 Ok
Cache-Control: no-store, no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json
Server: Microsoft-HTTPAPI/2.0
Strict-Transport-Security: max-age=31536000
x-ms-last-state-change-utc: Wed, 13 Jun 2018 09:06:24.157 GMT
x-ms-resource-quota: documentSize=10240;documentsSize=10485760;documentsCount=-1;collectionSize=10485760;
x-ms-resource-usage: documentSize=0;documentsSize=0;documentsCount=4;collectionSize=1;
lsn: 16
x-ms-item-count: 1
x-ms-schemaversion: 1.6
x-ms-alt-content-path: dbs/dbid/colls/colId
x-ms-content-path: KDxQANCm7QA=
x-ms-xp-role: 2
x-ms-documentdb-query-metrics: totalExecutionTimeInMs=0.42;queryCompileTimeInMs=0.08;queryLogicalPlanBuildTimeInMs=0.02;queryPhysicalPlanBuildTimeInMs=0.03;queryOptimizationTimeInMs=0.00;VMExecutionTimeInMs=0.06;indexLookupTimeInMs=0.00;documentLoadTimeInMs=0.04;systemFunctionExecuteTimeInMs=0.00;userFunctionExecuteTimeInMs=0.00;retrievedDocumentCount=4;retrievedDocumentSize=3490;outputDocumentCount=1;outputDocumentSize=0;writeOutputTimeInMs=0.00;indexUtilizationRatio=0.25
x-ms-global-Committed-lsn: 15
x-ms-number-of-read-regions: 1
x-ms-transport-request-id: 41097
x-ms-request-charge: 2.59
x-ms-serviceversion: version=2.0.0.0
x-ms-activity-id: 67ef44ac-e98a-45e2-a150-26769f534d6b
x-ms-session-token: 1:16
x-ms-continuation: {"token":null,"range":{"min":"05C1D9CD673398","max":"05C1E399CD6732"}}
x-ms-gatewayversion: version=2.0.0.0
Date: Tue, 26 Jun 2018 11:40:06 GMT

2EB
{"_rid":"KDxQANCm7QA=","Documents":[{"id":"67b56674-bfcd-491c-80ef-6fc0b08fb117","prop1":"e84dd030-47e3-4fc0-8d1f-e91440c6b24b","prop2":1,"prop3":"c6a22a49-dd96-4b1f-8f52-d552ce1a8393","prop4":78,"prop5":"11:40:05","prop6":73,"prop7":"2cac4837-0584-454b-977b-2d587e2b75eb","prop8":54,"prop9":"328676e5-49c9-4432-a007-24902be5c3a7","prop10":26,"prop11":"b4ff7605-63fc-4450-ae84-d295c867a5ce","prop12":74,"prop13":"cb35a856-aa06-4b2c-9a42-493d2e2d066b","prop14":3,"prop15":"0b160481-4338-4067-a3ca-5d4c911fe4da","prop16":77,"_rid":"KDxQANCm7QAEAAAAAAAACA==","_self":"dbs\/KDxQAA==\/colls\/KDxQANCm7QA=\/docs\/KDxQANCm7QAEAAAAAAAACA==\/","_etag":"\"0f001693-0000-0000-0000-5b3226160000\"","_attachments":"attachments\/","_ts":1530013206}],"_count":1}
0


```


RUコストは 12.41でした。データ量が少ないのであまり参考になりませんがクエリ統計がこちら。ここから各クエリのIndex検索でかかった時間、ドキュメントロード時間/サイズ、Runtimeの実行時間などが取得可能です。

```json

{
  "TotalTime": "00:00:00.0004100",
  "RetrievedDocumentCount": 5,
  "RetrievedDocumentSize": 4184,
  "OutputDocumentCount": 0,
  "QueryPreparationTimes": {
    "CompileTime": "00:00:00.0000900",
    "LogicalPlanBuildTime": "00:00:00.0000300",
    "PhysicalPlanBuildTime": "00:00:00.0000400",
    "QueryOptimizationTime": "00:00:00"
  },
  "QueryEngineTimes": {
    "IndexLookupTime": "00:00:00",
    "DocumentLoadTime": "00:00:00.0000400",
    "WriteOutputTime": "00:00:00",
    "RuntimeExecutionTimes": {
      "SystemFunctionExecutionTime": "00:00:00",
      "UserDefinedFunctionExecutionTime": "00:00:00",
      "TotalTime": "00:00:00.0000200"
    }
  },
  "Retries": 0,
  "IndexHitRatio": 0.0
}
{
  "TotalTime": "00:00:00.0004200",
  "RetrievedDocumentCount": 4,
  "RetrievedDocumentSize": 3490,
  "OutputDocumentCount": 1,
  "QueryPreparationTimes": {
    "CompileTime": "00:00:00.0000800",
    "LogicalPlanBuildTime": "00:00:00.0000200",
    "PhysicalPlanBuildTime": "00:00:00.0000300",
    "QueryOptimizationTime": "00:00:00"
  },
  "QueryEngineTimes": {
    "IndexLookupTime": "00:00:00",
    "DocumentLoadTime": "00:00:00.0000400",
    "WriteOutputTime": "00:00:00",
    "RuntimeExecutionTimes": {
      "SystemFunctionExecutionTime": "00:00:00",
      "UserDefinedFunctionExecutionTime": "00:00:00",
      "TotalTime": "00:00:00.0000200"
    }
  },
  "Retries": 0,
  "IndexHitRatio": 0.25
}
{
  "TotalTime": "00:00:00.0004100",
  "RetrievedDocumentCount": 3,
  "RetrievedDocumentSize": 2794,
  "OutputDocumentCount": 0,
  "QueryPreparationTimes": {
    "CompileTime": "00:00:00.0000700",
    "LogicalPlanBuildTime": "00:00:00.0000300",
    "PhysicalPlanBuildTime": "00:00:00.0000400",
    "QueryOptimizationTime": "00:00:00"
  },
  "QueryEngineTimes": {
    "IndexLookupTime": "00:00:00",
    "DocumentLoadTime": "00:00:00.0000300",
    "WriteOutputTime": "00:00:00",
    "RuntimeExecutionTimes": {
      "SystemFunctionExecutionTime": "00:00:00",
      "UserDefinedFunctionExecutionTime": "00:00:00",
      "TotalTime": "00:00:00.0000100"
    }
  },
  "Retries": 0,
  "IndexHitRatio": 0.0
}
{
  "TotalTime": "00:00:00.0005000",
  "RetrievedDocumentCount": 5,
  "RetrievedDocumentSize": 4197,
  "OutputDocumentCount": 0,
  "QueryPreparationTimes": {
    "CompileTime": "00:00:00.0000800",
    "LogicalPlanBuildTime": "00:00:00.0000300",
    "PhysicalPlanBuildTime": "00:00:00.0000500",
    "QueryOptimizationTime": "00:00:00"
  },
  "QueryEngineTimes": {
    "IndexLookupTime": "00:00:00",
    "DocumentLoadTime": "00:00:00.0000500",
    "WriteOutputTime": "00:00:00",
    "RuntimeExecutionTimes": {
      "SystemFunctionExecutionTime": "00:00:00",
      "UserDefinedFunctionExecutionTime": "00:00:00",
      "TotalTime": "00:00:00.0000300"
    }
  },
  "Retries": 0,
  "IndexHitRatio": 0.0
}
{
  "TotalTime": "00:00:00.0004200",
  "RetrievedDocumentCount": 5,
  "RetrievedDocumentSize": 4195,
  "OutputDocumentCount": 0,
  "QueryPreparationTimes": {
    "CompileTime": "00:00:00.0000800",
    "LogicalPlanBuildTime": "00:00:00.0000300",
    "PhysicalPlanBuildTime": "00:00:00.0000400",
    "QueryOptimizationTime": "00:00:00"
  },
  "QueryEngineTimes": {
    "IndexLookupTime": "00:00:00",
    "DocumentLoadTime": "00:00:00.0000400",
    "WriteOutputTime": "00:00:00",
    "RuntimeExecutionTimes": {
      "SystemFunctionExecutionTime": "00:00:00",
      "UserDefinedFunctionExecutionTime": "00:00:00",
      "TotalTime": "00:00:00.0000300"
    }
  },
  "Retries": 0,
  "IndexHitRatio": 0.0
}
```

なおPartition Keyを指定すればダイレクトに該当のPartitionにアクセスできるのでPartitionKeyRangesに関連する処理を省くことができ大幅にRUコストを抑えることができます。Partition Keyの設計は非常に重要です。
ついでにLINQも試してみましたが内部的に同等のクエリに変換しているので同じRUです。シンプルなクエリなので同じ結果ですが複雑なクエリになると生成されるSELECTが異なる可能性があるのでRUも異なる可能性があります。
なお、[DocumentClient.ReadDocumentAsync](https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.documents.client.documentclient.readdocumentasync?view=azure-dotnet#Microsoft_Azure_Documents_Client_DocumentClient_ReadDocumentAsync_System_Uri_Microsoft_Azure_Documents_Client_RequestOptions_)（Read a document cost with PartitionKey）は1RUとコストをさらにおさえることができました。以下メッセージのようにBodyにQueryを含めるのではなくダイレクトにRest GETでデータを取得していることが次のメッセージからわかります。

```http
GET https://xxxxxxxxxxxx-japaneast.documents.azure.com/dbs/dbid/colls/colId/docs/d899a5f6-e6e5-43fc-a7f3-b02e2359eacc HTTP/1.1
x-ms-documentdb-partitionkey: ["079d7925-c9c4-4d38-85e2-b5f3e7eaa4da"]
x-ms-date: Wed, 27 Jun 2018 03:48:10 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig%xxxxxxxxxxxx%3d
x-ms-session-token: 4:19
Cache-Control: no-cache
x-ms-consistency-level: Session
User-Agent: documentdb-dotnet-sdk/1.22.0 Host/32-bit MicrosoftWindowsNT/6.2.9200.0
x-ms-version: 2017-11-15
Accept: application/json
Host: dskcosmosdb-japaneast.documents.azure.com
```



つぎに、こんな感じでデータ量を増やしてみました。ちなみにデータロードには[BulkExecutor](https://docs.microsoft.com/ja-jp/azure/cosmos-db/bulk-executor-dot-net)のBulkImportSampleで以下の指定をしました。BulkExecutor便利です。

```xml
<add key="NumberOfDocumentsToImport" value="10000000" />
<add key="NumberOfBatches" value="10" />
<add key="CollectionPartitionKey" value="/profileid" />
```

データ量はこんな感じ。
![image](https://qiita-image-store.s3.amazonaws.com/0/33998/4c85a225-ebfd-8edd-29d1-ef31875b3f78.png)



Partition Keyを指定しないクエリのコストがめちゃくちゃ増えました。やっぱりパーティションは重要です。

![image](https://qiita-image-store.s3.amazonaws.com/0/33998/c683b533-b8c4-d316-6f84-b6e747fdf951.png)


## サンプルコード

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
using Microsoft.Extensions.Configuration;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using System.Configuration;

namespace QueryPerformance
{
    class Program
    {
        const int runs = 10;

        public static IConfiguration Config { get; set; }

        private static readonly string endpointUrl = ConfigurationManager.AppSettings["EndpointUrl"] ?? "Not Found";
        private static readonly string primaryKey = ConfigurationManager.AppSettings["PrimaryKey"] ?? "Not Found";
        private static readonly string database = ConfigurationManager.AppSettings["Database"] ?? "Not Found";
        private static readonly string collection = ConfigurationManager.AppSettings["Collection"] ?? "Not Found";
        private static DocumentClient client;

        static void Main(string[] args)
        {

            client = new DocumentClient(new Uri(endpointUrl), primaryKey);
        
            try
            {
                Program p = new Program();
                p.CompareReadVsQuery().Wait();
            }
            catch (DocumentClientException de)
            {
                Exception baseException = de.GetBaseException();
                Console.WriteLine("{0} error occurred: {1}, Message: {2}", de.StatusCode, de.Message, baseException.Message);
            }
            catch (Exception e)
            {
                Exception baseException = e.GetBaseException();
                Console.WriteLine("Error: {0}, Message: {1}", e.Message, baseException.Message);
            }
            finally
            {
                Console.WriteLine("Finish");
            }
        }

        private async Task CompareReadVsQuery()
        {
            await client.OpenAsync().ConfigureAwait(false);
            var random = new Random((int)DateTime.Now.Ticks);
            var doc = new Doc
            {
                Id = Guid.NewGuid().ToString(),
                prop1 = Guid.NewGuid().ToString(),
                prop2 = random.Next(100),
                prop3 = Guid.NewGuid().ToString(),
                prop4 = random.Next(100),
                prop5 = DateTime.UtcNow.ToLongTimeString(),
                prop6 = random.Next(100),
                prop7 = Guid.NewGuid().ToString(),
                prop8 = random.Next(100),
                prop9 = Guid.NewGuid().ToString(),
                prop10 = random.Next(100),
                prop11 = Guid.NewGuid().ToString(),
                prop12 = random.Next(100),
                prop13 = Guid.NewGuid().ToString(),
                prop14 = random.Next(100),
                prop15 = Guid.NewGuid().ToString(),
                prop16 = random.Next(100)
            };

            double writeRU = await this.CreateDocument(database, collection, doc);
            Console.WriteLine("Write a document cost: {0} RU", writeRU);

            double readWithPartitionKeyRU = await this.ReadDocumentWithPartitionKey(doc.Id, doc.prop1);
            Console.WriteLine("Read a document cost with PartitionKey: {0} RU", readWithPartitionKeyRU);

            //double queryWithPartitionKeyRU = await this.QueryDocumentWithPartitionKey(doc.Id, doc.prop1);
            //Console.WriteLine("Query cost (SELECT) with PartitionKey : {0} RU", queryWithPartitionKeyRU);

            //double queryRU = await this.QueryDocument(doc.Id);
            //Console.WriteLine("Query cost (SELECT) without PartitionKey: {0} RU", queryRU);

            //double linqQueryWithPartitionKeyRU = await this.QueryDocumentByLinqWithPartitionKey(doc.Id, doc.prop1);
            //Console.WriteLine("LINQ Query cost with PartitionKey : {0} RU", linqQueryWithPartitionKeyRU);

            //double linqQueryRU = await this.QueryDocumentByLinq(doc.Id);
            //Console.WriteLine("LINQ Query cost without PartitionKey: {0} RU", linqQueryRU);

        }

        private async Task<double> CreateDocument(string databaseName, string collectionName, Doc doc)
        {
            var result = await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), doc);
            return result.RequestCharge;
        }

        // Read a document without Partion Key
        private async Task<double> ReadDocument(string id)
        {
            var docUri = UriFactory.CreateDocumentUri(database, collection, id);
            var result = await client.ReadDocumentAsync(docUri);
            return result.RequestCharge;
        }

        // Read a document with Partion Key
        private async Task<double> ReadDocumentWithPartitionKey(string id, string partitionKey)
        {
            var docUri = UriFactory.CreateDocumentUri(database, collection, id);
            var result = await client.ReadDocumentAsync(
                docUri,
                new RequestOptions { PartitionKey = new PartitionKey(partitionKey) });
            return result.RequestCharge;
        }

        // Query Document
        private async Task<double> QueryDocument(string id)
        {
            var RU = 0.0;
            var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
            FeedOptions queryOptions = new FeedOptions { EnableCrossPartitionQuery = true, PopulateQueryMetrics=true };
 
            var queryable = client.CreateDocumentQuery(collectionUri,
                    String.Format("SELECT * FROM docs WHERE docs.id = '{0}'", id), queryOptions).AsDocumentQuery();

            while (queryable.HasMoreResults)
            {
                FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
                //foreach (var res in queryResponse.QueryMetrics)
                //{
                //    var queryMetrix = JsonConvert.SerializeObject(res.Value, Formatting.Indented);
                //    Console.WriteLine("QueryMetrix");
                //    Console.WriteLine(queryMetrix);
                //}
                RU += queryResponse.RequestCharge;
            }
            return RU;
        }

        private async Task<double> QueryDocumentWithPartitionKey(string id, string partitionKey)
        {
            var RU = 0.0;
            var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
            FeedOptions queryOptions = new FeedOptions { EnableCrossPartitionQuery = true, PartitionKey = new PartitionKey(partitionKey), PopulateQueryMetrics = true };

            var queryable = client.CreateDocumentQuery(collectionUri,
                    String.Format("SELECT * FROM docs WHERE docs.id = '{0}'", id), queryOptions).AsDocumentQuery();

            while (queryable.HasMoreResults)
            {
                FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
                RU += queryResponse.RequestCharge;
            }
            return RU;
        }


        // Query Document using LINQ
        private async Task<double> QueryDocumentByLinq(string id)
        {
            var RU = 0.0;
            var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
            FeedOptions queryOptions = new FeedOptions { MaxItemCount = 1, EnableCrossPartitionQuery = true, PopulateQueryMetrics = true };

            var queryable = client.CreateDocumentQuery<Doc>(collectionUri, queryOptions)
                .Where(d => d.Id == id)
                .AsDocumentQuery();

            while (queryable.HasMoreResults)
            {
                FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
                RU += queryResponse.RequestCharge;
            }
            return RU;
        }

        // Query Document using LINQ
        private async Task<double> QueryDocumentByLinqWithPartitionKey(string id, string partitionKey)
        {
            var RU = 0.0;
            var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
            FeedOptions queryOptions = new FeedOptions { MaxItemCount = 1, PartitionKey = new PartitionKey(partitionKey), PopulateQueryMetrics = true };

            var queryable = client.CreateDocumentQuery<Doc>(collectionUri, queryOptions)
            .Where(d => d.Id == id)
            .AsDocumentQuery();

            while (queryable.HasMoreResults)
            {
                FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
                RU += queryResponse.RequestCharge;
            }
            return RU;
        }

        private async Task<double> QueryDocumentByRange(string id)
        {
            var RU = 0.0;
            var collectionUri = UriFactory.CreateDocumentCollectionUri(database, collection);
            FeedOptions queryOptions = new FeedOptions { MaxItemCount = 1, EnableCrossPartitionQuery = true, PopulateQueryMetrics = true };

            var queryable = client.CreateDocumentQuery(collectionUri,
                String.Format("SELECT top 1 * FROM docs WHERE docs.prop10 > 50", id), queryOptions).AsDocumentQuery();
            while (queryable.HasMoreResults)
            {
                FeedResponse<dynamic> queryResponse = await queryable.ExecuteNextAsync<dynamic>();
                RU += queryResponse.RequestCharge;
            }
            return RU;
        }
    }
}
```