次の方法で共有


働くプログラマ

MongoDB と NoSQL を試す

Ted Neward

コード サンプルのダウンロード

Ted NewardMicrosoft .NET Framework が 2000 年に発表され、2002 年に初めてリリースされてから約 10 年、.NET の開発者たちはマイクロソフトが次々と繰り出す新たなものすべてに遅れまいと必死に取り組んできました。そればかりか、.NET を日常的に使用する開発者と、そうでもない開発者から成り立つ "コミュニティ" を離れた開発者によって、マイクロソフトが対応しない穴を埋める新たなものがいくつか生み出されています。このことは混沌や混乱を招いているに過ぎないと言い換えることもできますが、どちらの表現が妥当かは読者の皆さんの判断にお任せします。

マイクロソフトの庇護を離れたコミュニティから出現したこうした "新しい" ものの 1 つが NoSQL 活動です。これは、すべてのデータがなんらかの形のリレーショナル データベース システムに格納される、されるであろう、あるいはされなければならない、という考え方に公に異を唱える開発者のグループが行っている活動です。テーブル、行、列、主キー、外部キー制約、null にまつわる議論、主キーを自然なものにするかどうかなど、侵さざるべき聖域はありません。

今月から数か月にわたるシリーズでは、NoSQL 活動に携わる開発者が支持する主要ツールの 1 つ、MongoDB について調査します。MongoDB の Web サイトによると、この名称は「hu "mongo" us (とてつもなく大きい)」という単語に由来しているそうです (私がでっちあげたわけではありません)。今回は、MongoDB のほぼすべてについて取り上げようと思っています。MongoDB のインストール、調査、.NET Framework からの操作 (提供されている LINQ サポートを含む)、他の環境 (デスクトップ アプリケーション、Web アプリケーション、および Web サービス) からの使用、Windows 運用環境の管理者から怒りを買わないようにセットアップする方法などを取り上げます。

問題 (このコラムで取り上げる理由)

MongoDB の詳細に踏み込む前に、.NET Framework の開発者であれば、これから 30 分間の人生を犠牲にしてこのコラムを読み、ラップトップで例を理解していく必要があるかについて疑問を持つのは当然です。結局のところ、SQL Server には無償で再頒布可能な Express エディションがあります。このエディションでは、従来の企業中心またはデータセンター中心のリレーショナル データベースよりも軽量なデータ ストレージ オプションが提供され、当然ながら、アクセスを簡単にするたくさんのツールやライブラリ (マイクロソフト独自の LINQ と Entity Framework を含む) を利用できます。

問題は、リレーショナル モデルの長所、つまりリレーショナル モデルそのものが最大の弱点にもなることにあります。.NET、Java、またはまったく別のテクノロジを扱っているとしても、開発者であればそのほとんどが、わずか数年の経験を経ただけで、テーブル、行、列といういわゆる "四角形" のモデルにうまく収まらないものがあることを、苦痛の面持ちで詳しく説明することができるようになります。階層型のデータをモデル化しようとすると、熟練の開発者ですら完全にお手上げ状態になることがあります。おそらくこのような理由から、Joe Celko 氏が『SQL for Smarties, Third Edition』(Morgan-Kaufmann、2005 年、英語) という書籍を執筆しました。この書籍全体を通して、リレーショナル モデルで階層型データをモデル化する場合の考え方が説明されています。このことに、リレーショナル データベースはデータにとって融通性のない構造 (データベース スキーマ) を前提とするという基本的な "既知の事実" が加わると、データに対してアドホックな追加をサポートするという試みが厄介なものになります (では、挙手してください。このコラムを読んでいる方の中で、Notes (注釈) 列や、Note1、Note2、Note3 といった列を含むデータベースを扱ったことがある方はどのくらいいらっしゃいますか)。

NoSQL 活動に携わる開発者は、リレーショナル モデルには長所がないとか、リレーショナル データベースが廃れていくなどと言っているわけではありません。しかし、過去 20 年間におよぶ開発者生活の中での基本的な事実として、本質的に関連性のないデータ、またはほんのわずかしか関連性のないデータをリレーショナル データベースに頻繁に格納してきました。

ドキュメント指向のデータベースでは、"関係" ではなく "ドキュメント" を格納します。このドキュメントは、しっかりと組み合わされたデータのコレクションで、一般に、システム内の他のデータ要素には結び付けられません。たとえば、ブログ システム内の各ブログ エントリが互いに結び付けられることはまったくありません。一方のブログ エントリから他方のエントリを参照する場合でも、内部的にではなく、ユーザーのブラウザーによって逆参照されることを意図したハイパーリンク経由で結び付けられることがほとんどです。そのブログ エントリへのコメントは、そのブログ エントリの範囲内にすべて含まれます。ユーザーがコメントを投稿したエントリに関係なく、すべてのコメントをまとめて表示することはほとんどありません。

