CouchDB を探る

Web アプリケーションのためのドキュメント指向データベース

CouchDB とは何か

CouchDB はオープンソースのドキュメント指向データベース管理システムであり、アクセスには RESTful JSON (JavaScript Object Notation) API が使われます。「Couch」という言葉は「Cluster Of Unreliable Commodity Hardware」の頭字語であり、CouchDB の目標を反映しています。CouchDB の目標とは、極めてスケーラブルであると同時に、故障を起こしがちなハードウェア上で実行された場合でも高可用性と高信頼性を実現することです。CouchDB は元々 C++ で作成されましたが、2008年4月、フォルト・トレランスを強化するために、このプロジェクトは Erlang OTP プラットフォームに移行されました。

CouchDB は Linux® や Mac OS X を含め、ほとんどの POSIX システムにインストールすることができます。Windows® は現在正式にはサポートされていませんが、Windows プラットフォーム用の非公式なバイナリー・インストーラーの作成作業が行われています。CouchDB はソースからインストールすることもでき、パッケージ・マネージャー (Mac OS X の MacPorts など) が入手可能な場合にはパッケージ・マネージャーを使ってインストールすることもできます。

CouchDB は Apache Software Foundation によるトップ・レベルのオープンソース・プロジェクトであり、Apache ライセンスの V2.0 のもとでリリースされています。このオープンソース・ライセンスでは、著作権に関する注意事項と免責事項の範囲内で、他のソフトウェアに使用する目的でソース・コードを使用したり変更したりすることができます。大部分のオープンソース・ライセンスと同様、このライセンスではユーザーが必要に応じてソフトウェアを使用、変更、配布することができます。Apache ライセンスのコードを使用しているとの注意書きがあれば、変更したソフトウェアを Apache ライセンスのもとで提供する必要はありません。

ドキュメント指向データベースとリレーショナル・データベースとの違い

多くの人にとって、ドキュメント指向データベース管理システムの概念は、最初は理解しにくいものです。リレーショナル・データベース管理システムを長年扱ってきた人達の場合はなおさらです。その理由は、この 2 つのモデルの間には類似点がほとんどなく、大きくモデルが異なっているからです。

当然のことですが、ドキュメント指向データベースは一連の自己完結型のドキュメントで構成されています。つまり、ドキュメントに関するデータはすべて、そのドキュメント自体の中に保存されます。リレーショナル・データベースの場合とは異なり、関連するテーブルにドキュメントのデータが保存されるわけではありません。実際、ドキュメント指向データベースには、テーブルも、行も、列も、関係も、まったくありません。つまりドキュメント指向データベースにはスキーマがなく、データベースを実際に使う前に厳密なスキーマを定義する必要はありません。あるドキュメントに新しいフィールドを追加する場合には、単純にそのドキュメントにそのフィールドを含めることができ、そのようにしてもデータベースの中にある他のドキュメントに悪影響を与えることがありません。またこれは、ドキュメントの中の値を持たないフィールドに空のデータ値を保存する必要がない、という意味でもあります。

近く出版される本、『CouchDB: The Definitive Guide』(「参考文献」を参照) では、「実際のドキュメント」の例として名刺を取り上げ、名刺を (リレーショナル・データベースではなく) ドキュメント指向データベースの中に記述する方法を説明しています。名刺のデータを保存する場合、リレーショナル・データベースでは 4 つ以上のテーブルを使います。つまり「人」用のテーブル、「会社」用のテーブル、「連絡先詳細」用のテーブル、そして名刺自体のためのテーブルです。これらのテーブルはすべて、厳密に定義された列とキーを持ち、結合を使うことでデータを組み合わせます。

リレーショナル・データベースには、個々のデータが厳密に定義されるという利点がありますが、その一方で、後で変更が必要になった場合には、データ構造に柔軟性がないため、変更するのが困難です。またリレーショナル・データベースの場合、さまざまな状況にレコードを適応させることができません。例えば、ある人は FAX 番号を持っており、ある人は持っていないかもしれません。名刺に「FAX: なし」などと書くことはあり得ず、そうした場合には FAX については記載しません。

ドキュメント指向データベースでは、それぞれの名刺が独自のドキュメントの中に保持され、各ドキュメントが使用フィールドを定義することができます。そのため、FAX 番号を持たない人は FAX の値を定義する必要がなく、一方で FAX 番号がある人は名刺にその番号を記載したければ FAX の値を定義することができます。

