データ ポイント

Microsoft Azure DocumentDB の概要

Julie Lerman

コード サンプルのダウンロード(VB)

Julie Lerman2011 年 11 月号で「ドキュメント データベースとは」(msdn.microsoft.com/magazine/hh547103、英語) というコラムを執筆しました。そのコラムでは、MongoDB、CouchDB、RavenDB という著名なドキュメント データベースをいくつか取り上げました。この 3 種類の NoSQL データベースは、どれもいまだに広く利用されています。その当時、マイクロソフトは、キーと値のペアを基盤とする NoSQL データベースとして、Microsoft Azure テーブル ストレージを用意していましたが、ドキュメント データベースは市場に投入していませんでした。しかし 2014 年 8 月、Microsoft Azure DocumentDB をリリースしました。名前が示すように、これは Azure 上で利用できる NoSQL ドキュメント データベース サービスです。

今回は、この Azure DocumentDB の概要を紹介します。このコラムが、読者の皆さんが自ら Azure DocumentDB を調べる良いきっかけになればさいわいです。このサービスは Azure 上で利用でき、2015 年 4 月 8 日にプレビューから一般公開されるリソースへと昇格しましたが、それ以前から既に使用されていました。たとえば、bit.ly/1GMnBd9 (英語) には、ソリューションの一部として DocumentDB を実装した SGS という会社の優れたユーザー導入事例があります。このプロジェクトに参加した開発者の 1 人からその事例に言及するツイートが送られ、同社が Document DB に非常に満足しているようすが示されています。

ドキュメント データベースとは

以前のコラムでこの疑問に詳しく答えました。今回は説明を簡潔にするため、この疑問についてはそのコラムを参照することをお勧めします。ドキュメント データベースは、データをドキュメントとして (大半は個別の JSON ドキュメントとして) 格納します (MongoDB は JSON ドキュメントを BSON というイナリ フォーマットに圧縮するため、やや異なります)。このストレージでは、データベース全体を移動して関連データを集める必要がないため、大容量のデータを操作する際のパフォーマンスが非常に高くなります。関連するデータは、1 つの JSON ドキュメントに結合することができます。ドキュメント データベースなどの NoSQL データベースの重要なもう 1 つの特徴は、スキーマを使用しない点です。データの格納と取得のために、テーブルのスキーマを事前定義する必要があるリレーショナル データベースとは異なり、ドキュメント データベースでは各ドキュメントが独自のスキーマを定義できます。そのため、データベースはドキュメントのコレクションとして構成されます。図 1 は、個別の JSON ドキュメントがどのようになっているかを簡単に示しています。プロパティ名に値を添えて指定し、関連するデータを含んでいることがわかります。

図 1 簡単な JSON ドキュメント

{
  "RecipeName": "Insane Ganache",
  "DerivedFrom": "Café Pasqual’s Cookbook",
  "Comments":"Insanely rich. Estimate min 20 servings",
  "Ingredients":[
    {
      "Name":"Semi-Sweet Chocolate",
      "Amount":"1.5 lbs",
      "Note":"Use a bar, not bits. Ghiradelli FTW"
    },
    {
      "Name":"Heavy cream",
      "Amount":"2 cups"
    },
    {
      "Name":"Unsalted butter",
      "Amount":"2 tbs"
    }
],
  "Directions": "Combine chocolate, cream and butter in the top ..."
}

上記のデータはすべて JSON 形式の自己記述型であるだけではなく、関連データ (原材料) も含んでいます。このようなドキュメント データベースの一部に共通するもう 1 つの特徴として、すべて HTTP 呼び出しを使ってアクセスできます。この特徴については後ほど説明します。繰り返しになりますが、ドキュメント データベースについてのここまでの説明は、以前のコラムに詳しく記載しています。

Azure DocumentDB の構造

図 1 は、ドキュメント データベースに格納される代表的なドキュメントの外観を示しています。ただし、Azure DocumentDB を構成するのはこのようなドキュメントだけではありません。Azure DocumentDB ではドキュメントを 1 つのリソースと考えます。ドキュメントを、コレクションにグループ化することもあります。そのコレクションも、DocumentDB のリソースです。ドキュメントと同じように、コレクションも HTTP 呼び出しを使って作成、更新、削除、およびクエリできます。実際、DocumentDB 全体はさまざまな種類のリソースから構成されます。コレクションは DocumentDB の 1 つのデータベースにグループ化されます。1 つの DocumentDB アカウントで複数のデータベースを保持することができ、複数のアカウントを所持することも可能です。

