読者です 読者をやめる 読者になる 読者になる

DocumentDBライブラリの「1クエリ」は「1HTTPリクエスト」ではない

豆知識的なメモ ブログです。

私自身、DocumentDBを学ぶ上で「あー、そうなんだぁ。ライブラリがいい意味でも、そうでない意味でも、面倒見てくれてるんだなぁ」と思った点について書いておきます。

※本記事では「FeedOptions.MaxItemCount」と「DocumentDBのREST API呼び出しにおけるHTTPヘッダー「x-ms-max-item-count」」について記述しています。

前提条件

以下のモデルクラスデータが 1,000件 、DocumentDBに保存されているものとします。

public class ProductItem
{
  [JsonProperty(PropertyName = "id")]
  public string Id { get; set; }

  public string Name { get; set; }

  public int Price { get; set; }

  public int StockNumber { get; set; }
}

また、ドキュメントは以下のデータベース・コレクションに保存されているものとします。

データベース名: ExampleDB1
コレクション名: ExampleCollection1

ライブラリを使って全件クエリー!

プロジェクトに対してNugetから「Microsoft.Azure.Document」を追加すると以下のようなコードで 1,000件のProductItemオブジェクト を取得することができます。

// リスト1
string EndpointUrl = "https://rddocdb.documents.azure.com:443/";
string PrimaryKey = "[プライマリキー]";

string DatabaseID = "ExampleDB1";
string CollectionID = "ExampleCollection1";

// DocumentClientオブジェクト初期化
DocumentClient client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);

// 全件取得
Uri documentCollectionUri = 
  UriFactory.CreateDocumentCollectionUri(DatabaseID, CollectionID);
var query = 
  client.CreateDocumentQuery<ProductItem>(documentCollectionUri);
// result変数に1,000件のデータが取得される
var result = query.ToList();

実際に発行されたHTTP

上記コードでは、最後の「result = query.ToList();」を実行したタイミングで DocumentDB へのクエリー要求が行われます。
処理完了後に result 変数には1,000件の ProductItemオブジェクト が取得されます。
FiddlerでHTTP通信を監視すると、以下のようなHTTP通信が行われていることを確認することができます。

f:id:daigo-knowlbo:20170509031327p:plain

10回の /docs へのリクエストが行われています。
また、各リクエスト時の リクエストチャージ(x-ms-request-charge)は 38.1 でした。全部で38.1×10のRUを消費。

つまり、ライブラリを利用するコードとしては「query.ToList()」の1文が、10回のHTTPリクエストに分割された、ということです。

x-ms-max-item-countの影響

全1,000件のデータが10回のHTTPリクエストに分割された理由、それはDocumentClient.CreateDocumentQuery()メソッド呼び出し時のFeedOptions引数に影響します。
リスト1ではCreateDocumentQuery()の第2引数を指定しませんでしたが、CreateDocumentQuery()には第2引数にFeedOptionsオブジェクトを指定することができます。
FeedOptions.MaxItemCountの値が1リクエストで取得する項目の最大件数になります(DocumentDBのREST API呼び出しにおけるHTTPヘッダー「x-ms-max-item-count」値)。
また、FeedOptions.MaxItemCountのデフォルト値は「100」となっています。
つまり、これを省略したリスト1では「1リクエストで取得する最大件数=100」で動作を行いました。

10回のリクエストの途中のHTTPヘッダーを見ると以下のようになっています。

f:id:daigo-knowlbo:20170509030905p:plain

上記キャプチャでは、HTTPレスポンスに「x-ms-continuation: {“token”:“qd4DAJ67FQDIAAAAAAAAAA==”,“range”:{“min”:“”,“max”:“FF”}}」が記述されています。
つまり、HTTPリクエスト・レスポンス間での特定クエリーに対するページング処理が挟み込まれています(10件ずつのページング)。

FeedOptions.MaxItemCountを指定する

では、以下のリスト2のように FeedOptions.MaxItemCount= 1000 を指定してみます。

// リスト2
string EndpointUrl = "https://rddocdb.documents.azure.com:443/";
string PrimaryKey = "[プライマリキー]";

string DatabaseID = "ExampleDB1";
string CollectionID = "ExampleCollection1";

// 1度の最大取得項目数は1,000
var feedOption = new FeedOptions() { MaxItemCount = 1000 };

// DocumentClientオブジェクト初期化
DocumentClient client = new DocumentClient(new Uri(EndpointUrl), PrimaryKey);

// 全件取得
Uri documentCollectionUri = 
  UriFactory.CreateDocumentCollectionUri(DatabaseID, CollectionID);
var query = 
  client.CreateDocumentQuery<ProductItem>(documentCollectionUri, feedOption);
var result = query.ToList();

リスト2を実行すると、以下のように1度のHTTPリクエストで1,000件のProductItemオブジェクトを取得することができます。

f:id:daigo-knowlbo:20170509031939p:plain

ちなみにこの時の消費RUは 380.96 となりました。