この 2 つのタイプのデータベースが異なる点には、一意識別子の保存方法もあります。リレーショナル・データベースの場合には、自動インクリメント機能やシーケンス・ジェネレーターによって生成される主キーの概念を使うことが普通です。当然ながら、これらの識別子は、その識別子が使われるテーブルやデータベースに対して一意であるにすぎないため、他のテーブルやデータベースで同じ識別子を使うことができます。この場合、別のネットワーク上にある 2 つのデータベースに対して同時に更新操作が行われると、その 2 つのデータベースが両方とも次の一意識別子を正確に取得することができません。一方、CouchDB にはオート・インクリメント機能やシーケンス機能がありません。代わりに CouchDB では、すべてのドキュメントに UUID (Universally Unique Identifier: 汎用一意識別子) を割り当てるため、別のデータベースが誤って同じ一意識別子を選択することはあり得ません。

ドキュメント指向のデータベースとリレーショナル・データベースとのもう 1 つの重要な違いは、ドキュメント指向のデータベースが結合をサポートしていない点です。これは CouchDB には主キーも外部キーもないことによる結果です。結合の元となるキーがないのです。これは CouchDB データベースから相互に関連する一連のデータを取得することはできないという意味ではありません。ビューと呼ばれる機能を利用すると、実際にはデータベース自体の中で定義されていない任意の関係をドキュメント間に作成することができます。これはつまり、データベース・レイヤーの中で事前に関係を定義しなくても、典型的な SQL の結合クエリーの利点をすべて利用できるということです。

ドキュメント指向のデータベースはリレーショナル・データベースとは異なる方式で動作しますが、リレーショナル・データベースを置き換えるわけではないことに注意することが重要です。CouchDB はリレーショナル・データベースの置き換えではなく、ウィキやブログ、ドキュメント管理システムなど、ドキュメント指向モデルの方が適しているプロジェクトで従来のリレーショナル・データベースの代わりとなる手段なのです。

CouchDB の動作

CouchDB は、B 木による強力なストレージ・エンジンの上に構築されています。このストレージ・エンジンによって CouchDB の中のデータはソートされた状態に維持され、また対数的な償却時間で検索、挿入、削除を行うメカニズムが提供されます。CouchDB では内部のデータ、ドキュメント、ビューのすべてに対してこのストレージ・エンジンを使っています。

CouchDB のデータベース構造にはスキーマがないため、CouchDB ではビューを使うことでドキュメント間に任意の関係を作成し、集約機能とレポート作成機能を実現します。こうしたビューによる結果は、分散コンピューティングを使用して大規模なデータ・セットの処理と生成を行うモデルである、 Map/Reduce を使って計算されます。Map/Reduce モデルは Google によって導入されたもので、Map ステップと Reduce ステップに分解することができます。Map ステップではマスター・ノードがドキュメントを受け取り、問題は副問題に分割されます。次にこれらの副問題はワーカー・ノードに分散され、ワーカー・ノードはそうした副問題を解決して結果をマスター・ノードに返します。Reduce ステップではマスター・ノードがワーカー・ノードからの結果を受け取り、それらの結果を組みあわせて全体としての結果と元の問題への解を得ます。

CouchDB の Map/Reduce 関数によってキーと値のペアが生成され、CouchDB はそれらのペアをキーによるソート順で B 木エンジンに挿入します。これによって B 木の中でのキーによる参照が非常に効率的になり、また操作のパフォーマンスが高くなります。これはまた、多くのノードにまたがってデータを分割することができ、そのように分割しても各ノードを個別に照会できる機能は損なわれないということでもあります。

従来のリレーショナル・データベース管理システムでは、場合によってはロック機能を使って並列処理を管理し、あるクライアントがデータを更新している間は別のクライアントがそのデータにアクセスできないようにしています。こうすることによって複数のクライアントが同時に同じデータ・セットを変更してしまう事態は防ぐことができますが、多くのクライアントが同時にシステムを使用している場合には、どのクライアントがロックを受け取るかの判断やロック・キューの順序維持のためにデータベースの動作が遅くなりがちです。CouchDB ではロック・メカニズムがない代わりに、各クライアントにデータベースの最新スナップショットが与えられる MVCC (Multiversion Concurrency Control) と呼ばれる方法が使わます。これはつまり、トランザクションがコミットされるまで、他のユーザーには何も変更されていないように見えるということです。最近のほとんどのデータベースは (Oracle (V7 以降)、MySQL (InnoDB を使用した場合)、Microsoft® SQL Server 2005 以降など)、ロック・メカニズムから MVCC に移行し始めています。