このようなリソースはすべて、DocumentDB という 1 つの世界の中に存在する主要な構成要素です。それ以外に、ドキュメントに付属する一連のリソースもあります。このようなリソースには、リレーショナル データベースで使い慣れたストアド プロシージャ、ユーザー定義関数 (UDF)、インデックス、トリガーなどの名前が付けられています。

ドキュメントに関連する最後のリソースの 1 つは添付ファイルで、JSON ドキュメントに添付される任意の種類のバイナリです。添付ファイルのバイナリは Azure Blob ストレージに存在しますが、メタデータは DocumentDB に格納されるため、さまざまなプロパティの添付ファイルをクエリできます。

また、DocumentDB にはセキュリティ機能が組み込まれており、そのスコープ内ではユーザーやアクセス許可もリソースになるため、ドキュメントと同じ方法で操作することができます。

DocumentDB の操作

Azure DocumentDB のリソースを操作するには、SQL、REST API、.NET API のようなさまざまなクライアント API など、さまざまな方法があります。.NET API は LINQ を使ってデータベースにクエリできるようにします。クエリの詳細については、http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-sql-query/ を参照してください。

Azure ポータルでは DocumentDB のアカウントの作成や管理を行います (http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-create-account/ のドキュメントを参照してください)。このポータルでは自身の DocumentDB を管理できるだけでなく、ドキュメント エクスプローラーやクエリ エクスプローラーを使ってドキュメントの表示やクエリも実行できます。クエリ エクスプローラーでは、SQL 構文を使用できます (図 2 のシンプルなクエリ参照)。

Azure ポータルnoクエリ エクスプローラーでのドキュメントのクエリに使用する SQL 構文
図 2 Azure ポータルnoクエリ エクスプローラーでのドキュメントのクエリに使用する SQL 構文

この SQL をアプリケーションで使うこともできます。たとえば、以下は「DocumentDB を使用した Node.js Web アプリケーションの作成」 (http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-nodejs-application/) から一部抜粋したコードで、クエリを SQL 構文で表現しています。

getOrCreateDatabase: function (client, databaseId, callback) {
  var querySpec = {
    query: 'SELECT * FROM root r WHERE r.id=@id',
    parameters: [{
      name: '@id',
      value: databaseId
    }]
  };

このような初期の DocumentDB では、この SQL 構文が制限されていることがわかります。ただし、既存の SQL は UDF を使って補完できます。たとえば、CONTAINS(r.name, "Chocolate") などの文字列を評価する述語を作成するために、独自の CONTAINS 関数を作成することができます。

Azure の他の多くのリソースと同様に、Azure DocumentDB にはネイティブな REST API があり、HTTP を使用したクエリと更新が可能です。すべてのリソースには固有の URI があります。以下は DocumentDB の特定のアクセス許可に対する HTTP 要求の例です。

GET https://contosomarketing.documents.azure.com/dbs/ruJjAA==/users/ruJjAFjqQAA=/permissions/ruJjAFjqQABUp3QAAAAAAA== HTTP/1.1
x-ms-date: Sun, 17 Aug 2014 03:02:32 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig%3dGfrwRDuhd18ZmKCJHW4OCeNt5Av065QYFJxLaW8qLmg%3d
x-ms-version: 2014-08-21
Accept: application/json
Host: contosomarketing.documents.azure.com

REST API を直接操作することに関する詳細については、https://msdn.microsoft.com/ja-jp/library/azure/dn781481.aspx を参照してください。しかし、REST API の操作は、非常に面倒になる可能性があります。.NET、Node.js、JavaScript、Java、Python など、Azure DocumentDB の操作に使用できるクライアント API が既に数多く存在します。bit.ly/1Cq9iVJ から SDK をダウンロードして、ドキュメントをお読みください。

.NET 開発者にとっては、LINQ を使ってクエリできる .NET ライブラリが便利です。現在サポートされている LINQ の表現は、Queryable.Where、Queryable.Select、および Queryable.SelectMany ですが、LINQ メソッドのサポートは時間と共に確実に増えていくでしょう。

DocumentDB に対してなんらかの操作を行う前に、操作に使用するアカウント、データベース、およびコレクションを指定する必要があります。たとえば、次のコードは .NET API を使用して Microsoft.Azure.Documents.ClientDocument を定義しています。

string endpoint = ConfigurationManager.AppSettings["endpoint"];
string authKey = ConfigurationManager.AppSettings["authKey"];
Uri endpointUri = new Uri(endpoint);
client = new DocumentClient(endpointUri, authKey);

このサンプル コードは、Azure ドキュメント ページ (https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-dotnet-application/) の ASP.NET.MVC と DocumentDB のチュートリアルのコードから引用しています。このチュートリアルでは Azure ポータルで DocumentDB のアカウントを作成するところから始めて、各手順を順番に説明しています。このチュートリアルや前述の Node.js の資料など、さまざまな言語で DocumentDB を操作するデモのいずれかを参考にすることを強くお勧めします。サンプル アプリケーションには、Item クラスという 1 つの型があります (図 3 参照)。

図 3 Item クラス

public class Item
  {
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }
    [JsonProperty(PropertyName = "descrip")]
    public string Description { get; set; }
    [JsonProperty(PropertyName = "isComplete")]
    public bool Completed { get; set; }
  }

