Azure Cosmos DB入門(6)
本コンテンツは「Azure Cosmos DB入門」の(6)です
6 Cosmos DBプログラミング ~ Gremlin編
さて、今回は Gremlin編 になります。
6.1 はじめに
以前説明したDocumentDB / MongoDBは、共にデータモデルが「ドキュメント データモデル」でした。
今回のGremlinは、がらりと変わり「グラフ データモデル」になります。
6.1.1 グラフ データモデルとは
グラフデータモデルとは「facebookの友達」「twitterのフォロー」「駅の路線図の繋がり」のような繋がりのデータ構造を指します。
グラフデータモデルを扱うデータベースを、グラフデータベースと呼びます。
例えば、facebookの友達・フォローの関係をグラフデータモデルで表すと以下のようになります。
グラフで最も重要な要素として、以下の2つの概念があります。
Vertex
直訳で「頂点」ですね。上の例の図では、「人」「企業」の丸い要素(ノード)が該当します。
Vertexには「ラベル」を付けることができます。例では「人」「企業」がラベルに該当します。ラベルは、Vertexの種類を表します。
また、Vertexには任意の数の「プロパティ」を関連付けることができます。例では「名前・生年月日・性別」がプロパティに該当します。Edge
Edgeは「VertexとVertexを結ぶ線(辺)」を表します。上の例の図では 矢印線 が該当します。
Edgeにも「ラベル」を付けることができます。例では「友達」「フォロー」「いいね」に該当します。ラベルは、Edgeの種類を表します。
また、Edgeには任意の数の「プロパティ」を関連付けることができます。例では「いつから?・関係」がプロパティに該当します。
このようなデータ構造はRDBでも表現は可能です。しかし、データ構造上の概念の異なるテーブル形式に保存するため、分かりにくい もしくは 複雑な データ構造として保存されることになります。
また、グラフデータベースのトラバーサル言語であるGremlinを使用すると、以下のような検索を簡単に行うことができます。
- 高橋さんの友達の友達はだれ?(→下田さん、佐藤さん、上田さん、神山さん)
- 高橋さんの友達をフォローしている人はだれ?(→木村さん、川田さん)
つまり、「複数の要素のグラフ上の関係性の保持」および「それらの要素の関係性の検索」を容易に行うことができるものが グラフデータベースです。
6.1.2 Gremlinとは
グラフデータベースについて学ぼうとすると「TinkerPop」という言葉が出てきます。
TinkerPopは、現在、Apache Software Foundationのトッププロジェクトであり、「グラフコンピューティング フレームワーク」です。TinkerPopという抽象化層の上に各種ベンダーがグラフデータベースを構築しています。
Cosmos DBも同様にTinkerPopの上に実装されたグラフデータベースという位置づけになります。
また、Cosmos DB以外にも以下のようなグラフデータベースが存在します。
- Neo4j
- DESGraph
- IBM Graph
- Titan
- …等々多数
そして、グラフデータベースの利用者(利用アプリケーション)は、「Gremlin traversal language」によって、グラフデータを操作します。
「Gremlin traversal language」は、RDBでいうところの「SQL」に該当します。つまり、グラフデータベースに対して、データを検索する・追加する・更新する、といった操作を行うための言語です。
TinkerPopは元々(Ver.2.xまで)は、以下のようなコンポーネントの組み合わせで構成されていました。
- Rexster(RESTインターフェイス)
- Furnace(グラフアルゴリズム) / Frames(オブジェクトグラフマッパー)
- Gremlin(グラフ トラバーサル言語)
- Pipes(データフロー)
- BluePrints(プロパティグラフモデル)
Ver.3.xからはこれらが統合され、全体で「Gremlin」と呼ばれるようになっています。
この辺りのお話は、TinkerPopやGremlinに関する少し複雑な部分に入ってしまい、「Azure Cosmos DB入門」と題した本投稿から逸脱(?)しそうなので、詳細は以下の公式ページを参照してください。
6.2 準備
では、具体的なCosmos DB(Gremlin)開発のお話に入るために、Azure上へのCosmos DBの準備と、Visual Studioプロジェクトの準備を行います。
6.2.1 Cosmos DB(Gremlin)データベースアカウントの作成
Azureポータルを利用して「Cosmos DB(Gremlin(グラフ))」を作成します。
Azureポータルをブラウザで表示し「新規」を選択。検索テキストボックスに「Azure Cosmos DB」と入力します。
「Azure Cosmos DB」を選択します。
「作成」をクリックします。
アカウント情報を入力して「作成」をクリックします。
ここでは以下の設定を行いました。
ID: gremlincosmos
API: Gremlin(グラフ)
リソースグループ: gremlincosmos
場所: 西日本
以下のCosmos DBアカウントが作成されます。
6.2.2 Visual Studio 2017プロジェクトの作成
Visual Studio 2017を起動し、メニュー「ファイル → 新規作成 → プロジェクト」を選択します。
ここでは、プロジェクトテンプレートは「コンソールアプリケーション」、名前は「CosmosGremlinExample」としました。
次に、Cosmos DB(Gremlin)にアクセスするために、NuGetパッケージライブラリをプロジェクトに追加します。
ソリューションエクスプローラからプロジェクトをマウス右ボタンクリックし、表示されたメニューから「NuGetパッケージの管理を選択します。
表示されたNuGetパッケージ管理画面から、左上の「参照タブ」を選択し、検索テキストボックスに「Microsoft.Azure.Graphs」と入力します。 一覧に Microsoft.Azure.Graphs が表示されるので、選択して「インストール」ボタンをクリックします。
「Microsoft.Azure.Graphs」と、その依存関係である「Microsoft.Azure.DocumentDB」がインストールされます。
6.3 データベースとコレクションの作成
今回のCosmos DB(Gremlin)においても、DocumentDBの場合と同様に「データベースアカウント → データベース → コレクション → ドキュメント(グラフデータ)」というデータモデル構造となります。
まず、データベースとコレクションを作成したいと思います。
作成方法は「Azureポータルで作成」「プログラムで作成」などがあります。
「Azureポータルで作成」「プログラムで作成」の2つの方法は共に、DocumentDBの時とまったく同じ方法となります。
6.3.1 プログラムで作成
本稿では、Gremlin(グラフ)操作を管理するクラスとして GremlinManagerクラス を用意することとします。
プロジェクトに GremlinManagercs を追加します。
(1)接続情報の定義
「接続情報」及びこれから「作成するデータベース名・コレクション名」等をGremlinManagerクラスに定数として定義します(リスト 1)。
リスト1 GremlinManager.cs private const string EndPoint = "【URI】"; private const string AuthKey = "【認証キー】"; private const string DatabaseId = "BookStoreDb"; private const string CollectionId = "BookStoreCollection";
【URI】【認証キー】は各データベースアカウントごとに適切な値を設定します。
Azureポータルで「キー」タブを選択することで確認することができます。
データベースIDは「BookStoreDb」、コレクションIDは「BookStoreCollection」としました。これはサンプルとして、オンライン書店のデータベースを想定します。
(2)基本名前空間のusing定義
Gremlin接続・操作を行う上での基本的な名前空間をusing定義しておきます(リスト 2)。
リスト2 GremlinManager.cs // 一般的に利用する名前空間 using System; using System.Collections.Generic; using System.Threading.Tasks; // Cosmos DB(Gremlin)関連の名前空間 using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; using Microsoft.Azure.Documents.Linq; using Microsoft.Azure.Graphs; using System.Text;
(3)接続クライアントオブジェクトの定義
CosmosDB(Gremlin)に接続する為の接続クライアント「Microsoft.Azure.Documents.Client.DocumentClient」をGremlinbManagerクラスのフィールド変数「client」として定義します(リスト 3)。
clientはコンストラクタで初期化することとします。
リスト3 GremlinManager.cs private DocumentClient client = null; ... public GremlinManager() { this.client = new DocumentClient( new Uri(GremlinManager.EndPoint), GremlinManager.AuthKey, new ConnectionPolicy { ConnectionMode = ConnectionMode.Direct, ConnectionProtocol = Protocol.Tcp }); }
(4)データベースとコレクション作成メソッドの追加
GremlinManagerクラスにデータベースを作成するメソッド「CreateDatabase()」、コレクションを作成するメソッド「CreateCollection()」を定義します(リスト 4)。
また、DocumentCollectionオブジェクトは、後々の使い勝手を考慮し、クラスメンバフィールドに保持することとします。
リスト4 GremlinManager.cs private DocumentCollection collection = null; ... public async Task<Database> CreateDatabase() { // データベースを作成 Database database = await this.client.CreateDatabaseIfNotExistsAsync( new Database { Id = GremlinManager.DatabaseId }); return database; } public async Task<DocumentCollection> CreateCollection() { // パーティションキー指定はなし DocumentCollection collection = new DocumentCollection(); collection.Id = GremlinManager.CollectionId; // スループットは 400RU RequestOptions options = new RequestOptions(); options.OfferThroughput = 400; // コレクションを作成 this.collection = await this.client.CreateDocumentCollectionIfNotExistsAsync( UriFactory.CreateDatabaseUri(GremlinManager.DatabaseId), collection, options); return collection; }
以下が GremlinManager.CreateDatabase() / .CreateCollection() 呼び出しコードです(リスト5)。
リスト5 Program.cs private GremlinManager manager = new GremlinManager(); ... var database = await manager.CreateDatabase(); var collection = await manager.CreateCollection();
上記コードによるデータベース・コレクションの作成を行った結果をAzureポータルで確認したものが以下です。
パーティションキーなし、予約RUは400での作成となります。
6.4 グラフデータの操作
BookStoreDb - BookStoreCollection にグラフデータを追加していきます。
このデータベースはオンライン書店を想定します。
以下のようなグラフデータを作成することとします。
【サンプルグラフデータ】
Customer Vertex
Customer Vertexは顧客を表します。
ラベルは「Customer」、それ以外には「id」のみを持ちます。Book Vertex
Book Vertexは書籍を表します。
ラベルは「Book」、「id」の他に「title」「author」のプロパティを持ちます。
idは書籍の ISBN番号 を使用しています。order Edge
order Edgeは、Customer(顧客)が購入したBook(書籍)を表します。
6.4.1 Vertexの追加
Vertexの追加は Gremlin では以下のような構文で行います。
ラベルのみ指定してVertexを作成 g.addV('【ラベル】') ラベルとIDを指定してVertexを作成 g.addV('【ラベル】').property('id', '【ID】')
GremlinManagerクラスに、上記 Gremlin構文 を実行する「AddVertex()」メソッドを追加します(リスト6)。
リスト6 GremlinManager.cs // ラベルとidを指定してVertexを追加 public async Task<bool> AddVertex(string label, string id) { string gr = string.Format("g.addV('{0}').property('id', '{1}')", label, id); var ret = await this.client.CreateGremlinQuery<dynamic>( this.collection,gr) .ExecuteNextAsync(); return true; }
- CreateGremlinQuery()メソッド
DocumentClient.CreateGremlinQuery()メソッドを呼び出すことでGremlinクエリーオブジェクトを作成することができます。
CreateGremlinQuery()メソッドは、DocumentClientクラスの拡張メソッドで、Microsoft.Azure.Graphsで実装されています。
また、Bookは id 以外のプロパティ(title / author)を追加します。
リスト7に示すような「SetProperty()」、もう1つの「AddVertex()」オーバーロードメソッドをGremlinManagerクラスに追加しました。
リスト7 GremlinManager.cs // idを持つVertexにプロパティを追加します public async Task<bool> SetProperties(string id, Dictionary<string, string> properties) { StringBuilder grSb = new StringBuilder( string.Format("g.V('{0}')", id)); foreach (string key in properties.Keys) { grSb.Append(string.Format(".property('{0}', '{1}')", key, properties[key])); } var ret = await this.client.CreateGremlinQuery<dynamic>( this.collection, grSb.ToString()) .ExecuteNextAsync(); return true; } // Vertexの作成とプロパティ設定同時に行います public async Task<bool> AddVertex(string label, string id, Dictionary<string, string> properties) { StringBuilder grSb = new StringBuilder( string.Format("g.addV('{0}').property('id', '{1}')", label, id)); foreach (string key in properties.Keys) { grSb.Append(string.Format(".addProperty('{0}', '{1}')", key, properties[key])); } var ret = await this.client.CreateGremlinQuery<dynamic>( this.collection, grSb.ToString()) .ExecuteNextAsync(); return true; }
GremlinManager.AddVertex() / SetProperties() を呼び出して、4つのCustomer Vertex と 5つのBook Vertex を作成する処理はリスト8の通りです。
リスト8 Program.cs GremlinManager manager = new GremlinManager(); // add Customer Vertex var cv1 = await manager.AddVertex("Customer", "daigo"); var cv2 = await manager.AddVertex("Customer", "tanaka"); var cv3 = await manager.AddVertex("Customer", "kido"); var cv4 = await manager.AddVertex("Customer", "sakai"); // add Book Vertex // AddVertex() + SetProperties()呼び出し var bv1 = await manager.AddVertex("Book", "978-4101339115"); var prop1 = new Dictionary<string, string>(); prop1.Add("title", "きらきらひかる"); prop1.Add("author", "江國香織"); var bp1 = await manager.SetProperties("978-4101339115", prop1); // もう1つのAddVertex()オーバーロード呼び出し var prop2 = new Dictionary<string, string>(); prop2.Add("title", "流しのしたの骨"); prop2.Add("author", "江國香織"); var bv2 = await manager.AddVertex("Book", "978-4101339153", prop2); var prop3 = new Dictionary<string, string>(); prop3.Add("title", "キッチン"); prop3.Add("author", "吉本ばなな"); var bv3 = await manager.AddVertex("Book", "978-4041800089", prop3); var prop4 = new Dictionary<string, string>(); prop4.Add("title", "月に吠える"); prop4.Add("author", "萩原朔太郎"); var bv4 = await manager.AddVertex("Book", "978-4903620510", prop4); var prop5 = new Dictionary<string, string>(); prop5.Add("title", "抱擁、あるいはライスには塩を"); prop5.Add("author", "江國香織"); var bv5 = await manager.AddVertex("Book", "978-4087713664", prop5);
6.4.2 Edgeの追加
続いて Customer / Book をつなげる Edge を追加します。
まず、Gremlinにおける Edge追加構文は以下の通りです。
【FROMのVertex】.addE('【ラベル】').to(【ToのVertex】) 例)g.V('daigo').addE('order').to(g.V('978-4101339115'))
GremlinManagerクラス にEdgeを作成する「AddEdge()」メソッドを追加します(リスト9)。
リスト9 GremlinManager.cs public async Task<bool> AddEdge(string label, string fromId, string toId) { string gr = string.Format( "g.V('{0}').addE('{1}').to(g.V('{2}'))", fromId, label, toId); var ret = await this.client.CreateGremlinQuery<dynamic>( this.collection, gr) .ExecuteNextAsync(); return true; }
GremlinManager.AddEdge()を呼び出してサンプルとなる Customer / Book Vertexに対するEdgeを追加する処理を実装します(リスト10)。
リスト10 Program.cs // add order Edge // daigo -> きらきらひかる var e1 = await manager.AddEdge("order", "daigo", "978-4101339115"); // tanaka -> きらきらひかる var e2 = await manager.AddEdge("order", "tanaka", "978-4101339115"); // tanaka -> 月に吠える var e3 = await manager.AddEdge("order", "tanaka", "978-4903620510"); // sasaki -> キッチン var e4 = await manager.AddEdge("order", "sasaki", "978-4041800089"); // sasaki -> 流しのしたの骨 var e5 = await manager.AddEdge("order", "sasaki", "978-4101339153"); // kido -> きらきらひかる var e6 = await manager.AddEdge("order", "kido", "978-4101339115"); // kido -> 抱擁、あるいはライスには塩を var e7 = await manager.AddEdge("order", "kido", "978-4087713664"); // kido -> 流しのしたの骨 var e8 = await manager.AddEdge("order", "kido", "978-4101339153");
以上で【サンプルグラフデータ】の作成が完了しました。
6.4.3 Vertexの検索
作成したグラフに対する検索を行います。
(1) すべてのVertexを一覧
まずは すべてのVertex を一覧してみます。
Gremlin構文は以下となります。
g.V()
GremlinManagerクラスにGetAllVertex()メソッドを追加します(リスト11)。
リスト11 GremlinManager.cs public async Task<List<dynamic>> GetAllVertex() { List<dynamic> result = new List<dynamic>(); var query = this.client.CreateGremlinQuery<dynamic>( this.collection, "g.V()"); if(query.HasMoreResults) { foreach (dynamic item in await query.ExecuteNextAsync()) { result.Add(item); } } return result; }
GremlinManager.GetAllVertex()の呼び出しと実行結果は以下の通りです(リスト12)。
リスト12 Program.cs GremlinManager manager = new GremlinManager(); Console.WriteLine(""); Console.WriteLine("-start- すべてのVertexをリストします"); var ret = await manager.GetAllVertex(); foreach (var vertex in ret) { Console.WriteLine("---"); Console.WriteLine("id: " + vertex.id); string label = vertex.label; if (label == "Book") { Console.WriteLine("title: " + vertex.properties.title[0].value); Console.WriteLine("author: " + vertex.properties.author[0].value); } } Console.WriteLine("-end-");
リスト12の実行結果 -start- すべてのVertexをリストします --- id: daigo --- id: tanaka --- id: kido --- id: sakai --- id: 978-4101339115 title: きらきらひかる author: 江國香織 --- id: 978-4101339153 title: 流しのしたの骨 author: 江國香織 --- id: 978-4041800089 title: キッチン author: 吉本ばなな --- id: 978-4903620510 title: 月に吠える author: 萩原朔太郎 --- id: 978-4087713664 title: 抱擁、あるいはライスには塩を author: 江國香織 -end-
(2) idでVertexを検索
特定のidを持つVertexを検索します。
Gremlin構文は以下の通りです。
g.V('【id】')
GremlinManagerクラスにGetVertexById()メソッドを追加します(リスト13)。
リスト13 GremlinManager.cs public async Task<dynamic> GetVertexById(string id) { dynamic result = null; string gr = string.Format("g.V('{0}')", id); var query = this.client.CreateGremlinQuery<dynamic>( this.collection, gr); if (query.HasMoreResults) { foreach (dynamic item in await query.ExecuteNextAsync()) { result = item; } } return result; }
GremlinManager.GetVertexById()の呼び出し及び実行結果は以下の通りです(リスト14)。
リスト14 Program.cs GremlinManager manager = new GremlinManager(); dynamic vertex = await this.manager.GetVertexById("978-4087713664"); Console.WriteLine(""); Console.WriteLine("-start- 978-4087713664のVertexを検索します"); Console.WriteLine("id: " + vertex.id); Console.WriteLine("title: " + vertex.properties.title[0].value); Console.WriteLine("author: " + vertex.properties.author[0].value); Console.WriteLine("-end-");
6.4.4 Edgeの検索
すべてのVertex を一覧してみます。
Gremlin構文は以下となります。
g.E()
GremlinManagerクラスにGetAllEdge()メソッドを追加します(リスト15)。
リスト15 GremlinManager.cs public async Task<List<dynamic>> GetAllEdge() { List<dynamic> result = new List<dynamic>(); var query = this.client.CreateGremlinQuery<dynamic>( this.collection, "g.E()"); if (query.HasMoreResults) { foreach (dynamic item in await query.ExecuteNextAsync()) { result.Add(item); } } return result; }
上記コードから分かるように、Vertex一覧と同じ要領です。
6.4.5 Vertex~Edge~Vertexを辿る(1)
次に少しグラフデータベースらいしい検索を行ってみます。
- daigo が購入した Book を同様に購入した Customer を取得します
(つまり嗜好の同じCustomerを抽出します)
GremlinManager.GetSameOrderCustomers()メソッドを追加します(リスト16)。
リスト16 GremlinManager.cs public async Task<List<dynamic>> GetSameOrderCustomers(string id) { List<dynamic> result = new List<dynamic>(); string gr = string.Format( "g.V('{0}').as('self').outE('order').inV().inE().outV().where(neq('self'))", id); // 上記の省略形は以下です。 //string gr = string.Format( // "g.V('{0}').as('self').out('order').in().where(neq('self'))", // id); var query = this.client.CreateGremlinQuery<dynamic>( this.collection, gr); if(query.HasMoreResults) { foreach (dynamic item in await query.ExecuteNextAsync()) { result.Add(item); } } return result; }
GetSameOrderCustomers()で実行されるGremlin構文は以下になります。
g.V('daigo').as('self').outE('order').inV().inE().outV().where(neq('self'))
ごちゃごちゃしていますが、先頭から順番に「.(ドット)」区切りで見ていくと理解することができます。
また、以下の説明の下に、処理に該当する番号を振った図を示します。
1 “g.V(‘daigo’)”
Vertexの中から id=daigo のものを取得します。2 “as(‘self’)”
as()はエイリアスになります。
selfには任意の文字列を指定することができます。 以降の構文中で self をして Vertex(“daigo”) を指すことができます。3 “outE(‘order’)”
対象のVertexから出力しているEdge、さらにラベルが'order'のものを取得します。
ここでは1つの order Edge が該当します。4 “inV()”
対象のEdgeから入力しているVertexを取得します。
ここでは1つの Book Vertex が該当します。5 “inE()”
対象のVertexに入力しているEdgeを取得します。
ここでは3つの Order Edge が該当します。6 “outV()”
対象のEdgeに出力しているVertexを取得します。
ここでは3つの Customer Vertex が該当します。7 “where(neq(‘self’))”
whereはSQLなどと同じく抽出条件を指定します。
neq()は not equal の意味を持ちます。
selfは as() により指定した V(‘daigo’) を指します。
つまり、このGremlin構文においては「自分自身は除外する」という意味を持ちます。 最終的に2つのCustomer Vertexが該当します。
GremlinManager.GetSameOrderCustomers()の呼び出しと実行結果は以下の通りです(リスト17)。
リスト17 Program.cs Console.WriteLine(""); Console.WriteLine("-start- daigoと同じ書籍を購入したCustomerをリストします"); var ret = await manager.GetSameOrderCustomers("daigo"); foreach (var vertex in ret) { Console.WriteLine(vertex.id); } Console.WriteLine("-end-");
リスト17の実行結果 -start- daigoと同じ書籍を購入したCustomerをリストします tanaka kido -end-
6.4.6 Vertex~Edge~Vertexを辿る(2)
最後に以下のクエリーを行います。
- daigo が購入した書籍と同じものを購入したCustomerが購入した別の本、つまりdaigoへのリコメンド書籍を取得します。
リスト18がGremlinManager.GetRecomendBooks()の実装になります。
リスト 18 GremlinManager.cs public async Task<List<dynamic>> GetRecomendBooks(string id) { List<dynamic> result = new List<dynamic>(); string gr = string.Format( "g.V('{0}').as('self').outE('order').inV().as('sourceBook').inE().outV().where(neq('self')).outE('order').inV().where(neq('sourceBook'))", id); var query = this.client.CreateGremlinQuery<dynamic>( this.collection, gr); if (query.HasMoreResults) { foreach (dynamic item in await query.ExecuteNextAsync()) { result.Add(item); } } return result; }
GremlinManager.GetRecomendBooks()の呼び出しはリスト19になります。
リスト19 Program.cs GremlinManager manager = new GremlinManager(); Console.WriteLine(""); Console.WriteLine("-start- daigoにおすすめの書籍をリストします"); var ret = await manager.GetRecomendBooks("daigo"); foreach (var vertex in ret) { Console.WriteLine("id: " + vertex.id); Console.WriteLine("title: " + vertex.properties.title[0].value); Console.WriteLine("author: " + vertex.properties.author[0].value); } Console.WriteLine("-end-");
6.5 まとめ
Cosmos DBのGremlin(グラフ)についての説明を行いました。
Gremlin言語については、ほんの一部の機能のみの説明にとどめましたが、SQLと同様に多数の構文が存在するため、以下の公式ドキュメントから確認していただくのが良いと思います。
また、本説明ではシングルパーティションコレクションを作成・使用しましたが、パーティショニングに関する考え方はDocumentDB / MongoDBの時と同様です。
では、次回は「Table API(データモデル)」の説明になります。
6.6 資料
本記事のサンプルは以下からダウンロード可能です。