ドキュメント: CouchDB データベースを構成するブロック

CouchDB データベースは一意の名前を付けてドキュメントを保存します。そして保存したドキュメントをアプリケーションが読み取り、変更するために RESTful JSON API を提供しています。CouchDB データベースのすべてのデータはドキュメントの中に保存され、各ドキュメントは任意の数のフィールドで構成されます。これはつまり、各ドキュメントは他のドキュメントの中で定義されていないフィールドを持てるということです。言い換えると、ドキュメントは厳密なデータベース・スキーマに従う必要がないということです。

また各ドキュメントには、一意のドキュメント ID やリビジョン番号などのメタデータ (データに関するデータ) も含まれています。ドキュメントのフィールドには、テキスト・ストリング、数字、ブール値 (真/偽) など、さまざまなタイプのデータを含めることができ、フィールドのサイズに制限はありません。ただし、各フィールドの名前は一意でなければなりません (ドキュメントは同じ名前のフィールドを 2 つ持つことはできません)。

CouchDB ドキュメントが変更されると、その変更が実際に既存のドキュメントに加えられるのではなく、そのドキュメント全体の新しいバージョンが作成されます (新しいバージョンは「リビジョン」と呼ばれます)。つまり、ドキュメントの変更に関する完全な履歴がデータベースによって自動的に保持されるということです。このドキュメントのリビジョン管理システムの動作はウィキや Web ベースのドキュメント管理システムでのリビジョン管理とほとんど同じですが、データベース・レベルで自動的にリビジョン処理が行われる点が異なります。

CouchDB にはロック・メカニズムがなく、2 つのクライアントが同時に同じドキュメントをロードして編集することができます。ただし一方のクライアントが変更を保存すると、もう一方のクライアントで変更を行ってそれを保存しようとすると編集競合の警告が表示されます。この競合を解決するためには、新しいバージョンのドキュメントをロードして編集内容を適用しなおし、再度保存を試みます。CouchDB では、ドキュメントの更新が「オール・オア・ナッシング」つまり成功か失敗のいずれかであると保証することで、データの一貫性を維持しています。更新内容の一部が保存されたドキュメントがデータベースの中に存在することはあり得ません。

ビュー: CouchDB から有用な情報を取得する

CouchDB は元来構造を持たず、厳密なスキーマがないことで柔軟性やスケーラビリティーの点では優れていますが、一方で実際のアプリケーションで非常に使いにくくなりかねません。リレーショナル・データベースの場合、普段使用するアプリケーションにとっては、厳密に定義されたテーブル間の関係が重要であり、その関係によってデータに意味を持たせます。ただし、ハイパフォーマンスが要求される場合にはマテリアライズド・ビューを作成することでデータを正規化されていない状態にします。ドキュメント指向のデータベースの場合、多くの点でこの逆を行います。つまりドキュメント指向のデータベースはデータをフラットなアドレス空間に保存します。これはまったく正規化されていない状態のデータ・ウェアハウスとほとんど同じです。そしてビュー・モデルを提供することでデータに構造を追加し、そのデータを集約することで有用な意味を持たせるのです。

CouchDB のビューは要求に応じて作成され、ビューを使ってデータベースの中にあるドキュメントの集約、結合、レポート作成が行われます。このビューは動的に作成され、データベースの中にあるドキュメントには何も影響を与えません。ビューはデザイン・ドキュメントの中で定義され、さまざまなインスタンスで複製することができます。これらのデザイン・ドキュメントには JavaScript 関数が含まれており、これらの関数が MapReduce の概念を使ってクエリーを実行します。ビューの Map 関数はドキュメントを引数に取り、一連の計算を実行することで、ビューに表示するデータを判断します。ビューに Reduce 関数がある場合には Reduce 関数を使って結果を集約します。ビューには一連のキーと値が渡され、ビューはそれらを組み合わせて 1 つの値にします。

リスト 1 は CouchDB ビューの Map 関数と Reduce 関数の例です。これらの関数は添付ファイルがあるドキュメントの数をカウントします。

リスト 1. 典型的な CouchDB ビュー

map: function(doc) { if (doc._attachments) { emit("with attachment", 1); } else { emit("without attachment", 1); } } reduce: function(keys, values) { return sum(values); }