item クラスの各プロパティが JsonProperty PropertyName を指定しているのがわかります。これは必須ではありませんが、これによって .NET クライアントは、格納された JSON データと Item 型とをマップできるようになります。また、これによって、データベースでの名前とは無関係に、クラスのプロパティの名前を付けることができるようになります。定義済みのクライアントを使うことで、既知のデータベース ID を指定して Microsoft.Azure.Documents.Database のインスタンスを返す LINQ クエリを表現できます。

var db = Client.CreateDatabaseQuery()
               .Where(d => d.Id == myDatabaseId)
               .AsEnumerable()
               .FirstOrDefault();

そこからデータベース内にコレクションを定義し、最終的には以下のような LINQ 表現によってそのコレクションをクエリして、1 つの JSON ドキュメントを返すことができます。

return Client.CreateDocumentQuery(Collection.DocumentsLink)
             .Where(d => d.Id == id)
             .AsEnumerable()
             .FirstOrDefault();

.NET API に含まれるさまざまなオブジェクトでも、CreateDocumentAsync、UpdateDocumentAsync、DeleteDocumentAsync の CUD メソッドを使って、ドキュメントの挿入、更新、削除の操作が可能です。これらの CUD メソッドは、HTTP 呼び出しを REST.API にラップしたものです。クエリと同様に、ストアド プロシージャや添付ファイルなど、他の種類のリソース用の関連 CUD メソッドがあります。

CAP への新たな対応

DocumentDB が他のドキュメント データベースと一線を画す、興味深い特徴の 1 つは、一貫性を調整できる点です。ドキュメント データベースに関する以前のコラムで CAP 定理を取り上げ、分散システムでは 3 つの保証のうち、同時に 2 つの保証しか満たすことはできないと説明しました。CAP 定理の 3 つの保証とは、一貫性 (Consistency)、可用性 (Availability)、および分断耐性 (Partition-tolerance) です。リレーショナル データベースは、可用性を犠牲にして (トランザクションの完了を待機するなど)、一貫性を確保しています。これに対して NoSQL データベースは、最終的な一貫性の耐性を高くします。つまり、可用性を重視するため、データが必ずしも最新ではない可能性があります。

Azure DocumentDB では、CAP 定理の対処に新しい方法を用意し、一貫性のレベルを調整できるようにして、可用性と分断耐性の両方の保証を同時に満たすチャンスを提供します。一貫性のレベルは、Strong、Bounded staleness、Session、Eventual の 4 つから選択でき、データベースだけではなく操作単位に定義することができます。一貫性を確保するかしないかではなく、一貫性のレベルを調整することでソリューションにおけるニーズを満たします。一貫性のレベルの詳細については、http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-consistency-levels/ を参照してください。

サーバー側 JavaScript

