Azure Cosmos DB入門(1)
本コンテンツは「Azure Cosmos DB入門」の(1)です。
1 Cosmos DBとは
Cosmos DBはAzureで提供されるデータベースサービスの1つです。データベースの種類としては、いわゆる「NoSQL」データベースとなります。
公式ドキュメントにおいては、まず以下のように説明されています。
「グローバル分散可能なマルチモデルデータベースサービス」
1.1 特徴
Cosmos DBの代表的な特徴は以下ような点があげられます。
- グローバル分散可能
- 柔軟なスケーリングが可能
- 超高速な応答速度
- マルチデータモデル
- 多様な一貫性モデル
- 高いレベルのSLA
上記項目について、それぞれ順番に見ていきましょう。
1.1.1 グローバル分散可能
Azureは世界30ヶ所以上のリージョンでサービスが提供されています。Cosmos DBは、これらのリージョンに対してレプリケーションが可能です。
つまり、世界中に分散してデータを保持することができます。
世界規模のサービスを提供する場合、データベース(Cosmos DB)とアプリケーション(例えばAppService)を各リージョンにレプリカ&配置することで、エンドユーザーが最寄りのリージョンに対してのアクセスでサービスを利用するようにすることができます。つまり、地理的に遠いリージョンへのアクセスによるネットワーク遅延を回避することが可能になります。勿論、そのうえでレプリケーションされたCosmos DBはバックエンドで自動的にリージョン間のデータ同期が行われます。また、特定リージョンのダウン時には自動的なフェールオーバーが行われます。
1.1.2 柔軟なスケーリングが可能
スループットのスケーリングもCosmos DBの大きな特徴になります。
クラウドサービスの特徴として自由自在にスケール調整が可能なことがあげられます。
Cosmos DBは、更に、オンライン状態のまま5秒以内にパフォーマンスのスケール変更が可能となっています。スケーリングの調整は、Azureポータル上からもプログラム上からも簡単に可能となります。
また、Cosmos DBにおけるスループットは「RU(Request Unit)」という単位になっており、これが課金対象の大きな要素となります。
ストレージ容量についてもペタバイト単位までスケール可能となっています。
そしてCosmos DBは無制限にスケーリングすることが可能です。
1.1.3 超高速な応答速度
Cosmos DBに限らず、AWSのDynamoDBにしても、それからオンプレ運用可能なApache CASSANDRAにしても、NoSQLデータベースの特徴として「超高速である」ということは周知の事実でしょう。
対比されるデータベースシステムとしてRDBがありますが、NoSQLでは「テーブル間リレーションという概念を持たない」「限られたトランザクション機能とする」等の機能実装上の方針の違いから「高速な動作」を手に入れています。
Cosmos DBでは、同一リージョン内における読み取り・書き込みのレイテンシーに対して以下の表に示すようなミリ秒単位の速度を保証しています。
Read(1KB) | Indexed Write(1KB) | |
---|---|---|
50% | < 2ms | < 6ms |
99% | < 10ms | < 15ms |
※「同一リージョン内」でのレイテンシーとは、つまり「Japan WestにApp Serviceをパブリッシュ&同じくJapan WestにCosmos DBを作成」というケースにおける App ServiceからCosmos DBへのデータアクセスにおけるレイテンシーを指します。
1.1.4 マルチデータモデル
これもCosmos DBの大きな特徴になります。
従来のNoSQLでは、CASSANDRAは「Key-Value」、MongoDBは「ドキュメント」・・・のようにデータベースプロダクト(サービス)と保持するデータモデル形式は固定化されていました。
しかし、Cosmos DBでは以下のような異なるデータモデルを保持することができます。
- 「ドキュメント」
- 「Graph」
- 「Key-Value」
またそれらのデータモデルに合わせて、アクセス用のAPIもそれぞれ用意されています。
1.1.5 多様な一貫性モデル
従来の一般的なNoSQLデータベースでは、データの一貫性に関して以下の2つのモデルを提供していました(AWS DynamoDBの該当)。
- 「Strong」 : 強固な一貫性を提供する(ただし速度性能を犠牲にする)
- 「Eventual」 : (一貫性を維持するためにタイムラグを要するが)最終的にデータの一貫性を提供する(速度性能重視)
Cosmos DBでは、上記に加えて「Bounded-staleness」「Session」「Consistent Prefix」という3つのモードが用意されています。(合計5つの一貫性モードが用意されています)
1.1.6 高いレベルのSLA
Cosmos DBでは以下について99.9%という非常に高いSLA保証を行っています。
- 可用性保証 99.9%
- スループット保証 99.9%
- レイテンシー保証 99.9%(99%が10ms未満のレイテンシーである事)
- 一貫性保証 読取り要求の100%が、要求された一貫性レベルの一貫性を保証
SLAの詳細は以下に公開されています。
https://azure.microsoft.com/en-us/support/legal/sla/cosmos-db/v1_0/
1.1.7 Cosmos DBは自分たちが普通に使うものか?
上記で紹介した6つの特徴から「Cosmos DBは"なんかすごい"」という事が感じ取れると思います。と同時に「世界規模の分散」や「無制限なスケーリング」などを必要とするような、例えばfacebookだったりtwitterだったりのようなシステムを自分たちは作りはしないし、これって使う必要あるの?という思いを持つ方も多いことでしょう。
しかし、Cosmos DBがレイテンシーに関して「読み込み <10ms、書き込み <15ms」の保証をしているデータベースシステムである、という点だけでも使う価値を見出せるのではないかと思います。
多くの既存の、そして現役の開発の現場(これは、特定企業内のBtoBなシステム、不特定多数の企業を対象としたBtoBのクラウドサービス、不特定多数の個人を対象としたBtoCのサービス、を含む)においても、RDBによるデータアクセスがボトルネックとなるケース、その為のパフォーマンスチューニングを追加で講じる必要に直面するケースは多いのではないでしょうか。
「RDBをCosmos DBにすべて置き換える」という選択肢もありますし、「RDBとCosmos DBのハイブリット運用」という選択肢もあるでしょう。
また、Cosmos DBはACIDなトランザクション処理をサポートしていますが、従来のRDBよりも制約があり設計上の工夫が必要である点があります。その様なことから、マスタデータをRDBで、読み取り効率を上げたい部分に関してはRDB → Cosmos DBへのデータ連携を走らせ、Cosmos DBからのリードパフォーマンスの優位性を享受する、ということも考えられます。
それから、データ要件によってはRDBのテーブル構造がマッチしないケースもあります。例えば私が所属する会社の製品にワークフロー(稟議書)を扱うものがあります。稟議書は「申請者から何人もの承認者を経るパス」を持ちます。パスは単純なケースもあれば、稟議書の内容によって複雑な分岐をする場合もあります。下図のようなパス(フロー)をデータベースに保持する場合、テーブル構造よりもグラフ構造で保存した方が自然な形で保存することができますし、速度も圧倒的に早いでしょう。
※私自身もNoSQLをガンガンシステムに適用しているわけではないので、この辺りは業界全体としても経験を蓄積し、並行してさらなる技術の進歩が起こりながらベストプラクティスを求めていくことになると思います。
1.2 Hello Cosmos DB
ではHello Worldならぬ、Hello Cosmos DBとして、Cosmos DBを利用するファーストステップを行ってみようと思います。
1.2.1 AzureポータルでCosmos DBを作成
ブラウザで「https://portal.azure.com」を開きます。
サイドメニューから「新規」を選択します。
フィルターに「Azure Cosmos DB」と入力します。
「Azure Cosmos DB」を選択します。
「作成」ボタンをクリックします。
任意の「ID」(ここでは cosmosdb)を入力します。
「API」は「SQL(DocumentDB)」を選択します。
「リソースグループ」の任意の値(ここでは cosmosdb)を入力します。
以上で「cosmosdb」というデータベースアカウントのCosmos DB(DocumentDBデータモデル)が作成されました。
1.2.2 Azureポータルでデータを操作
実際の運用アプリケーションでは、データの作成・検索・更新等の操作はプログラムから行われますが、ここではまず、Azureポータル上からCosmos DBのデータ操作を行いその雰囲気を感じ取ります。
(1) データの入れ物の準備(データベースとコレクション)
作成したCosmos DBをAzureポータルで表示した画面が以下です。
「Data Explorer(Preview)」を選択すると、以下の画面が表示されます。
SQL ServerのManagement Studioのようなノリでデータ操作を行うことができます。
「New Collection」をクリックすると以下の画面が表示されます。
このデータベースアカウント「cosmosdb」に対して、「データベース」→「コレクション」を作成します。コレクションは「データ項目」を入れる入れ物になります(詳細は後述)。
ここでは以下の内容にてデータベースとコレクションを作成します。
* Database id = ExampleDb1
* Collection id = ExampleCol1
* Storage capacity = Fixed(10GB)
* Throughput = 400
* Partition key = なし
データを格納する入れ物が完成しました(コレクションを作成すると「Throughput = 400」というパフォーマンスを提供するためのリソースがAzure上に確保された状態となります。そ の為、課金の対象となりますので、作成→放置→課金継続・・・にはご注意ください)。
(2) データ(ドキュメント)の作成
データベース・コレクションの作成を行った後の「Data Explorer(Preview)」の表示は以下の通りです。
左側のツリーから「Documents」を選択し、「New Document」を選択します。
JSONエディタが表示されるので、適当にデータを編集してSaveボタンをクリックします。
※本チュートリアルでは、Cosmos DBを作成する際に、APIとして「SQL(DocumentDB)」を選択したためデータモデルが「ドキュメント(つまりJSONデータモデル)」となっています。
作成されたデータは以下の通りです。自ら編集した物以外の項目「rid」「self」「etag」「attachments」「_ts」が付加されていますが、これらはCosmos DBが内部的に自動で付加したものです。
この後の上記手順を繰り返し、適当なデータ5件を作成しました。
作成したデータ一覧 [ { "id": "1", "FirstName": "Ryuichi", "LastName": "Daigo", "FavoriteDB": "Cosmos DB", "Age": 17, "_rid": "2cRoAIH5HAACAAAAAAAAAA==", "_self": "dbs/2cRoAA==/colls/2cRoAIH5HAA=/docs/2cRoAIH5HAACAAAAAAAAAA==/", "_etag": "\"0f00fff9-0000-0000-0000-591fcf700000\"", "_attachments": "attachments/", "_ts": 1495256943 }, { "id": "2", "FirstName": "Hiroaki", "LastName": "Sakamoto", "FavoriteDB": "SQL Server", "Age": 25, "_rid": "2cRoAIH5HAADAAAAAAAAAA==", "_self": "dbs/2cRoAA==/colls/2cRoAIH5HAA=/docs/2cRoAIH5HAADAAAAAAAAAA==/", "_etag": "\"0f0006fa-0000-0000-0000-591fd0400000\"", "_attachments": "attachments/", "_ts": 1495257151 }, { "id": "3", "FirstName": "Susumu", "LastName": "Akiyama", "FavoriteDB": "Neo4j", "Age": 36, "_rid": "2cRoAIH5HAAEAAAAAAAAAA==", "_self": "dbs/2cRoAA==/colls/2cRoAIH5HAA=/docs/2cRoAIH5HAAEAAAAAAAAAA==/", "_etag": "\"0f0007fa-0000-0000-0000-591fd05b0000\"", "_attachments": "attachments/", "_ts": 1495257178 }, { "id": "4", "FirstName": "Hiroko", "LastName": "Takada", "FavoriteDB": "Oracle", "Age": 32, "_rid": "2cRoAIH5HAAFAAAAAAAAAA==", "_self": "dbs/2cRoAA==/colls/2cRoAIH5HAA=/docs/2cRoAIH5HAAFAAAAAAAAAA==/", "_etag": "\"0f0008fa-0000-0000-0000-591fd07d0000\"", "_attachments": "attachments/", "_ts": 1495257212 }, { "id": "5", "FirstName": "Yoko", "LastName": "Kitahara", "FavoriteDB": "Mongo DB", "Age": 18, "_rid": "2cRoAIH5HAAGAAAAAAAAAA==", "_self": "dbs/2cRoAA==/colls/2cRoAIH5HAA=/docs/2cRoAIH5HAAGAAAAAAAAAA==/", "_etag": "\"0f000efa-0000-0000-0000-591fd0aa0000\"", "_attachments": "attachments/", "_ts": 1495257257 } ]
(3) データの検索
引き続き「Data Explorer(Preview)」を使用します。
「Edit Filter」ボタンをクリックします。
フィルター入力テキストボックスが有効になるので、以下のように抽出条件を設定して「Apply Filter」ボタンをクリックします。抽出条件はSQLライクに記述可能です。
where c.Age > 20
条件に該当する3件に絞り込みが行われました。
同様の機能を持ったツールとして「ドキュメント エクスプローラー」「クエリ エクスプローラー」というものがAzureポータル上で提供されています。
(4) 後片付け
コレクションを作成したままにしておくと課金対象となってしまうので削除しておきましょう。
きれいにコレクションの上のデータベースごと削除することとします。
「Data Explorer(Preview)」でデータベース(ExampleDb1)の右側の「・・・」をクリックするとポップアップメニューが表示されます。「Delete Database」を選択します。
確認画面が表示されるので、削除対象のデータベース名を入力して「OK」ボタンをクリックします。
1.2.3 プログラムでデータを操作
次に、前述の「Azureポータルでデータを操作」で行った操作をC#プログラムから行います。
(1) 接続情報の確認
Azureポータルで「キー」を選択します。「読み取り/書き込みキー」タブを選択します。
「URI」と「プライマリキー」を控えておきましょう。
(2) Visual Studio 2017プロジェクトを作成
メニュー「ファイル→新規作成→プロジェクト」を選択し、表示された「新しいプロジェクト」に以下の内容を入力します。
- プロジェクトテンプレート=「Windows クラシックデスクトップ→コンソールアプリ(.NET Framework)」
- 名前=HelloCosmosDb
(3) CosmosDB接続ライブラリ(NuGetパッケージ)を追加
ソリューションエクスプローラからHelloCosmosDbプロジェクトをマウス右ボタンクリックして、表示されたポップアップメニューから「NuGet パッケージの管理」をクリックします。
表示されたNuGetパッケージ管理画面の参照タブで「Microsoft.Azure.DocumentDB」を選択して「インストール」ボタンをクリックします。
「ライセンスへの同意」ダイアログが表示されますが、そのまま「同意する」をクリックします。
(4) データの入れ物の準備(データベースとコレクション)
話を単純にする為に、コンソールアプリケーションのProgram.csにコードを追記していくこととします。
また、以下、各処理のコードスニペットを紹介し、最後に全体の実装を掲載する事とします。
Cosmos DB関連の名前空間を適時usingします。
using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client;
接続情報と作成するデータベース名・コレクション名を定数定義しておきます。
private const string EndpointUrl = "https://cosmosdb.documents.azure.com:443/"; private const string PrimaryKey = "キー"; private const string DatabaseId = "ExampleDb1"; private const string CollectionId = "ExampleCol1";
データベースとコレクションを作成するコードは以下の通りです。
private DocumentClient client; ... public async Task<bool> CreateDatabaseCollection() { // 接続用クライアントオブジェクトの作成 this.client = new DocumentClient( new Uri(EndpointUrl), PrimaryKey); // データベースの作成 await this.client.CreateDatabaseIfNotExistsAsync( new Database { Id = DatabaseId }); // コレクションの作成 var collection = new DocumentCollection { Id = CollectionId }; await this.client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DatabaseId), collection); return true; }
まずは、接続クライアントオブジェクト「DocumentClient」を作成します。作成したDocumentClientオブジェクトは、後で別の処理でも流用することを考慮し、clientフィールド変数に保持することにしました。
DocumentClientは接続オブジェクトであるため、エンドポイントおよび接続用キーをコンストラクタで渡すことになります。
データベースの作成には「CreateDatabaseIfNotExistsAsync()メソッド」を呼び出します。引数でデータベースIDを指定します。メソッド名から想像がつくように「存在しなかったら作成、存在したらそのまま処理が正常終了」します。
コレクションの作成には「CreateDocumentCollectionIfNotExistsAsync()メソッド」を呼び出します。第1引数に対して「UriFactory.CreateDatabaseUri()メソッド」呼び出しの戻り値を渡しています。第2引数のDocumentCollectionオブジェクトによりコレクションIDを指定しています。
(5) データ(ドキュメント)の作成
前述と同様に以下のようなスキーマのデータ(ドキュメント)を作成します。
{ "id": "1", "FirstName": "Ryuichi", "LastName": "Daigo", "FavoriteDB": "Cosmos DB", "Age": 17, }
上記スキーマに合致したモデルクラス Person を用意します。
namespace HelloCosmosDb { public class Person { public string Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string FavoriteDB { get; set; } public int Age { get; set; } }
Personオブジェクトを5件の固定データで作成する実装は以下の通りです。
DocumentClientオブジェクト(this.client)の「UpsertDocumentAsync()」メソッドを呼び出します。Upsertの名前から分かるように「キーが同じドキュメントが既に存在すれば更新を、存在しなければ作成」処理が行われます。
第1引数は、どのコレクションに対してドキュメントを作成するかを表します。
第2引数は、作成するオブジェクトそのものを指定します。
public async Task<bool> CreateDocuments() { string[] id = { "1", "2", "3", "4", "5" }; string[] firstName = { "Ryuichi", "Hiroaki", "Susumu", "Hiroko", "Yoko" }; string[] lastName = { "Daigo", "Sakamoto", "Akiyama", "Takada", "Kitahara" }; string[] favoriteDB = { "Cosmos DB", "SQL Server", "Neo4J", "Oracle", "MongoDB" }; int[] age = { 17, 25, 36, 32, 18 }; for(int i=0; i<5; i++) { var person = new Person() { Id =id[i], FirstName=firstName[i], LastName=lastName[i], FavoriteDB = favoriteDB[i], Age = age[i] }; var newDocument = await this.client.UpsertDocumentAsync( UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), person); } return true; }
(6) データの検索
ここまでで、cosmosdbデータベースアカウント → ExampleDb1データベース → ExampleCol1 上に5件のドキュメントが作成された状態です。
Ageが20より多きドキュメントを検索してコンソール出力します。
public void SearchDocuments() { Uri collectionUrl = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); var query = this.client.CreateDocumentQuery<Person>(collectionUrl) .Where(c => c.Age > 20); foreach (var person in query) { Console.WriteLine(person); Console.WriteLine("-----"); } }
DocumentClientオブジェクト(this.client)の「CreateDocumentQuery()」メソッドでクエリーオブジェクトを作成することができます。Linqメソッドk構文でWhere句を記述することができます。
(7) ソース全体と実行結果
上記に説明した実装およびコードスニペットをまとめた実装の全体コードは以下になります。
//Person.cs using System; namespace HelloCosmosDb { public class Person { public string Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string FavoriteDB { get; set; } public int Age { get; set; } public override string ToString() { return string.Format( "ID:{0}\r\nFirstName:{1}\r\nLastName:{2}\r\nFavoriteDB:{3}\r\nAge:{4}", this.Id, this.FirstName, this.LastName, this.FavoriteDB, this.Age); } } }
// Program.cs using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; namespace HelloCosmosDb { class Program { private const string EndpointUrl = "https://cosmosdb.documents.azure.com:443/"; private const string PrimaryKey = "キー"; private const string DatabaseId = "ExampleDb1"; private const string CollectionId = "ExampleCol1"; private DocumentClient client; static void Main(string[] args) { Console.WriteLine(UriFactory.CreateDatabaseUri(DatabaseId)); var program = new Program(); program.CreateDatabaseCollection().Wait(); program.CreateDocuments().Wait(); program.SearchDocuments(); } public async Task<bool> CreateDatabaseCollection() { // 接続用クライアントオブジェクトの作成 this.client = new DocumentClient( new Uri(EndpointUrl), PrimaryKey); // データベースの作成 await this.client.CreateDatabaseIfNotExistsAsync( new Database { Id = DatabaseId }); // コレクションの作成 var collection = new DocumentCollection { Id = CollectionId }; await this.client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DatabaseId), collection); return true; } public async Task<bool> CreateDocuments() { string[] id = { "1", "2", "3", "4", "5" }; string[] firstName = { "Ryuichi", "Hiroaki", "Susumu", "Hiroko", "Yoko" }; string[] lastName = { "Daigo", "Sakamoto", "Akiyama", "Takada", "Kitahara" }; string[] favoriteDB = { "Cosmos DB", "SQL Server", "Neo4J", "Oracle", "MongoDB" }; int[] age = { 17, 25, 36, 32, 18 }; for(int i=0; i<5; i++) { var person = new Person() { Id =id[i], FirstName=firstName[i], LastName=lastName[i], FavoriteDB = favoriteDB[i], Age = age[i] }; var newDocument = await this.client.UpsertDocumentAsync( UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), person); } return true; } public void SearchDocuments() { Uri collectionUrl = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId); var query = this.client.CreateDocumentQuery<Person>(collectionUrl) .Where(c => c.Age > 20); foreach (var person in query) { Console.WriteLine(person); Console.WriteLine("-----"); } } } }
実行結果は以下の通りです。
// 実行結果 ID:600bb1b7-fdfc-479c-b295-8fc3f589b45a FirstName:Hiroaki LastName:Sakamoto FavoriteDB:SQL Server Age:25 ----- ID:a2b5fd96-de4f-4283-bd10-78bfd5056505 FirstName:Susumu LastName:Akiyama FavoriteDB:Neo4J Age:36 ----- ID:82e9770d-7a9f-4b0e-adbb-849051912154 FirstName:Hiroko LastName:Takada FavoriteDB:Oracle Age:32 ----- 続行するには何かキーを押してください . . .
Azure Cosmos DB入門(目次)
「Azure Cosmos DB入門」目次
DocumentDB が Azure Cosmos DB としてリニューアルされたので、改めてこのサービスの全体像を整理したコンテンツ(ブログ)を「Cosmos DB入門」として書こうと思います。
目次は以下の通り。順次コンテンツを追加していく予定です。
※ 2017/07/09に(1)~(8)のコンテンツが揃いました。今後もCosmos DBのトピックをブログに継続してエントリーしていきます。
- Azure CosmosDB入門(1)
- 1 Cosmos DBとは
- 1.1 特徴
- 1.1.1 グローバル分散可能
- 1.1.2 柔軟なスケーリングが可能
- 1.1.3 超高速な応答速度
- 1.1.4 マルチデータモデル
- 1.1.5 多様な一貫性モデル
- 1.1.6 高いレベルのSLA
- 1.1.7 Cosmos DBは自分たちが普通に使うものか?
- 1.2 Hello Cosmos DB
- 1.2.1 AzureポータルでCosmos DBを作成
- 1.2.2 Azureポータルでデータを操作
- 1.2.3 プログラムでデータを操作
- 1.1 特徴
- 1 Cosmos DBとは
- Azure CosmosDB入門(2)
- 2 Cosmos DBの主要概念
- 2.1 データモデルとアクセスAPI
- 2.1.1 atom-record-sequence(ARS)
- 2.1.2 AzureポータルでCosmos DBを作成する
- 2.2 各種データモデル
- 2.2.1 「ドキュメント」モデル
- 2.2.2 「グラフ」 モデル(2017/5現在Preview版)
- 2.2.3 「テーブル」モデル(2017/5現在Preview版)
- 2.3 パーティショニング
- 2.4 RU(Request Unit)
- 2.4.1 1RUとは?
- 2.4.2 RUはコレクションに割り当てる
- 2.4.3 RUという概念のすばらしさ
- 2.4.4 予約RUの設定方法
- 2.4.5 消費RUの確認方法
- 2.4.6 RUの話は深い・・・(後半に続く)
- 2.1 データモデルとアクセスAPI
- 2 Cosmos DBの主要概念
- Azure CosmosDB入門(3)
- 3 Cosmos DBプログラミング ~ DocumentDB編(前編)
- 3.1 まず、はじめに
- 3.2 準備
- 3.3 ドキュメントの作成・更新
- 3.4 ドキュメントの検索
- 3.5 ドキュメントの削除
- 3.6 つづく・・・
- 3.7 資料
- 3 Cosmos DBプログラミング ~ DocumentDB編(前編)
- Azure CosmosDB入門(4)
- Azure CosmosDB入門(5)
- 5 Cosmos DBプログラミング ~ MongoDB編
- 5.1 はじめに
- 5.1.1 Cosmos DB(MongoDBデータモデル)とMongoDB
- 5.2 準備
- 5.2.1 Cosmos DB(MongoDB)データベースアカウントの作成
- 5.2.2 Visual Studio 2017プロジェクトの作成
- 5.3 データベースとコレクションの作成
- 5.3.1 Azureポータルで作成
- 5.3.2 プログラムで作成
- 5.3.3 mongoコマンドで作成
- 5.4 ドキュメントの作成
- 5.4.1 ドキュメントの一括投入 ~ Studio 3T for MongoDB(旧Mongo chef)
- 5.5 ドキュメントの検索
- 5.5.1 Mongoクエリー言語による検索
- 5.5.2 LINQによる検索
- 5.6 ドキュメントの更新
- 5.7 ドキュメントの削除
- 5.8 資料
- 5.9 つづく・・・
- 5.1 はじめに
- 5 Cosmos DBプログラミング ~ MongoDB編
- Azure CosmosDB入門(6)
- 6 Cosmos DBプログラミング ~ Gremlin編
- 6.1 はじめに
- 6.1.1 グラフ データモデルとは
- 6.1.2 Gremlinとは
- 6.2 準備
- 6.2.1 Cosmos DB(Gremlin)データベースアカウントの作成
- 6.2.2 Visual Studio 2017プロジェクトの作成
- 6.3 データベースとコレクションの作成
- 6.3.1 プログラムで作成
- 6.4 グラフデータの操作
- 6.4.1 Vertexの追加
- 6.4.2 Edgeの追加
- 6.4.3 Vertexの検索
- 6.4.4 Edgeの検索
- 6.4.5 Vertex~Edge~Vertexを辿る(1)
- 6.4.6 Vertex~Edge~Vertexを辿る(2)
- 6.5 まとめ
- 6.6 資料
- 6.1 はじめに
- 6 Cosmos DBプログラミング ~ Gremlin編
- Azure CosmosDB入門(7)
- 7 Cosmos DBプログラミング ~ Table編
- 7.1 はじめに
- Cosmos DB(Table) と Azure Table Storage
- アクセス用クラスライブラリ
- 7.2 Cosmos DB(Table)
- 7.2.1 データベース構造
- 7.2.2 Cosmos DB固有情報の設定
- 7.3 準備
- 7.3.1 Cosmos DB(Table)データベースアカウントの作成
- 7.3.2 Visual Studio 2017プロジェクトの作成
- 7.4 テーブルの作成
- 7.4.1 Azureポータルでテーブルを作成
- 7.4.2 コードでテーブルを作成
- 7.5 テーブルの操作
- 7.5.1 エンティティ型の定義
- 7.5.2 エンティティの追加
- 7.5.3 エンティティの一括追加
- 7.5.4 エンティティの検索
- 7.5.5 エンティティの更新
- 7.5.6 エンティティの削除
- 7.6 まとめ
- 7.7 資料
- 7.1 はじめに
- 7 Cosmos DBプログラミング ~ Table編
- Azure CosmosDB入門(8)
- 8 Cosmos DBをもっと知りたい
- 8.1 一貫性レベル(Consistency Level)
- 8.1.1 Cosmos DBの提供する一貫性レベル(Consistency Level)
- 8.1.2 既定の一貫性レベル
- 8.2 RU(Request Unit)詳細
- 8.2.1 RUの設定方法
- 8.2.2 消費RUの確認方法
- 8.2.3 RU/s超過時のレスポンスコード429
- 8.2.4 リトライ動作の指定
- 8.2.5 一貫性レベル(Consistency Level)による消費RUの違い
- 8.2.6 RU/m (RU/分) の設定
- 8.3 グローバルレプリケーション
- 8.4 障害復旧およびフェールオーバー
- 8.4.1 自動フェールオーバー
- 8.4.2 手動フェールオーバー
- 8.5 まとめ
※2017/07/09 目次更新
- 8.1 一貫性レベル(Consistency Level)
- 8 Cosmos DBをもっと知りたい
はじめに
先日 2017/5/10-12 に開催された Build 2017 において多数の新しい技術の発表がなされました。
その中の1つとして Azure Cosmos DB も発表されました。
Azure Cosmos DBは従来 DocumentDB として公開されていたサービスに置き換わるものです。厳密には「Cosmos DB が DocumentDB の機能を内包している」という関係性を持ちます。
Azure Cosmos DBは、2010年に「Project Florence」として開始されました。大規模アプリケーション開発の課題に対処するために、マイクロソフト社内向けに開発されていたサービスです。しかし、グローバルに分散されたアプリケーションの構築は、マイクロソフト以外の課題でもあるとの認識から2015年に「Azure DocumentDB」としてサービスリリースされました。
DocumentDBに多数の機能を追加し、そして2017/5/10に Azure Cosmos DB としてサービスリリースが行われました。
※これまでのDocumentDB情報
Cosmos DBは、従来のDocumentDBの知識やコードはそのまま利用可能です。大幅な機能追加によるリニューアルであり、従来のDocumentDBの機能や仕様がひっくり返されている部分はありません。
つまり、ネット上に公開されているDocumentDBの情報は(完全にマッチしないまでも)引き続きCosmos DBにおいて有効な情報です。
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通信が行われていることを確認することができます。
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ヘッダーを見ると以下のようになっています。
上記キャプチャでは、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オブジェクトを取得することができます。
ちなみにこの時の消費RUは 380.96 となりました。
DocumentDB でページング処理を行う
「データを一覧してページング表示する」という要件は エンプラ系でも コンシューマー系でも 常にある要件です。
ということで、DocumentDBでデータのページング取得を行う方法を書いておこうと思います。
1. 前提
DocumentDBアカウントには、既に以下のデータが保存されている状態を前提とします。
データベース名: ExampleDB1
コレクション名: ExampleCollection1
ドキュメント: ProductItemドキュメントが1,000件保存されている
ProductItemは以下のようなデータクラスです。
// 保存するドキュメント using Newtonsoft.Json; namespace PagingExample { 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; } } }
以下のような感じで、適当なデータが入っています。
2. DocumentDB操作管理クラスを用意
DocumentDBを操作する管理クラス(DocumentManager)の実装は以下の通りです。
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Linq; using Microsoft.Azure.Documents.Client; using System.IO; using System.Threading.Tasks; namespace PagingExample { public class DocumentManager { private const string EndpointUrl = "https://rddocdb.documents.azure.com:443/"; private const string PrimaryKey = "[マスターキー]"; private const string DatabaseID = "ExampleDB1"; private const string CollectionID = "ExampleCollection1"; private DocumentClient client; // DocumentClientオブジェクトを初期化 public async Task<bool> InitializeDocumentClient() { this.client = new DocumentClient(new Uri(DocumentManager.EndpointUrl), DocumentManager.PrimaryKey, new ConnectionPolicy { ConnectionMode = ConnectionMode.Gateway, ConnectionProtocol = Protocol.Https }); return true; } // ProductItemを 10件 ずつ取得 public async Task<(List<ProductItem>, string)> QueryProductItems(string continuration) { List<ProductItem> result = null; var feedOption = new FeedOptions() { MaxItemCount = 10, RequestContinuation = continuration }; Uri documentCollectionUri = UriFactory.CreateDocumentCollectionUri( DocumentManager.DatabaseID, DocumentManager.CollectionID); var query = this.client.CreateDocumentQuery<ProductItem>( documentCollectionUri, feedOption) .Where( p => p.Price > 700 ) .AsDocumentQuery(); var ret = await query.ExecuteNextAsync<ProductItem>(); result = ret.ToList(); string retContinuration = ret.ResponseContinuation; return (result, retContinuration); } } }
InitializeDocumentClient()メソッド
DocumentClientオブジェクトを new してメンバーフィールドに保持します。
QueryProductItems()メソッド
引数 continuration は、DocumentDBに「どのページ(というかどの位置から継続抽出するか)」の状態文字列です。1ページ目の取得時には「空文字列」を設定します。
CreateDocumentQuery()を呼び出す際の引数「FeedOptions」で、「1度に取得する件数(MaxItemCount)」と「どのページ(どこから継続取得するかを表す状態文字列(RequestContinuation )」を指定します。
AsDocumentQuery()呼び出して、ExecuteNextAsync()呼び出した結果から、10件の抽出データ(ret.ToList()) と
次のページを読み込む為の継続取得状態文字列(ret.ResponseContinuation)が得られます。
これら2つのデータをタプルで戻り値として呼び出し元に返却しています。
また、サンプルなので Price > 700 という固定的なWhere条件を付加しています。
3. DocumentManagerを呼び出してページングデータ取得
では、前述で用意した DocumentManager を利用してProductItemデータを10件ずつページング取得したいと思います。
以下が実装になります。
var manager = new DocumentManager(); var initResult = await manager.InitializeDocumentClient(); // コンソール出力用ページ番号 int pageNo = 0; // DocumentDBに投げるcontinuration値(1ページ目は空文字列) string continuration = ""; do { (List<ProductItem> items, string continuration) queryResult = await manager.QueryProductItems(continuration); // 次のページを取得するためのcontinuration文字列取得 continuration = queryResult.continuration; // 取得したデータをコンソール出力 Console.WriteLine(string.Format("page {0}", pageNo)); Console.WriteLine(string.Format("continuration {0}", continuration)); foreach (var item in queryResult.items) { Console.WriteLine(string.Format("{0} {1}", item.Name, item.Price)); } Console.WriteLine("-----"); pageNo++; } while (!string.IsNullOrEmpty(continuration)); // continurationが空だと終端ページに達したということ
データ抽出で得られた(Responseされた)Continuation値を、次のデータ抽出(Request)で利用する形です。
4. 実行結果
実行結果は以下の通りです。
page 0 continuration {"token":"+RID:mi01AOyHIgA7AAAAAAAAAA==#RT:1#TRC:10#FPC:AgEAAAB8AD8AABhwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABLcAHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAE=","range":{"min":"","max":"FF"}} Product_8 800 Product_9 900 Product_18 800 Product_19 900 Product_28 800 Product_29 900 Product_38 800 Product_39 900 Product_48 800 Product_49 900 ----- page 1 continuration {"token":"+RID:mi01AOyHIgBtAAAAAAAAAA==#RT:2#TRC:20#FPC:AgEAAAB2AG8AAGDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHKsAccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAE=","range":{"min":"","max":"FF"}} Product_58 800 Product_59 900 Product_68 800 Product_69 900 Product_78 800 Product_79 900 Product_88 800 Product_89 900 Product_98 800 Product_99 900 ----- page 2 continuration {"token":"+RID:mi01AOyHIgCfAAAAAAAAAA==#RT:3#TRC:30#FPC:AgEAAABuAJ+ANcABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMAB","range":{"min":"","max":"FF"}} Product_108 800 Product_109 900 Product_118 800 Product_119 900 Product_128 800 Product_129 900 Product_138 800 Product_139 900 Product_148 800 Product_149 900 ----- page 3 continuration {"token":"+RID:mi01AOyHIgDRAAAAAAAAAA==#RT:4#TRC:40#FPC:AgEAAABoAN8ABhxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABI8AHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMAB","range":{"min":"","max":"FF"}} Product_158 800 Product_159 900 Product_168 800 Product_169 900 Product_178 800 Product_179 900 Product_188 800 Product_189 900 Product_198 800 Product_199 900 ----- page 4 continuration {"token":"+RID:mi01AOyHIgADAQAAAAAAAA==#RT:5#TRC:50#FPC:AgEAAABiAA8BGHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHIMAccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMABBxxwwAEHHHDAAQcccMAB","range":{"min":"","max":"FF"}} Product_208 800 Product_209 900 Product_218 800 Product_219 900 Product_228 800 Product_229 900 Product_238 800 Product_239 900 Product_248 800 Product_249 900 ----- ...省略... ----- page 18 continuration {"token":"+RID:mi01AOyHIgC-AwAAAAAAAA==#RT:19#TRC:190#FPC:AgEAAAAKAL+DA8ABBxxwwAE=","range":{"min":"","max":"FF"}} Product_908 800 Product_909 900 Product_918 800 Product_919 900 Product_928 800 Product_929 900 Product_938 800 Product_939 900 Product_948 800 Product_949 900 ----- page 19 continuration Product_958 800 Product_959 900 Product_968 800 Product_969 900 Product_978 800 Product_979 900 Product_988 800 Product_989 900 Product_998 800 Product_999 900 ----- 続行するには何かキーを押してください . . .
5. HTTP通信内容
DocumentDBライブラリにより、裏側で具体的にどのようなREST API リクエストとレスポンスが行われたのかをFiddlerで確認します。
上記Fiddler画面から以下のことが分かります。
- 「/dbs/ExampleDB1/colls/ExampleCollection1/docs」へのページごとのリクエストが行われた
- FeedOptions.MaxItemCount / FeedOptions.RequestContinuation の値がHTTPリクエストヘッダでそれぞれ「x-ms-max-item-count」「x-ms-continuation」して指定されている
- HTTPレスポンスヘッダ「x-ms-continuation」で、次のページを読み込むための継続状態文字列が返されている
資料
公式ドキュメントでは以下を見ればいいのかな。
Common DocumentDB REST request headers | Microsoft Docs
Common Azure Cosmos DB REST response headers | Microsoft Docs
DocumentDB のインデックス設定によるRUの変動について
DocumentDBのインデックスについて調べてのでブログに記しておこうと思います。
DocumentDBではデフォルト設定でも結構いい感じに動くようにインデックス設定が行われているのですが、カスタマイズ可能であり、これを最適化するとRUや(インデックス用)データサイズの節約につながります。
※RU = RequestUnit。DocumentDBにおける「単位時間(秒)あたりの処理数の抽象単位」。課金に大きく影響する数値です。
1. デフォルトのインデックス設定
DocumentDBでは、インデックスはコレクションに対して設定可能です。
またコレクションを作成すると、デフォルトでは、以下のようなインデックスポリシーが設定されます。
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*", "indexes": [ { "kind": "Range", "dataType": "Number", "precision": -1 }, { "kind": "Hash", "dataType": "String", "precision": 3 } ] } ], "excludedPaths": [] }
これは、Azureポータル上で「対象DocumentDBアカウント → DataExploere → 対象データベース → 対象コレクション → Scale & Settings」で確認することができます。
「includedPaths」はインデックス適用対象のパス、「excludedPaths」はインデックス所帯対象のパス、という意味になります。
「path: “/*"」とは、「格納されたJSONデータ要素のすべて(配下の階層を含めてすべて)に対して」という意味を持ちます。
そして ”/*" に対して、「Range」「Hash」の2種類のインデックスが設定されています。
それぞれのインデックス種類の意味は、Azure Cosmos DB indexing policies | Microsoft Docsに記述されており、以下が対象箇所の引用になります。
Hash:
Hash を /prop/? (または/) に使用して、以下のクエリを効率的に処理することができます。
SELECT FROM collection c WHERE c.prop = “value”
Hash を /props/[]/? (または / または /props/) に使用して、以下のクエリを効率的に処理することができます。
SELECT tag FROM collection c JOIN tag IN c.props WHERE tag = 5Range:
Range を /prop/? (または/) に使用して、以下のクエリを効率的に処理することができます。
SELECT FROM collection c WHERE c.prop = “value”
SELECT FROM collection c WHERE c.prop > 5
SELECT FROM collection c ORDER BY c.prop
インデックスの種類にはもう1つ「Spatial」があります。これはGEOクエリーを行う為のインデックスとなります。
つまり、コレクションに対するデフォルトのインデックス設定は、一般的な「文字列比較」や「数値比較」による抽出、また並べ替えのためのインデックスが、すべてのドキュメント要素に適用されることになります。
2. 下準備
さて、本投稿では「インデックスの最適化によるRUの変動」を調べるのですが、そのためのデータを準備したいと思います。
以下のPhotoItemクラスをコレクションに保存するドキュメントエンティティとします。
public class PhotoItem { [JsonProperty(PropertyName = "id")] public string Id { get; set; } // タイトル public string Title { get; set; } // 「いいね」数 public int LikeCount { get; set; } // 写真のURI public string PhotoUri { get; set; } // カテゴリ public string Category { get; set; } }
データベース・コレクションは以下のDocumentManagerクラスのInitializeDocumentClient()メソッドで作成します。
データベース名は ExampleDB1、コレクション名は ExampleCollection1、パーティションキーとして Category を設定します。
CreateExampleDocument(PhotoItem photoItem)メソッドは、PhotoItemドキュメントを作成します。
public class DocumentManager { private const string EndpointUrl = "https://rddocdb.documents.azure.com:443/"; private const string PrimaryKey = "[プライマリキー]"; private const string DatabaseID = "ExampleDB1"; private const string CollectionID = "ExampleCollection1"; private DocumentClient client; // データベースとコレクションを作成 public async Task<bool> InitializeDocumentClient() { this.client = new DocumentClient(new Uri(DocumentManager.EndpointUrl), DocumentManager.PrimaryKey, new ConnectionPolicy { ConnectionMode = ConnectionMode.Gateway, ConnectionProtocol = Protocol.Https }); // ExampleDB1 データベースを作成 await this.client.CreateDatabaseIfNotExistsAsync( new Database { Id = DocumentManager.DatabaseID }); // ExampleCollection1 コレクションを作成(パーティションキーはCategory) Collection<string> partitionPaths = new Collection<string>(); partitionPaths.Add("/Category"); var collection = new DocumentCollection { Id = DocumentManager.CollectionID, PartitionKey = new PartitionKeyDefinition() { Paths = partitionPaths } }; collection = await this.client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DocumentManager.DatabaseID), collection); return true; } // ドキュメントを作成 public async Task<Document> CreateExampleDocument(PhotoItem photoItem) { // ドキュメント作成 var doc = await this.client.CreateDocumentAsync( UriFactory.CreateDocumentCollectionUri(DocumentManager.DatabaseID, DocumentManager.CollectionID), photoItem); return doc; } ...省略 }
以下のコードで DocumentManagerクラス を呼び出してデータベース・コレクション・10,000件のドキュメントを作成します。
// DocumentManagerクラスを呼び出してデータベースを初期化 Random random = new Random(); var manager = new DocumentManager(); var initResult = await manager.InitializeDocumentClient(); string[] categories = { "sky", "sea", "mountain", "people" }; for (int i = 0; i < 10000; i++) { PhotoItem item = new PhotoItem() { Id = i.ToString(), Title = string.Format("SampleTitle{0}", i.ToString()), LikeCount = random.Next(1000), PhotoUri = string.Format("https://xxxxxxx/{0}", i.ToString()), Category = categories[i%4] }; var ret = await manager.CreateExampleDocument(item); System.Threading.Thread.Sleep(50); // 429しないように緩やかに追加する Console.WriteLine(i); }
3. デフォルト状態での書き込みと読み込み
デフォルトのインデックス状態で書き込みと読み込みを確認してみます。
書き込み
10件のドキュメントを書き込んでみます。
Random random = new Random(); var manager = new DocumentManager(); var initResult = await manager.InitializeDocumentClient(); { string[] categories = { "sky", "sea", "mountain", "people" }; for (int id = 10000; id < 10010; id++) { PhotoItem item = new PhotoItem() { Id = id.ToString(), Title = string.Format("SampleTitle{0}", id.ToString()), LikeCount = random.Next(1000), PhotoUri = string.Format("https://xxxxxxx/{0}", id.ToString()), Category = categories[id % 4] }; var ret = await manager.CreateExampleDocument(item); } }
上記ソースにより送信されたHTTPリクエストは以下のようになります(ここでは1件のみ抽出。続けて実施した10件の書き込みのRUは同一であった為)。
POST https://rddocdb-japaneast.documents.azure.com/dbs/ExampleDB1/colls/ExampleCollection1/docs HTTP/1.1 x-ms-documentdb-partitionkey: ["sea"] x-ms-date: Thu, 04 May 2017 16:46:29 GMT authorization: type%3dmaster%26ver%3d1.0%26sig%3deHQp5YAx4oaQAkFvxYn0vh9sMbLFqPliTss6TqfrT1Y%3d x-ms-session-token: 0:10043 Cache-Control: no-cache x-ms-consistency-level: Session User-Agent: documentdb-dotnet-sdk/1.13.2 Host/32-bit MicrosoftWindowsNT/6.2.9200.0 x-ms-version: 2017-01-19 Accept: application/json Host: rddocdb-japaneast.documents.azure.com Content-Length: 109 Expect: 100-continue {"id":"10001","Title":"SampleTitle10001","LikeCount":469,"PhotoUri":"https://xxxxxxx/10001","Category":"sea"}
そして、HTTPレスポンスは以下の通りです。
HTTP/1.1 201 Created 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: Thu, 04 May 2017 08:40:05.215 GMT etag: "00003133-0000-0000-0000-590b5ae50000" x-ms-resource-quota: documentSize=10240;documentsSize=10485760;documentsCount=-1;collectionSize=10485760; x-ms-resource-usage: documentSize=6;documentsSize=3912;documentsCount=10031;collectionSize=6560; x-ms-schemaversion: 1.3 x-ms-alt-content-path: dbs/ExampleDB1/colls/ExampleCollection1 x-ms-content-path: FvRbAKerSQA= x-ms-quorum-acked-lsn: 10043 x-ms-current-write-quorum: 3 x-ms-current-replica-set-size: 4 x-ms-xp-role: 1 x-ms-request-charge: 6.1 x-ms-serviceversion: version=1.13.76.2 x-ms-activity-id: 6f53bfbf-d4b1-4ad4-9bf1-b4ce7367636b x-ms-session-token: 0:10044 x-ms-gatewayversion: version=1.13.76.2 Date: Thu, 04 May 2017 16:46:28 GMT ...省略...
消費RUは・・・「x-ms-request-charge: 6.1」。
1件の書き込みRUは 6.1 でした。
読み込み
ドキュメントを読み込みを行います。
DocumentManagerクラスに検索用メソッドQueryPhotoItems()を追加します。
QueryPhotoItems()はかなり固定的な実装で、LikeCountが100より大きく、Categoryが sky のドキュメントを50件取得します。
public class DocumentManager { ...省略 public async Task<List<PhotoItem>> QueryPhotoItems() { List<PhotoItem> result = null; var feedOption = new FeedOptions() { MaxItemCount = 50 }; var query = this.client.CreateDocumentQuery<PhotoItem>( UriFactory.CreateDocumentCollectionUri(DocumentManager.DatabaseID, DocumentManager.CollectionID), feedOption) .Where(i => i.LikeCount > 100 && i.Category == "sky") .Take(50); result = query.ToList(); return result; } }
DocumentManager.QueryPhotoItems()を呼び出す実装は以下の通り。
var manager = new DocumentManager(); var initResult = await manager.InitializeDocumentClient(); // LikeCountが>100のドキュメントを50件検索 var result = await manager.QueryPhotoItems();
上記で実行されるHTTPリクエストは以下の通りです。
POST https://rddocdb-japaneast.documents.azure.com/dbs/ExampleDB1/colls/ExampleCollection1/docs HTTP/1.1 x-ms-continuation: x-ms-documentdb-isquery: True x-ms-max-item-count: 50 x-ms-documentdb-query-enablecrosspartition: False x-ms-documentdb-query-iscontinuationexpected: False x-ms-date: Thu, 04 May 2017 16:59:15 GMT authorization: type%3dmaster%26ver%3d1.0%26sig%3dKF%2fbc5aVHlTffYtoHF4k5qzALl727%2bNdHlERf3plj7U%3d Cache-Control: no-cache x-ms-consistency-level: Session User-Agent: documentdb-dotnet-sdk/1.13.2 Host/32-bit MicrosoftWindowsNT/6.2.9200.0 x-ms-version: 2017-01-19 Accept: application/json Content-Type: application/query+json Host: rddocdb-japaneast.documents.azure.com Content-Length: 109 Expect: 100-continue {"query":"SELECT TOP 50 * FROM root WHERE ((root[\"LikeCount\"] > 100) AND (root[\"Category\"] = \"sky\")) "}
HTTPレスポンスは以下の通りです。
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: Thu, 04 May 2017 08:40:13.755 GMT x-ms-resource-quota: documentSize=10240;documentsSize=10485760;documentsCount=-1;collectionSize=10485760; x-ms-resource-usage: documentSize=6;documentsSize=4298;documentsCount=10040;collectionSize=6964; x-ms-item-count: 50 x-ms-schemaversion: 1.3 x-ms-alt-content-path: dbs/ExampleDB1/colls/ExampleCollection1 x-ms-content-path: FvRbAKerSQA= x-ms-xp-role: 2 x-ms-request-charge: 20.32 x-ms-serviceversion: version=1.13.76.2 x-ms-activity-id: 2f1f663c-09e2-4913-b20d-8ef5ac3d63c2 x-ms-session-token: 0:10052 x-ms-gatewayversion: version=1.13.76.2 Date: Thu, 04 May 2017 16:59:15 GMT ...省略...
x-ms-request-charge: 20.32。
消費RUは 20.32 でした。
4. LikeCount/Categoryのみのインデックス設定で書き込みと読み込み
インデックスを以下のように変更します。
LikeCount / Categoryのみにインデックスを適用するようにカスタマイズします。
{ "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/LikeCount/*", "indexes": [ { "kind": "Range", "dataType": "Number", "precision": -1 } ] }, { "path": "/Category/*", "indexes": [ { "kind": "Hash", "dataType": "String", "precision": 3 } ] } ], "excludedPaths": [ { "path": "/*" } ] }
設定はAzureポータルで「設定 → インデックス作成ポリシー」から変更することができます。
書き込み
先程と同様に10件のドキュメントを書き込んでみます。
HTTPリクエストは以下の通りです。
POST https://rddocdb-japaneast.documents.azure.com/dbs/ExampleDB1/colls/ExampleCollection1/docs HTTP/1.1 x-ms-documentdb-partitionkey: ["mountain"] x-ms-date: Thu, 04 May 2017 17:03:09 GMT authorization: type%3dmaster%26ver%3d1.0%26sig%3deHdnuMnm8pSUdfK4T0ItZPjQzGvmyzqBOEExfbDCuUw%3d Cache-Control: no-cache x-ms-consistency-level: Session User-Agent: documentdb-dotnet-sdk/1.13.2 Host/32-bit MicrosoftWindowsNT/6.2.9200.0 x-ms-version: 2017-01-19 Accept: application/json Host: rddocdb-japaneast.documents.azure.com Content-Length: 114 Expect: 100-continue {"id":"10010","Title":"SampleTitle10010","LikeCount":782,"PhotoUri":"https://xxxxxxx/10010","Category":"mountain"}
HTTPレスポンスは以下の通りです。
HTTP/1.1 201 Created 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: Thu, 04 May 2017 08:40:05.215 GMT etag: "00004e33-0000-0000-0000-590b5ecd0000" x-ms-resource-quota: documentSize=10240;documentsSize=10485760;documentsCount=-1;collectionSize=10485760; x-ms-resource-usage: documentSize=6;documentsSize=4317;documentsCount=10040;collectionSize=6965; x-ms-schemaversion: 1.3 x-ms-alt-content-path: dbs/ExampleDB1/colls/ExampleCollection1 x-ms-content-path: FvRbAKerSQA= x-ms-quorum-acked-lsn: 10053 x-ms-current-write-quorum: 3 x-ms-current-replica-set-size: 4 x-ms-xp-role: 1 x-ms-request-charge: 5.52 x-ms-serviceversion: version=1.13.76.2 x-ms-activity-id: fedec436-d43f-479c-b8c7-8ce095d9dd07 x-ms-session-token: 0:10054 x-ms-gatewayversion: version=1.13.76.2 Date: Thu, 04 May 2017 17:03:08 GMT ...省略...
消費RUは・・・「x-ms-request-charge: 5.52」。
1件の書き込みRUは 5.52 でした。
デフォルトインデックス状態の 6.1 よりも小さな値になりました。
読み込み
ドキュメントを読み込みを行います。
先程と同様の読み込みを行います。
HTTPリクエストは以下の通りです。
POST https://rddocdb-japaneast.documents.azure.com/dbs/ExampleDB1/colls/ExampleCollection1/docs HTTP/1.1 x-ms-continuation: x-ms-documentdb-isquery: True x-ms-max-item-count: 50 x-ms-documentdb-query-enablecrosspartition: False x-ms-documentdb-query-iscontinuationexpected: False x-ms-date: Thu, 04 May 2017 17:10:13 GMT authorization: type%3dmaster%26ver%3d1.0%26sig%3dvO5eIEtwI7FZ3%2bD8d8ezEIvH3Hp9EuY1EJ8ZR0Rm2WE%3d Cache-Control: no-cache x-ms-consistency-level: Session User-Agent: documentdb-dotnet-sdk/1.13.2 Host/32-bit MicrosoftWindowsNT/6.2.9200.0 x-ms-version: 2017-01-19 Accept: application/json Content-Type: application/query+json Host: rddocdb-japaneast.documents.azure.com Content-Length: 109 Expect: 100-continue {"query":"SELECT TOP 50 * FROM root WHERE ((root[\"LikeCount\"] > 100) AND (root[\"Category\"] = \"sky\")) "}
HTTPレスポンスは以下の通りです。
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: Thu, 04 May 2017 08:40:13.755 GMT x-ms-resource-quota: documentSize=10240;documentsSize=10485760;documentsCount=-1;collectionSize=10485760; x-ms-resource-usage: documentSize=6;documentsSize=3929;documentsCount=10050;collectionSize=6597; x-ms-item-count: 50 x-ms-schemaversion: 1.3 x-ms-alt-content-path: dbs/ExampleDB1/colls/ExampleCollection1 x-ms-content-path: FvRbAKerSQA= x-ms-xp-role: 2 x-ms-request-charge: 20.32 x-ms-serviceversion: version=1.13.76.2 x-ms-activity-id: 2be70cc0-d86c-40ce-8f72-a2542c954ad9 x-ms-session-token: 0:10063 x-ms-gatewayversion: version=1.13.76.2 Date: Thu, 04 May 2017 17:10:13 GMT ...省略...
消費RUは 20.32 でした。
検索条件(LikeCount・Category)が、デフォルトインデックス時も、その後のカスタムインデックス時も共に対象となっている為に、読み込み時の消費RUが変化しませんでした。
4. 読み込みインデックスの効きを確認する
前述の確認では デフォルトインデックス時・カスタムインデックス時 共にインデックス対象の項目で検索してしまっていました。
検索条件を以下のようにした場合はどうでしょうか?
var query = this.client.CreateDocumentQuery<PhotoItem>( UriFactory.CreateDocumentCollectionUri(DocumentManager.DatabaseID, DocumentManager.CollectionID), feedOption) .Where(i => i.LikeCount > minLikeCount && i.Category == "sky" && i.Title == "SampleTitle0") .Take(50);
Title項目も検索対象としました(このクエリー条件は現実のビジネス的にはちょっと変な条件指定ですが・・・技術的検証としてとらえてください)。
Titleは「デフォルトインデックス時にはインデックス対象」「カスタムインデックス時にはインデックス対象外」となる項目です。
実施結果は以下のようになります。
デフォルトインデックス時(インデックス対象) : x-ms-request-charge: 5.96
カスタムインデックス時(インデックス対象外) : x-ms-request-charge: 204.98
RUに大きな違いが発生しました。
インデックス外の項目での検索の負荷の高さを確認することができます。
まとめ
DocumentDBではデフォルト状態で「すべての項目に対して Hash と Range のインデックスが設定されている」。
GEOクエリーを行わなければ、何もしないでもある程度いい感じに動いてくれる。
インデックスの最適化で消費RUの最適化が図れる。ただし、適切に実施しないと、消費RUに大きな差が生まれるので注意しなければならない。
といった感じでしょうか。
DocumentDBの(マスター)キーとauthorizationトークンについて
最近 DocumentDB について調べたり、使ったりしております。
DocumentDB は (たしか)2015年にGAしたと思うのですが、GAから日が経っているのに、想像したよりもネット上のブログなどでの資料が少ない気がしています(特に日本語)。
一方 本家のドキュメントは日本語版も含めかなりしっかりしている印象です。
まあ、大抵のエンプラ系システムであれば、NoSQLなんて使わず従来通りのSQL Server(もしくはSQL Database)でOKですからね。
普通の開発現場では、ちょー早くて、無制限にスケール可能で、でもデータ構造設計やパーティション分割やトランザクションや検索にコツが必要な DocumentDB(NoSQL) を欲するケースは少ないのでしょう・・・
でも、私個人的には、DocumentDBを含むクラウドネイティブな技術が好きなんですよね^^
と長くなりましたが本題へ・・・
(マスター)キーとauthorizationトークンの関係
このお話、DocumentDBをREST APIから使用している(使用したことがある)方は分かっていると思いますが、ライブラリ経由でしか使ったことがない方の場合は認識できていないかもしれない、という内容のお話です。
だからDocumentDBを利用した開発においては必須というわけではありません。
DocumentDBを利用する場合、以下の2パターンの方法があります。
- REST APIを直に呼び出す
- 各言語用ライブラリを使用する
大抵、後者の各言語用ライブラリを使う事と思います。
Visual Studio & .NET だったらNugetで「Microsoft.Azure.DocumentDB(.NET CoreならMicrosoft.Azure.DocumentDB.Core)」を追加するだけですね。
ライブラリはバックエンドでREST APIを呼び出してくれます。接続プロトコル(ConnectionPolicy.ConnectionProtocol)を HTTPS にしていれば、Fiddlerなんかでその通信内容を確認することができます。
で、DocumentDBに接続する場合には、DocumentClientクラスを以下のようにインスタンス化します。
var client = new DocumentClient(uri, authKey);
認証・認可のために第2引数に認証キー(authKey)を渡します(第1引数uriは、利用するDocumentDBアカウントのURIです)。
インスタンス化した DocumentClient に対して、CreateDocumentQuery()だったりCreateStoredProcedureQuery()だったりのメソッド呼び出しを行うことで対象のDocumentDBに対する操作を行うことができます。
「認証キー(authKey)」というのは、Azureポータルで確認可能なプライマリキー(セカンダリキー)です。
以下の画面キャプチャ。
ではDocumentClientがDocumentDBに対して操作を行う際にはどのような通信を行っているのか確認してみます。
まず、以下のようなDocumentDBの操作を行うクラスを用意します(DocumentManagerクラス)。
public class DocumentManager { private const string EndpointUrl = "https://rddocdb.documents.azure.com:443/"; private const string PrimaryKey = "[(マスター)プライマリキー]"; private const string DatabaseID = "ExampleDB1"; private const string CollectionID = "ExampleCollection1"; private DocumentClient client; public async Task<bool> Init() { // DocumentClientオブジェクト 作成 this.client = new DocumentClient(new Uri(DocumentManager.EndpointUrl), DocumentManager.PrimaryKey, new ConnectionPolicy { ConnectionMode = ConnectionMode.Gateway, ConnectionProtocol = Protocol.Https }); // ExampleDB1 作成 var db = await this.client.CreateDatabaseIfNotExistsAsync( new Database { Id = DocumentManager.DatabaseID }); // ExampleCollection1 作成 var collection = await this.client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(DocumentManager.DatabaseID), collection); return true; } public async Task<bool> ReadDocumentCollection() { // コレクション情報を読み込む var collection = await this.client.ReadDocumentCollectionAsync( UriFactory.CreateCollectionUri(DocumentManager.DatabaseID, DocumentManager.CollectionID)); return true; } }
Init()メソッドは、以下の処理を行います。
- DocumentClientオブジェクトを作成しprivateメンバーに保持
- ExampleDB1データベースを作成
- ExampleCollection1コレクションを作成
ReadDocumentCollection()メソッドは、Init()で作成されたExampleCollection1コレクション情報を読み取ります。
そして、以下のようにdocumentManagerを呼び出しましょう。
var documentManager = new DocumentManager();
var initResult = await documentManager.Init();
var readResult = await documentManager.ReadDocumentCollection();
最後の ReadDocumentCollection() 呼び出しに注目します。
Fiddlerで抜き取ったHTTPS通信は以下の通りです。
「/dbs/ExampleDB1/colls/ExampleCollection1」にGETリクエストを行っています。
つまり↓↓↓のREST APIが呼び出されています。
Get a Collection | Microsoft Docs
HTTP GETのリクエストヘッダで注目すべきは authorization です。
DocumentClientオブジェクト作成時に渡した認証キー(プライマリキー)を元にして authorization 値が生成されています。
厳密には認証キー(プライマリキー)を含む以下の要素から生成されています。
- HTTP Verb(GETとかPOSTとか)
- リソースタイプ(CollectionとかDocumentとかのアクセスの対象リソースタイプ)
- リソースリンク(こんなの・・/dbs/ExampleDB1/colls/ExampleCollection1)
- 日時(トークン生成日)
- キー
- キーのタイプ(masterとかresource)
- トークンバージョン
日時は注意が必要で、トークン生成時に指定した日時と HTTPヘッダ x-ms-date 値がマッチしている必要があります。
上記からauthorizationトークンを生成するロジックは Access Control on Azure Cosmos DB Resources | Microsoft Docs に書かれておりまして、コードの引用が以下になります。
// authorizationトークンを作成 static string GenerateAuthToken( string verb, string resourceType, string resourceLink, string date, string key, string keyType, string tokenVersion) { var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) }; verb = verb ?? ""; resourceType = resourceType ?? ""; resourceLink = resourceLink ?? ""; string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n", verb.ToLowerInvariant(), resourceType.ToLowerInvariant(), resourceLink, date.ToLowerInvariant(), "" ); byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad)); string signature = Convert.ToBase64String(hashPayLoad); return System.Web.HttpUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}", keyType, tokenVersion, signature)); }
以下のようなコードで認証トークンを生成することができます。
var authorizationToken = GenerateAuthToken( "get", "colls", "dbs/ExampleDB1/colls/ExampleCollection1", "Tue, 02 May 2017 10:20:01 GMT", "[プライマリキー]", "master", "1.0");
認証トークン生成して使ってみる
では認証トークンを生成して実際にREST呼び出ししてみましょう。
Fiddlerを使ってHTTPリクエストを投げたいと思います。
1.正常なケース
以下のリクエストを行います。
トークン生成に使用した 日時 と x-ms-date は同一でないといけない点には注意。
いい感じにレスポンスが返ってきました。
2.不正なauthorizationトークンを利用したケース
リクエスト内容とマッチしないauthorizationトークンを使ってリクエストを行います。
401 Unauthorized が返ってきます。
3.期限切れのauthorizationトークンを利用したケース
authorizationトークンは有効期限があります。
次は、有効期限切れになったauthorizationトークンでリクエストを行ってみます。
403 Forbidden が返ってきます。
bodyを見ると親切に以下のようなメッセージが書かれています。
{ "code":"Forbidden", "message":"The authorization token is not valid at the current time. Please create another token and retry (token start time: Thu, 04 May 2017 00:57:01 GMT, token expiry time: Thu, 04 May 2017 01:12:01 GMT, current server time: Thu, 04 May 2017 05:04:48 GMT).\r\n ActivityId: f7c026e7-ddea-4c89-9331-4c9b24d50d9e" }
つまり・・・↓↓↓と言っていますね。DocumentDB、超親切!!
そのトークンは現時刻では無効だよ。
別のトークン作ってリトライしてね。
そのトークンの開始時刻は「Thu, 04 May 2017 00:57:01 GMT」。無効になるのは「Thu, 04 May 2017 01:12:01 GMT」、今のサーバー時刻は「Thu, 04 May 2017 05:04:48 GMT」。
うん、つまりauthorizationトークンの有効期限は15分ってことですね。
まとめ
内容的に まとめ ってこともないのですが、いつも思うことはライブラリにラップされた内容だけでなくその裏側の本当の姿まで理解しておく事は重要だと思います。
そうすると、何か問題が起きた時に色々解決の糸口への道のりが短くなるのだと思っています。
Computer Vision APIでランドマーク認識する
本日は天気が良く、最近にしては珍しく予定が何もなかったのでプチドライブにいってきました。
我が家のある夢の国を出発し、永代橋を渡り、銀座を通り抜け、東京タワーへ、そしてレインボーブリッジ(下の一般道)を渡って夢の国に舞い戻る・・・という。
で、日本が誇るランドマーク「東京タワー」をカメラで撮ってきました。
ランドマークといえば先日2017/4/19に Cognitive Services のいくつかに機能が GA していました・・・(Face API / Computer Vision API / Content Moderator)
ということで、Computer Vision APIのランドマーク認識は、私の撮った写真をきちんと認識してくれるのかどうか試してみましょう!
Computer Vision APIよ、これが何か分かるか!?
※今日は、なんか、真ん中の展望台辺りから霧のようなものが噴霧されていました。
1. Azureに「Cognitive Services APIs」を作成
Cognitive Servicesを利用するためにはAzureに「Cognitive Services APIs」を作成する必要があります。
Azureポータルにログインします。
「+新規」をクリック。
Cognitive Services APIsを選択。
作成。
各種パラメータを適時設定します。
ここでは以下の設定にしました。
- アカウント名: MyCognitive1
- APIの種類: 今回はランドマーク認識を行うので「Computer Vision API」となります。
- 場所: 日本のリージョンはまだないので「東南アジア」にしました。
- 価格レベル: テストなので無料の F0 にしました(F0は、1分あたり20回・1ケ月あたり5000回の呼び出し制限)。
しばらく待つと作成が完了します。
以下はリソースの一覧画面で確認したところです。
MyCognitive1を選択し「概要」画面で「エンドポイント」をコピーしておきます。
「キー」を選択し「キー1」をコピーしておきます。
エンドポイントとキーはプログラムからAPI呼び出しを行う際に利用します。
2. コンソールアプリを作成
Visual Studio 2017(2015でもいいけど)を起動し、コンソールアプリケーションを作成します。
(前述で作成したCognitive Services(Computer Vision API)を呼び出すアプリです)
で、実装は単純に以下のサンプルをそのまま拝借させていただきました。
// https://azure.microsoft.com/ja-jp/blog/microsoft-cognitive-services-general-availability-for-face-api-computer-vision-api-and-content-moderator/から引用 // 名前空間、キー設定部分、エンドポイント指定部分を修正しています。 using System; using System.IO; using System.Net.Http; using System.Net.Http.Headers; namespace RecognizinglandmarksExample { static class Program { static void Main() { Console.Write("Enter image file path: "); string imageFilePath = Console.ReadLine(); MakeAnalysisRequest(imageFilePath); Console.WriteLine("\n\nHit ENTER to exit...\n"); Console.ReadLine(); } static byte[] GetImageAsByteArray(string imageFilePath) { FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read); BinaryReader binaryReader = new BinaryReader(fileStream); return binaryReader.ReadBytes((int)fileStream.Length); } static async void MakeAnalysisRequest(string imageFilePath) { var client = new HttpClient(); // Request headers. Replace the second parameter with a valid subscription key. //client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "putyourkeyhere"); client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "あなたのキーを設定"); // Request parameters. You can change "landmarks" to "celebrities" on requestParameters and uri to use the Celebrities model. string requestParameters = "model=landmarks"; //string uri = "https://westus.api.cognitive.microsoft.com/vision/v1.0/models/landmarks/analyze?" + requestParameters; string uri = "https://southeastasia.api.cognitive.microsoft.com/vision/v1.0/models/landmarks/analyze?" + requestParameters; Console.WriteLine(uri); HttpResponseMessage response; // Request body. Try this sample with a locally stored JPEG image. byte[] byteData = GetImageAsByteArray(imageFilePath); using (var content = new ByteArrayContent(byteData)) { // This example uses content type "application/octet-stream". // The other content types you can use are "application/json" and "multipart/form-data". content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); response = await client.PostAsync(uri, content); string contentString = await response.Content.ReadAsStringAsync(); Console.WriteLine("Response:\n"); Console.WriteLine(contentString); } } } }
上記ソースから以下の行を修正します。
client.DefaultRequestHeaders.Add(“Ocp-Apim-Subscription-Key”, “あなたのキーを設定”);
「あなたのキーを設定」部分には、Azureポータルからの「Cognitive Services APIs」作成で得られた「キー1」に置き換えます。
もう1ヶ所、以下のエンドポイント部分も修正対象ですが、こちらは上記ソースでは東南アジア用に修正してあります。
string uri = “https://westus.api.cognitive.microsoft.com/vision/v1.0/models/landmarks/analyze?” + requestParameters;
ビルドしてコンソールアプリを作成します。
(このコンソールアプリでは、Vision APIの呼び出しに単純なHTTP POSTを行っているだけなので、特別なNugetパッケージの追加は必要ありません)
3. 実行
CTRL + F5で実行します。
画像ファイル名を聞かれるので、入力してEnter。
認識の結果がJSONで帰ってきます。
「Tokyo Tower」正解!そして confidence は 0.9743892 とのこと。
まとめ
本投稿は Cognitive Services の本当に触りの部分の薄っぺらな内容でしたが、顔認識・文字認識等々色々な機能が提供されています。BotとかMachine Learningとかと組み合わせるといろいろなことが出来そうですよね。