CouchDB のビューは、デザイン・ドキュメントの中に永続的に保存されるビューの場合もあり、要求に応じて実行される一時的なビューの場合もあります。一時的なビューは大量のリソースを消費するため、データベースの中に保存されるデータの量が増えるにつれて動作が遅くなります。そのため、ほとんどの場合はデザイン・ドキュメントの中で CouchDB のビューを作成する必要があります。

RESTful JSON API

CouchDB にはデータベースからデータを取得するための手段として API が用意されています。この API を利用するためには HTTP の GET リクエストと POST リクエストを使うことができ、こうしたリクエストに対して API は JSON を使った JavaScript オブジェクトの形でデータを返します。このため、どのような言語でアプリケーションが作成されている場合も容易にデータベースを操作することができます。また、Ajax リクエスト・オブジェクトを作成可能な JavaScript フレームワーク (Prototype、JQuery、ExtJS など) を使うことも可能であり、Web アプリケーション用にサーバー・サイドの言語を使う必要はありません。

この API から返されるそのままの JSON レスポンスについて説明するために、(そしてその説明を簡単にするために) この記事では curl というコマンドライン・ツールを使うことにします。このツールを使うと、 GET 、 POST 、 PUT 、 DELETE リクエストを発行することができ、また Web サーバー (この場合はローカルにインストールされた CouchDB サーバー) から受信したそのままの HTTP レスポンスを表示することができます。

リクエストとして curl http://127.0.0.1:5984/ を実行すると、 {"couchdb":"Welcome","version":"0.8.1-incubating"} というレスポンスが返されます。この単純な API 呼び出しは単純な GET リクエストであり、またレスポンスはインストールされている CouchDB のバージョンを示しています。発行するリクエストのタイプを明示的に定義するためには curl の -X パラメーターを使います (例えば curl -X GET http://127.0.0.1:5984/_all_dbs のようにします)。すると、 [] という結果が返されます。

この例では、CouchDB サーバー上にあるすべてのデータベースの一覧を返す、特別な CouchDB ビューの URI を要求しました。もし実際に何らかのデータベースを作成してあったとしたら、このリクエストによってデータベース名の配列が返されたはずです。しかしここでは空の JavaScript 配列が返されています。今度はデータベースをいくつか作成し、リクエストとして curl -X PUT http://127.0.0.1:5984/fruit を実行すると、先ほどとは異なる結果が得られるはずです。

受信されるレスポンスは {"ok":true} です。次に 2 番目のリクエストとして curl -X PUT http://127.0.0.1:5984/vegetables を実行すると、先ほどと同じレスポンスが受信されます。ここで再度 curl -X GET http://127.0.0.1:5984/_all_dbs を実行してデータベースの一覧を要求します。すると今度は作成したデータベースが含まれた結果が得られます ( ["fruit","vegetables"] )。