さらに、ドキュメント指向のデータベースは高いパフォーマンスが求められる環境や、同時実行性が高い環境に威力を発揮する傾向があります。MongoDB は特に高いパフォーマンスが求められる環境を対象としていますが、これとよく似た CouchDB は同時実行性の高いシナリオを目指しています。MongoDB と CouchDB はどちらも、複数のオブジェクトが関係するトランザクションをサポートしません。つまり、データベース内で 1 つのオブジェクトの同時変更はサポートしますが、一度に複数のオブジェクトの変更を試みると、それらの変更が短期間残され、これらの変更を垣間見ることができます。ドキュメントは自動更新されますが、複数のドキュメントにわたって更新するトランザクションの概念は存在しません。これは、MongoDB に耐久力がないということではありません。単に、SQL Server インスタンスと同様、MongoDB のインスタンスも電源障害に対して耐久性がないだけです。完全な ACID (原子性、一貫性、分離性、持続性) セマンティクスを必要とするシステムは、従来のリレーショナル データベース システムの方が適しています。したがって、近いうちに、(おそらく Web サーバー上にあるレプリケートされたデータやキャッシュされたデータを除く) ミッション クリティカルなデータは、MongoDB のインスタンス内で見ることはなくなるでしょう。

一般に、MongoDB は、迅速にアクセスでき、頻繁に使用されるデータを格納する必要があるアプリケーションやコンポーネントで適切に機能します。Web サイトの分析、ユーザーのお気に入りや設定、およびデータが完全に構造化されていないシステムや、データ構造を柔軟にする必要があるシステムには、MongoDB がうってつけです。MongoDB が運用データの主要データ ストアに十分対応できないというわけではありません。単に、従来の RDBMS が適していない領域だけでなく、MongoDB と RDBMS のどちらも機能する多くの領域に MongoDB が適しているという意味です。

手始めに

既に説明したように、MongoDB はオープン ソースのソフトウェア パッケージで、MongoDB の Web サイト (mongodb.com、英語) から簡単にダウンロードできます。ブラウザーでこの Web サイトを開くと、Windows でダウンロード可能なバイナリがまとめて表示されているページへのリンクがすぐに見つかります。このページの右側に、[Downloads] リンクがあります。または、直接リンクがお好みであれば、mongodb.org/display/DOCS/Downloads (英語) をご使用ください。このページに記載されているように、安定しているバージョンは 1.4.2 リリースです。すべてを含む .zip ファイルしかないので、相対的に言えば、インストールは実に簡単です。目的の場所にコンテンツを圧縮解除するだけです。

本当に、これだけです。

.zip ファイルを圧縮解除すると、bin、include、および lib という 3 つのディレクトリが展開されます。関心があるディレクトリは bin のみです。このディレクトリには 8 つの実行可能ファイルが含まれています。依存関係にある他のバイナリ (ランタイム) は必要ありません。実際、この時点で必要な実行可能ファイルは 2 つだけです。つまり、MongoDB のデータベース プロセス自体の mongod.exe とコマンド ライン シェルのクライアントの mongo.exe です。通常、mongo.exe を SQL Server コマンド ライン シェルのクライアントである従来の isql.exe と同じ方法で使用して、すべてが適切にインストールされて動作することを確認したり、データを直接参照したり、管理タスクを実行したりできます。

すべてが正しくインストールされたことを確認するのは簡単で、コマンド ライン クライアントから mongod を起動するだけです。既定では、MongoDB は既定のファイル システム パス (c:\data\db) にデータを格納することを求めますが、コマンド ライン上で --config にテキスト ファイルの名前を渡して、これを構成できます。mongod を起動する場所に、db という名前のサブディレクトリがあるとすると、図 1 のようにして、すべてが適切かどうかを簡単に確認できます。

image: Firing up Mongod.exe to Verify Successful Installation

図 1 Mongod.exe を起動して、正しくインストールされていることを確認する

ディレクトリが存在しない場合、MongoDB はそのディレクトリを作成しません。Windows 7 がインストールされている筆者のコンピューターでは、MongoDB を起動すると、お決まりの "このアプリケーションはポートを開こうとしています" というダイアログ ボックスが表示されます。ポート (既定では、27017) にアクセスできるようにします。さもないと、ポートへの接続が厄介になります (これについては、次回以降のコラムで、MongoDB の運用環境への移行について説明するときに詳しく説明します)。

サーバーが実行されたら、シェルを使用してサーバーに接続するのは実に簡単です。mongo.exe アプリケーションがコマンド ライン環境を起動するため、これを使用してサーバーを直接操作できます (図 2 参照)。

image: Mongo.exe Launches a Command-Line Environment that Allows Direct Interaction with the Server