多くの開発者は、おそらくリレーショナル データベースのストアド プロシージャや UDF に精通しています。そのため、他のドキュメント データベースとは異なり、Azure DocumentDB ではこのような概念が導入されています。ただし、これらは JavaScript で記述されています。JavaScript は JSON をネイティブに操作できるため、JSON ドキュメントなどのリソースの操作を極めて効率的に行います。変換や翻訳、マッピングは必要はありません。ストアド プロシージャや UDF トリガーの形式でサーバー側 JavaScript を用意するもう 1 つのメリットは、複数のドキュメントにまたがるアトミック トランザクションを可能にすることです。アトミック トランザクションでは、1 つのプロセスが失敗した場合にそのトランザクションのスコープ内のすべてがロールバックされます。ストアド プロシージャや UDF を定義する方法は、SQL Server のようなリレーショナル データベースで行っていた方法とはまったく異なります。この機能はポータルにはまだ用意されていません。代わりに、クライアント側コードでサーバー側コードを定義します。bit.ly/1FiNK4y (英語) の Azure DocumentDB .NET Code Samples での Server-Side Script を参考にすることをお勧めします。

ここからは、ストアド プロシージャを作成して格納してから実行する方法について説明します。図 4 は、.NET API コードを使用してストアド プロシージャを DocumentDB に挿入する例を示しています。

図 4 DocumentDB へのストアド プロシージャの挿入

public static async Task<StoredProcedure> InsertStoredProcedure() {
  var sproc = new StoredProcedure
              {
                Id = "Hello",
                Body = @"
                  function() {
                    var context = getContext();
                    var response = context.getResponse();
                    response.setBody('Stored Procedure says: Hello World');
                  };"
              };
  sproc = await Client.CreateStoredProcedureAsync(setup.Collection.SelfLink, sproc);
  return sproc;
}

単純にするために、すべてのロジックを 1 つのメソッドにカプセル化しています。今回の StoredProcedure オブジェクトは ID と Body で構成します。Body はサーバー側の JavaScript です。プロシージャごとに JavaScript ファイルを作成して、StoredProcedure オブジェクトの作成時にファイルのコンテンツを読み取ることもできます。コードでは StoredProcedure オブジェクトがデータベース内にまだ存在していないものとしています。付属のダウンロード サンプルでは、データベースをクエリするカスタム メソッドを呼び出して、プロシージャがまだ存在しないことを確かめてから挿入を行っています。最後に (DocumentClient インスタンスを提供する) SetupDocDb<T>.Client プロパティを使って、以前ドキュメントにクエリしたのと同様の方法で、ストアド プロシージャを作成します。

これでデータベース内にストアド プロシージャが存在するようになり、使用することができます。この方法は SQL Server での作業方法と異なるため、頭を切り替えるのが少し難しいかもしれません。プロシージャの ID が "Hello" であることはわかりますが、ExecuteStoredProcedureAsync を呼び出すときにこの ID を特定するのは現在の API では不十分です。すべてのリソースには DocumentDB によって作成される SelfLink があります。SelfLink は変更できないキーで、DocumentDB の REST 機能をサポートします。これによりすべてのリソースが変更されない HTTP アドレスを持つことが保証されます。その SelfLink が、実行するストアド プロシージャをデータベースに指示する必要があります。つまり、まず既知の ID ("Hello") を使用してデータベースにクエリして、ストアド プロシージャを見つけなければなりません。その結果、その SelfLink 値を見つけることができます。このワークフローは、開発者と、SelfLink に必要な作業をなくすためにしくみを変えようとしている DocumentDB チームが摩擦を起こす原因になっています。こうした変更は、本稿公開時点でも行われている可能性があります。しかし現時点では、すべての DocumentDB リソースと同様にロシージャをクエリすることになります。クエリには CreateStoredProcedureQuery メソッドを使用します。その後、SelfLink を使ってプロシージャを実行し、結果を取得します。

public static async Task<string> GetHello() {
  StoredProcedure sproc = Client.CreateStoredProcedureQuery(Collection.SelfLink)
    .Where(s => s.Id == "Hello")
    .AsEnumerable()
    .FirstOrDefault();
  var response =
    (await Client.ExecuteStoredProcedureAsync<dynamic>(sproc.SelfLink)).Response;
  return response.ToString();
}

UDF の作成も同様です。UserDefinedFunction オブジェクトで UDF を JavaScript として定義し、DocumentDB に挿入します。UDF がデータベース内に存在すれば、クエリでその機能を使用できます。当初は SQL 構文を CreateDocumentQuery メソッドのパラメーターとしてしか使えませんでしたが、2015 年 4 月始めの DocumentDB の公式リリース直前に LINQ サポートが追加されました。以下は、カスタム UDF を使用する SQL クエリの例です。