これらのデータベースを作成すると、これらのデータベースはブール値の真である属性「ok」を返しました。これは実行した操作が正常に行われたことを示します。では思っていた通りの結果が得られなかった場合には何が起こるのでしょう。CouchDB サーバーを失敗させる試みとして、既存のデータベースの 1 つと同じ名前を持つデータベースを作成してみましょう ( curl -X PUT http://127.0.0.1:5984/fruit )。

すると今度はレスポンスとして {"error":"database_already_exists","reason":"Database \"fruit\" already exists."} が得られます。

これを見るとわかるように、CouchDB は指定されたデータベースを作成しようとしましたが、そのプロセスでエラーが発生しました。そのため、CouchDB は発生したエラーのエラー・コードの値 (この場合は "database_already_exists" ) を付けて属性 "error" を返しました。もちろん、実際のアプリケーションであれば、CouchDB サーバーからのすべてのレスポンスを調べて 属性 "error" がないかどうかを確認し、エラー・コードが見つかった場合はその値に応じてユーザーに理解しやすいエラー・メッセージを表示するはずです。

ここで例えば、vegetables データベースが必要なくなったので削除したいとします。CouchDB の中のデータベースを削除するためには、単純にベース URI にデータベースの名前を付加して HTTP の DELETE リクエストを発行します (例えば curl -X DELETE http://127.0.0.1:5984/vegetables など)。すると、先ほど実行した PUT リクエストの場合と同じく、正常に処理が行われたことを示すレスポンスが得られます。今度は GET を使ってデータベースの一覧を取得します ( curl -X GET http://127.0.0.1:5984/_all_dbs )。すると ["fruit"] というレスポンスが得られます。

データベースにデータが何もなくては意味がありません。リスト 2 のリクエストは「apple」というドキュメントを作成します。

リスト 2. apple というドキュメントを作成する

curl -X PUT http://127.0.0.1:5984/fruit/apple \ -H "Content-Type: application/json" -d {}

するとサーバーから {"ok":true,"id":"apple","rev":"1801185866"} というレスポンスが返されます。

これでドキュメントが作成できたので、このドキュメントをデータベースから取得しましょう ( curl -X GET http://127.0.0.1:5984/fruit/apple )。すると CouchDB は {"_id":"apple","_rev":"1801185866"} というレスポンスを返します。

最後の API 呼び出しとして、ある特定のデータベース (この場合は fruit データベース) に関する情報を取得します。そのためには curl -X GET http://127.0.0.1:5984/fruit を実行します。サーバーから受信されるレスポンスから、このデータベースに関する興味深い情報が得られます。

リスト 3. サーバーからのレスポンス

{"db_name":"fruit","doc_count":1,"doc_del_count":0,"update_seq":1, "compact_running":false,"disk_size":14263}

このセクションでは、CouchDB が提供している RESTful JSON API として利用可能な API メソッドをいくつか紹介しました。実際にはこうした HTTP リクエストをハンドコーディングする必要はなく、皆さんが選択する適当なプログラミング言語やスクリプト言語がコーディングしてくれるはずです。

また CouchDB には、CouchDB の管理ツールとして使用できる Futon という Web アプリケーションも含まれています。このアプリケーションを使うと、データベースやドキュメント、ドキュメントのリビジョンなどを自由自在に管理することができます。CouchDB をローカル・マシンの 5984 ポート (デフォルト) にインストールした場合には、ブラウザーで http://127.0.0.1:5984/_utils/ にアクセスすれば Futon を利用することができます。図 1 はFuton が動作している様子を示しています。

図 1. CouchDB の Futon ユーティリティーの動作画面

リレーショナル・データベース管理システムに慣れた人達は、CouchDB で提供されている RESTful API を初めて見ると尻込みしたくなるかもしれませんが、この API にはデータベースとやり取りするためのユニークで非常に使いやすい手段が用意されています。従来からのデータベース・システムでは通常、何らかの SQL クライアントを使ってデータベースに接続する必要があります。接続できると、その SQL クライアントはデータ取得用の一連の SQL 文を受け付け、CRUD (Create、Update、Delete) 操作を行います。CouchDB が RESTful JSON API を採用しているおかげで、ユーザーは HTTP プロトコルをサポートする任意のソフトウェアを使って CouchDB に接続することができます。実際、最近のプログラミング言語やスクリプト言語には HTTP プロトコル用のインターフェースが何らかの形で用意されているため、ほとんどすべての開発プロジェクトで CouchDB を使うことができます。

まとめ

Apache CouchDB プロジェクトはまだ初期の段階にあり、CouchDB もアルファ段階のソフトウェアにすぎません。とは言え、CouchDB は Web アプリケーションや iPhone アプリケーション、Facebook アプリケーションなどによく使われるようになっています。これまで、強力なウィキ、ブログ、ディスカッション・フォーラム、ドキュメント管理などのソフトウェアでは、リレーショナル・データベースを使いこなすことでドキュメント指向のデータを可能な限り効率的に保存していました。しかし CouchDB の安定リリースが入手できるようになると、これらのソフトウェアが利用するデータベースの選択肢として CouchDB はリレーショナル・データベースよりも魅力的なソリューションとなり、ドキュメントのリビジョン管理やスキーマ要件の頻繁な変更などに伴う問題を解消することができます。

一般的に、CouchDB に対する現在までの反応は概ね好意的ですが、多くの人達がブログやフォーラムで、リレーショナル・データベースとドキュメント指向データベースのどちらが優れているかをもっと議論する必要があると繰り返し指摘しています。単純な 1 つの事実として、CouchDB はリレーショナル・データベースを置き換えるものではなく、データベース開発の新しい標準になろうというわけでもありません。当然のことですが、CouchDB は単純であるため、DB2 や Oracle などの強力さとは比べものにならないケースは多数あります。しかしそれ以外の多くのケースでは何よりも単純さがデータベースに求められ、手軽に迅速な処理が必要な場合には従来の RDBMS は過剰であり、またリソースを消費しすぎるのです。

ダウンロード可能なリソース

関連トピック