図 2 Mongo.exe が起動した、サーバーを直接操作できるコマンド ライン環境

既定では、シェルは "test" データベースに接続します。ここでの目的は、すべてが正常に動作しているのを確認することだけなのでこのままでかまいません。もちろん、ここから個人を説明する簡単なオブジェクトなど、なんらかのサンプル データを作成して、MongoDB を操作するのは非常に簡単です。次の図を見れば、起動するデータを MongoDB がどのように表示するかを簡単に把握できます (図 3 参照)。

image: Creating Sample Data

図 3 サンプル データの作成

基本的に、MongoDB はデータの表記法として JavaScript Object Notation (JSON) を使用します。これで、クライアントが MongoDB を操作する際の柔軟性と方法がおわかりいただけるでしょう。MongoDB はデータを内部的に BSON 形式 (JSON のバイナリのスーパーセット) で格納して、ストレージとインデックスの作成を容易にします。ただし、MongoDB に適切な入出力形式は依然として JSON です。一般に、JSON は MongoDB の Web サイトや wiki などさまざまな場所で使用されているドキュメント形式です。JSON になじみのない方は、MongoDB の詳しい説明を読む前に、JSON について復習することをお勧めします。その間、興味本位で mongod がデータを格納しているディレクトリを見てみると、"test" という名前のファイルがいくつか存在することがわかります。

遊びはこのくらいにして、いくつかコードを記述しましょう。シェルを終了するのは簡単で、"exit" と入力するだけです。サーバーをシャットダウンするには、コマンド ウィンドウで Ctrl キーを押しながら C キーを押すか、ウィンドウを閉じるだけです。サーバーが終了の合図を受け取ると、すべての処理を適切に終了してから、プロセスを終了します。

MongoDB のサーバー (およびシェル。ただし、シェルはそれほど問題にはなりません) は、ネイティブ C++ アプリケーションとして記述されています (覚えていますか)。そのため、サーバーにアクセスするには、開いているソケット経由で接続して、サーバーにコマンドとデータを渡すための方法を認識している、ある種の .NET Framework のドライバーが必要です。MongoDB の配布物には、.NET Framework のドライバーは含まれていませんが、さいわいなことにコミュニティで提供されています。ここでいう "コミュニティ" とは、Sam Corder という名前の開発者です。彼は、MongoDB にアクセスするための .NET Framework のドライバーと LINQ のサポートをビルドしています。Sam が作成したツールは、ソース形式かバイナリ形式で、こちら (github.com/samus/mongodb-csharp、英語) から入手できます。このページの(右上隅の) バイナリかソースのいずれかをダウンロードします。ソースの場合はダウンロードしてからビルドします。いずれの方法でも、MongoDB.Driver.dll と MongoDB.Linq.dll の 2 つのアセンブリがダウンロードされます。[参照の追加] を使用して、プロジェクトの [参照] ノードに簡単に追加できます。これで、.NET Framework を使用する準備が整いました。

コードを記述する

基本的に、実行中の MongoDB サーバーへの接続を開くのは、他のあらゆるデータベースへの接続を開くのとそれほど変わりません (図 4 参照)。

図 4 MongoDB サーバーへの接続を開く

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port
      db.Disconnect();
    }
  }
}

先ほど作成したオブジェクトを検出するのも難しくありません。ただし、.NET Framework 開発者が以前に使用していた方法とは異なります (図 5 参照)。

図 5 作成した Mongo オブジェクトを検出する

using System;
using MongoDB.Driver; 

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      Mongo db = new Mongo();
      db.Connect(); //Connect to localhost on the default port.
      Database test = db.getDB("test");
      IMongoCollection things = test.GetCollection("things");
      Document queryDoc = new Document();
      queryDoc.Append("lastname", "Neward");
      Document resultDoc = things.FindOne(queryDoc);
      Console.WriteLine(resultDoc);
      db.Disconnect();
    }
  }
}

このコードにやや圧倒された方はご安心ください。MongoDB は従来のデータベースと異なる方法でデータを格納するため、"長い記述方法" でコードを記述しました。

まず、先ほど挿入したデータに 3 つのフィールド (firstname、lastname、および age) があったことを覚えているでしょうか。これらはすべて要素でここからデータを取得できます。しかし、さらに重要なのは、これらを格納したコード行 (いくぶん無遠慮に一気に片付けてしまいました) が "test.things.save()" であることです。これは、データが "things" と呼ばれるものに格納されることを意味しています。MongoDB テクノロジでは、"things" はコレクションなので、暗黙のうちにすべてのデータがコレクションに格納されます。コレクションはドキュメントを保持します。このドキュメントはキーと値のペアを保持します。このペアの値を別のコレクションにすることもできます。この場合、"things" はデータベースの内部に格納されるコレクションです。このデータベースは、先ほど説明したように、test データベースです。