select r.name,udf.HelloUDF() AS descrip from root r where r.isComplete=false

この UDF は単純になんらかのテキストを出力するだけなので、パラメーターは受け取りません。

クエリは JSON データに対してサーバー上で処理されるため、クエリ内では JsonProperty 名を使用しているのがわかります。LINQ クエリでは、代わりに Item 型のプロパティ名を使用します。

本稿付属のダウンロード サンプルにも同様のクエリがありますが、今回の例の UDF は、サンプルでは HelloUDF になっています。

パフォーマンスとスケーラビリティ

パフォーマンスとスケーラビリティには、非常に多くの要因が影響します。データ モデルやパーティションの設計さえも、すべてのデータ ストアのパフォーマンスとスケーラビリティに影響する可能性があります。DocumentDB のデータのモデル化については、http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-modeling-data/ のわかりやすいガイダンスを参考にすることを強くお勧めします。このガイダンスでは、グラフ設計とリレーションシップのメリットとデメリット、およびそれらが DocumentDB のパフォーマンスとスケーラビリティに及ぼす影響について説明しています。このガイダンスを執筆し、DocumentDB チームの上級プログラム マネージャーを務める Ryan CrawCour が、読み取りパフォーマンスと書き込みパフォーマンスにメリットのあるパターンを示しています。実際には、Azure DocumentDB だけではなく、一般的なモデル設計に役立つガイダンスです。

データベースのパーティションをどのように選択するかは、読み取りと書き込みのニーズにも左右されます。DocumentDB でのデータのパーティション分割についての資料 (http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-partition-data/) では、DocumentDB のコレクションを使ったパーティションの定義や、必要なデータ アクセス方式に応じたコレクションの定義方法についての詳細なガイダンスが示されています。

パーティションのもう 1 つのメリットは、必要に応じてコレクションやデータベースを作成 (削除) できることです。DocumentDB は柔軟にスケール変換されます。つまり、リソースの完全なコレクションが自動的に包含されます。

インデックスもパフォーマンスに大きな影響を与えます。DocumentDB では、複数のコレクションにまたがるインデックス作成ポリシーをセットアップできるようにしています。インデックスを設定しない場合は、前述のように、クエリを実行する際にリソースの SelfLink と ID のみを使用します。既定のインデックス作成ポリシーは、クエリのパフォーマンスとストレージ効率のバランスを取るように努めますが、ポリシーをオーバーライドして必要なバランスを確保することができます。インデックスには一貫性もあります。つまり、インデックスを活用して検索すれば、新しいデータにすばやくアクセスすることができます。インデックスの詳細については、http://azure.microsoft.com/ja-jp/documentation/articles/documentdb-indexing-policies/ を参照してください。

無償ではないが、費用対効果は高い

パフォーマンスとスケーラビリティを管理することでデータへのアクセスに影響しますが、データを提供するコストにも影響します。DocumentDB は Azure サービスの一環として有償で提供されます。選択する 3 つのパフォーマンスレベルによって、価格は 3 種類に分かれます。サービス料金はマイクロソフトによって定期的に調整されるため、DocumentDB の価格詳細ページ (http://azure.microsoft.com/ja-jp/pricing/details/documentdb/) を直接確認するのが最も確実です。あらゆる NoSQL データベースと同様、DocumentDB は大容量データ用のデータ ストレージの提供を目指しているため、現実的なシナリオでリレーショナル データを操作するよりも、費用対効果を劇的に向上することができます。


Julie Lerman* は、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2010 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。彼女の Twitter (twitter.com/julielerman、英語) をフォローして、juliel.me/PS-Videos (英語) で彼女の Pluralsight コースをご覧ください。*

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Ryan CrawCour に心より感謝いたします。
Ryan CrawCour は、20 年にわたりデータベースに携わるベテランです。はるか昔に、SQL Server 4.2 用に初めてのストアド プロシージャの作成を開始しました。多くのカーソル、結合、ストアド プロシージャなどの後、NoSQL ソリューションの自由で魅力的な世界の探求を始めました。Ryan は現在、レドモンドの DocumentDB 製品チームのプログラム マネージャーとして、このまったく新しい NoSQL DaaS (サービスとしてのデータベース) の将来を形作るために取り組んでいます。