したがって、データを取得する際には、最初に MongoDB サーバーに接続してから、test データベースに接続し、"things" コレクションを検索することになります。これが図 5 の最初の 4 行で実行されている処理です。つまり、接続を表す Mongo オブジェクトを作成して、サーバーに接続します。次に、test データベースに接続してから、"things" コレクションを取得します。

コレクションが返されたら、コードからクエリを発行して、FindOne 呼び出しを使用して 1 つのドキュメントを検索します。しかし、あらゆるデータベースと同じように、クライアントはコレクション内のすべてのドキュメントの取得を希望しているわけではなく、目的のドキュメントを見つける必要があります。したがって、なんらかの形でクエリを制約する必要があります。MongoDB では、フィールドと、それらのフィールドで検索するデータを含む Document を作成することでこれを実行できます。これは例示照会プログラム (QBE: Query By Example) として知られている概念です。必要なのは、値が "Neward" に設定されている lastname フィールドを含むドキュメントを見つけることなので、lastname フィールドとその値を含む Document を作成して、パラメーターとして FindOne に渡します。クエリが成功したら、目的のデータがすべて含まれているもう 1 つの Document (ともう 1 つのフィールド) が返されます。それ以外の場合は、null が返されます。

ところで、先ほど触れた長い記述方法に相当する短い記述方法も簡単です。

Document anotherResult = 
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward"));
       Console.WriteLine(anotherResult);

このコードを実行すると、送信された元の値が表示されるだけでなく、新しい値 (ObjectId オブジェクトが含まれている _id フィールド) も表示されます。これは、このオブジェクトの一意識別子で、新しいデータが格納されたときに、データベースによって暗黙のうちに挿入されました。このオブジェクトを変更する場合は、このフィールドを保存する必要があります。さもないと、データベースは、新しいオブジェクトが送信されたと見なします。一般に、これはクエリで返された Document を変更することで実行します。

anotherResult["age"] = 39;
       things.Update(resultDoc);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

ただし、妥当であればいつでも新しい Document インスタンスを作成して、ObjectId に一致するように手動で _id フィールドを設定することもできます。

Document ted = new Document();
       ted["_id"] = new MongoDB.Driver.Oid("4b61494aff75000000002e77");
       ted["firstname"] = "Ted";
       ted["lastname"] = "Neward";
       ted["age"] = 40;
       things.Update(ted);
       Console.WriteLine(
         db["test"]["things"].FindOne(
           new Document().Append("lastname", "Neward")));

もちろん、_id が既知であればクエリ条件として使用することもできます。

事実上、Document は型指定されていません。つまり、任意の名前で、フィールドにほとんど何でも格納できます。これには、いくつかの主要な .NET Framework の値型 (DateTime など) が含まれます。厳密に言うと、既に説明したように、MongoDB は BSON 形式のデータを格納します。これには、前述の ObjectId、バイナリ データ、正規表現、埋め込みの JavaScript コードなど、従来の JSON 型へのいくつかの拡張機能が含まれます (string、integer、Boolean、double、および null。ただし、null はオブジェクトのみで使用でき、コレクションでは使用できません)。ここでは、正規表現と埋め込みの JavaScript コードの 2 つには触れません。BSON でバイナリ データを格納できるということは、バイト配列に分解できるすべてのデータを格納できることを意味するので、事実上、MongoDB はあらゆるデータを格納できます。ただし、そのバイナリ BLOB にはクエリできない場合があります。

まだ終わっていません

MongoDB について説明することはたくさんあります。たとえば、LINQ のサポート、これまでに説明した単純な QBE スタイルのクエリ機能を超えるより複雑なサーバー側クエリ、運用サーバー ファームで MongoDB を使用して幸せに暮らすことなどがあります。しかし今のところは、このコラムを読んで IntelliSense を念入りに調べれば、働くプログラマとして作業を開始するには十分です。

ところで、説明を希望する特定のトピックがありましたら、遠慮なくお知らせください。結局のところ、これはまさしく皆さんのコラムなのですから。コーディングを楽しんでください。

Ted Neward は、.NET Framework および Java のエンタープライズ プラットフォーム システムを専門とする独立企業 Neward & Associates の社長を務めています。これまでに 100 個を超える記事を執筆している Ted は、C# MVP であり、INETA の講演者でもあります。さまざまな書籍を執筆および共同執筆していて、近々発売される『Professional F# 2.0』(Wrox、英語) もその 1 つです。彼は定期的にコンサルティングを行い、開発者を指導しています。彼の連絡先は ted@tedneward.com (英語のみ) です。ブログを blogs.tedneward.com (英語) に公開しています。

この記事のレビューに協力してくれた技術スタッフの Kyle Banker と Sam Corder に心より感謝いたします。