Riak 入門: 第 2 回 Web アプリケーションの頼れるキャッシング・サーバーとして Riak を統合する

Riak をキャッシング・サーバーとして使用して、アプリケーションおよびデータベース・サーバーの負荷を軽減する

この記事は、極めてスケーラブルな分散型データストアである Riak について 2 回にわたって説明する連載の第 2 回です。Erlang で作成された Riak は、Amazon の高可用性キー・バリュー型ストアである Dynamo をベースとしています。スケーラブルなキャッシング・ソリューションを利用すると、負荷の高い Web サイトでもアプリケーションやデータベース・サーバーの負荷を軽減することができます。特に、頻繁に読み取られる一方、更新される頻度は少ないデータをキャッシングするのが有効です。この記事では、海外のオンライン・ベット (ギャンブル) サイトの詳細を例に取り上げ、Riak を使用してキャッシング・ソリューションを実装する方法を探ります。また、Riak を既存の Web サイトに統合する方法や、キャッシング以外の Riak の機能として、検索機能、そして Riak を使ってユーザーからの要求に直接対処する方法についても説明します。記事の例に従うには、実際に機能する Riak クラスターが必要です。ローカルでクラスターを作成する手順については、連載の第 1 回で説明してあります。

Simon Buckle, Independent Consultant, Freelance

Photograph of Simon BuckleSimon Buckle は独立したコンサルタントです。分散型システム、アルゴリズム、並行性に関心を持っています。インペリアル・カレッジ・ロンドンでコンピューティングの修士号を取得しました。彼の Web サイトには simonbuckle.com からアクセスできます。



2012年 6月 14日

はじめに

ある特定のタイプのデータは、そのデータがキャッシュに入れるのに適していることを示すアクセス・パターンを持っています。例えば、海外のオンライン・ベット (ギャンブル) サイトには興味深い負荷特性があり、オッズやベット・スリップ (賭け金などを入力するフォーム) は頻繁にアクセスされますが、更新されることは比較的まれです。

この連載の他の記事

連載「Riak 入門」の他の記事をご覧ください。

このような場合、高負荷の需要に対処するためには、以下の特性を持つ極めてスケーラブルなシステムが必要です。

  • システムが信頼性のあるキャッシュとして機能することで、アプリケーション・サーバーとデータベースでの需要を削減できること。
  • キャッシングされる項目が検索可能であり、それらの項目を更新したり、無効にしたりできること。
  • あらゆるソリューションを既存のサイトに簡単に統合できること。

このようなソリューションに打ってつけなのが、Riak です。

しかし、Riak がこのようなキャッシング・ソリューションを実装するための唯一の候補というわけではありません。Riak の他にも多種多様なキャッシュがあります。例えば、よく使用されているキャッシュとして memcached があります。けれども memcached の場合、Riak とは異なり、データのレプリケーションを一切行わないため、特定の項目を格納するサーバーがダウンすると、その項目は使用できなくなります。memcached の他に、キャッシュとして使用することができ、よく使用されているキー・バリュー型ストアの例として Redis があります。Redis はレプリケーションをサポートしますが、そのための方式としてマスター・スレーブ構成が採られています。一方、Riak にはマスター (ノード) の概念がないことから、耐障害性のあるシステムが実現されます。


Web サイトへの統合

どんなソリューションでも、既存の Web サイトに容易に統合できるようでなければなりません。既存のすべてのデータを Riak にマイグレーションできるとは限らないため (あるいは、そうしないほうが適切である場合もあります)、統合のしやすさは重要な点です。キー・バリュー型ストアではなおのこと、主キーによってデータにアクセスする場合には、前述したようにキャッシュに入れるのに適した特定のタイプのデータがあります。このようなデータが、Riak にマイグレーションするのに適したデータです。

連載の第 1 回で説明したように、Riak には、さまざまなクライアント・ライブラリーが PHP、Ruby、Java などの言語で用意されています。これらのライブラリーは、Riak の統合を極めて単純な作業にする API を提供します。この記事では、PHP ライブラリーを使用した例により、Riak を既存の Web サイトに統合する方法を紹介します。

図 1 に、この例で検討するサーバー構成を示します。ロード・バランシングやファイアウォールなどの詳細は省いているので、この例でのサーバーは、LAMP スタックをインストールした単なるフロントエンドのコンピューターに過ぎません。

この例では、Riak を内部でのみ使用すること (外部からはアクセスできません)、そして攻撃を受けない環境で実行されることを前提とします。したがって、認証などのセキュリティー関連の問題はありません。そもそも Riak には権限付与が組み込まれていないため、これはそれほど的外れな前提ではありません。実際、認証の類いの処理はアプリケーションに委任する必要があります。

図 1. Web サイトへの単純な統合
サーバーがリレーショナル・データベースや Riak クラスターとやりとりする様子を示す図

これから説明するのは、Riak を既存の Web サイトに統合する際に考えられる基本的な方法の一例です。この方法では、単純なフォームを作成し、そのフォームを送信すると、PHP クライアントがフォームに入力された値に基づいてオブジェクトを Riak に保管するようにします。

図 2 に、管理者がシステム内に「ベット」のエントリー (この例での「ベット」のエントリーは「Key (キー)」、「Odds (オッズ)」、「Description (説明)」で構成されます) を作成するために使用する単純なフォームの例を示します。このフォームを HTML で作成し、リスト 1 の PHP スクリプトに対して POST を実行させるようにしてください。記事に付属のソース・コードに同様のフォームが用意されているので、そのフォームを出発点として使用するのでも構いません。フォームに入力する「Key (キー)」フィールドを、バケットにオブジェクトを保管するためのキーとして使用します。

図 2. ベットを作成するためのサンプル・フォーム
フォームのスクリーン・キャプチャー。このフォームには、「Key (キー)」、「Odds (オッズ)」、「Description (説明)」の入力フィールドと、「Create (作成)」ボタンがあります。

リスト 1 に記載するサンプル PHP コードを見れば、PHP クライアント・ライブラリーを使用して Riak を統合する方法がわかるはずです。require_once に指定されている PHP クライアント・ライブラリーへのパスは、実際のインストール場所に合わせて変更してください。この例では単純に、クライアント・ライブラリーを PHP スクリプトと同じディレクトリーに配置しています。デフォルトでは、すべてのクライアント・ライブラリーは Riak がポート 8098 で使用可能であることを前提とします。

リスト 1. Riak を統合するサンプル PHP コード
<?php

require_once('./riak.php');

# Could do check here to see if the current user has the
# appropriate credentials ? delegated to application.

$client = new RiakClient('192.168.1.1', 8098);
$bucket = $client->bucket('odds');

$bet = $bucket->newObject($_POST['key']);        
$data = array(
    'odds' => $_POST['odds'],
    'description' => $_POST['description']
);
$bet->setData($data);

# Save the object to Riak
$bet->store();

echo "Thanks!";
?>

上記のコードを PHP ファイル (任意の名前を付けてください) に保存して、そのファイルとサンプル・フォームを Web サイト上の任意の場所 (例えば、http://www.yoursite.com/riak-test.php) にアップロードしてください。その上で、サンプル・フォームに入力して、フォームを送信します。正常に統合されたことを証明するには、項目を作成するためにフォームに入力したキーを使って、Riak から直接その項目を取得します。

リスト 2. Riak から項目を取得する
$ curl -i http://localhost:8098/riak/odds/<key>
...
{ "odds":"", "description":"" }

この統合の例では PHP クライアントを使用しましたが、Java や Ruby on Rails などの他の言語やアプリケーション・フレームワークを使用する場合の方法も同様です。


ユーザーからの要求に直接対応する

クライアント・ライブラリーを使用して Riak を現行の構成に統合するだけでなく、ユーザーからの要求に Riak で直接対応することもできます。その場合には、Riak を単純な HTTP エンジンとして使用します。この方法を具体的に説明するために、Riak がユーザーからの要求に直接対応する方法を示す単純なデモを作成します。

この記事のソース・コードをダウンロードしてください。Riak が稼働中であることを確認してから、load.sh スクリプトを実行します。このスクリプトは、HTML ファイルと JavaScript ファイルのすべてを demo という名前のバケットにコピーします。この例では、JavaScript クライアントを使用します。

デモを表示するには、ブラウザーで URL http://localhost:8098/riak/demo/demo.html を開きます。

ベットを作成するためのフォームに何らかの値を入力してからフォームを送信すると、JSON オブジェクトが Riak に保管されます。オブジェクトのプロパティーは、フォームのフィールドに対応します。作成したオブジェクトが保管されると、そのオブジェクトの値を表示するページにリダイレクトされます。

リスト 3 に、ユーザーが入力した値からオブジェクトを作成するコードを記載します。keyodds、および description の値は、フォームに入力された値から取得されます。

リスト 3. Riak での JavaScript クライアント・ライブラリーの使用例
client.bucket("odds", function(bucket) {
    var key = $('#key').val();
    bucket.get_or_new(key, function(status, object) {
        object.contentType = 'application/json';
        object.body = { 'odds': $('#odds').val(), 'description': $('#desc').val() };
        object.store(function(status, object, request) {
            if (status == 'ok') {
                window.location = "http://localhost:8098/riak/odds/"+key;
            } else {
            alert("Failed to create object.");
        }
        }); 
    });
});

前述のとおり、Riak は信頼できる環境で稼働しているという前提となっています。この前提では、Riak の項目を保管および取得するページを追加してもセキュリティーに関する問題はありません。ただし、何らかの形での認証を講じることなく、この種の機能をインターネットに公開するのは一般的に望ましくありません。

単純な例とはいえ、この例から Riak がユーザーからのページ要求に直接対応できることがわかったはずです。例えば、Riak に保管されているデータを直接、既存の Web ページに組み込むには、JSONP や異なる生成元をまたいでのリソース共有 (同一生成元ポリシーにより、Ajax リクエストの送信対象はそのページが置かれているサーバーに制限されます) といった手法を用いるか、お使いのサーバーを Riak への要求に対するプロキシーとして使用して、要求されたデータを取得する方法が考えられます。


キャッシュとして Riak を使用する

キャッシュを使用する目的は、データに素早くアクセスできるようにすることです。要求されたデータがキャッシュに含まれている場合 (キャッシュ・ヒット)、アプケーションはキャッシュから値を読み取ることで、データベースから値を取得する場合に比べ、短時間で要求に対応することができます。要求されたデータがキャッシュに含まれていなければ (キャッシュ・ミス)、アプリケーションはそのデータを取得するためにデータベースにアクセスするのが通常です。一般に、キャッシュで対応できる要求の数が多ければ多いほど、システムは高速になります。Riak には、キャッシング・ソリューションを実装するのに打ってつけの数々の機能が備わっています。

その一例は、Riak のプラガブル・ストレージ・バックエンドです。ストレージ・バックエンドによって、データの保管方法は決まります。使用できるストレージ・バックエンドは複数ありますが、ここではそのすべてについて説明することはしません (詳細については「参考文献」を参照)。デフォルトのストレージ・バックエンドは Bitcask です。Bitcask はデータを保管および取得するための API を提供する Erlang アプリケーションで、ハッシュ・テーブルを使用することによってデータへの高速アクセスを実現しています (この場合のデータは永続化されます)。

バックエンドのうち、おそらくこの記事との関連性が深いのは、Memory バックエンドです。Memory バックエンドは、インメモリー・テーブルにデータのすべてを保管します (内部では、Erlang の ets テーブルを使用します)。Memory バックエンドが使用可能になっている場合、Riak は有効期限が設定された LRU キャッシュのように動作します。インメモリー・ストアを使用することによる利点は、ディスクにアクセスしてデータを取得する場合に比べ、はるかに短時間でデータを取得できることです。データがメモリー内に保管される場合 (この場合、データは永続化されません)、ノードがダウンすると、そのノードに保管されているデータは失われることになります。しかし、Riak をプライマリー・データストアとして使用する場合とは異なり、ノードをキャッシュとして使用するのであれば、この点はさほど問題にはなりません。データが失われたとしても、アプリケーションはいつでもそのデータをデータベースから取得することができます。Riak はクラスター内の複数のノードにデータを複製するので、あるノードでデータが失われても、他のノードには同じデータが保持されています。

Riak には、Memory バックエンドが同梱されています。Memory バックエンドを使用するには、クラスターに含まれる各ノードの app.config を開いて、storage_backend プロパティーを見つけます。このプロパティーの値を riak_kv_bitcask_backend から riak_kv_memory_backend に変更した上で、ファイルの終わりにリスト 4 のコードを追加します。

リスト 4. Memory バックエンドを使用する
{memory_backend, [
    {max_memory, 4096},	%% 4GB of memory
    {ttl, 86400}        %% Time in seconds
]}

上記の値は、それぞれのセットアップに適した値に変更してください。変更作業が完了したら、クラスター内のノードを再起動します。

Riak クラスター内で複数のストレージ・バックエンドを実行することもできます。これはつまり、バケットに応じて異なるバックエンドを利用できるということです。例えば、あるバケット (例えば cache) は Memory バックエンドを使用するように構成する一方、他のバケット (データを永続化しなければならないバケット) については、例えば Bitcask を使用するように構成することができます。

キャッシュのように動作する Riak のセットアップはこれで完了したので、今度はデータを更新したり、あるいは何らかの理由で (有効期限が切れる前に) データを無効にしたりするために、クラスター内のデータにアクセスする方法を学ぶ必要があります。


何をお探しですか?

すでに説明したように、HTTP インターフェースを使用して Riak に保管されたデータを取得する方法は、取得したいオブジェクトのバケット名とキーで構成される URL を作成し、その URL に対して HTTP GET を実行することです。オブジェクトのキーを知っていれば、この方法でまったく問題ありませんが、取得したいオブジェクトのキーがわからない場合や、特定の基準を満たすオブジェクトのセットを取得したい場合もあります。その場合には、クラスターに格納されたオブジェクトを検索する方法が必要になります。

クラスター内に保管されている文書に対して Map/Reduce ジョブを実行することで、保管されているデータに対してクエリーを実行する方法については、前回の記事で説明しました。一般に、クエリーの実行にかかる時間は、クラスター内の文書の数に比例します。文書の数が多ければ多いほど、文書に対してクエリーを実行するのは時間がかかります。時間的制約がないクエリーの場合は、それでも問題ありません。時間的制約がないクエリーとは、ユーザーが瞬時に応答を受け取ることを期待していないクエリーのことです。けれども検索のような操作となると、毎回すべての文書を (動的に) 検索するのは現実的でありません。結果を得られるまでに、数分、あるいは何時間もかかることもあります!

幸い、Riak にはこの問題に対するソリューションがすでに用意されています。それが、Riak Search です。Riak Search は、クラスター全体にわたって保管された文書を検索するために必要な機能を提供します。Riak Search はこの記事で詳細を説明するにはあまりにも壮大な話題ですが、その概要を説明すると、まず文書をトークン化して (Riak Search は標準的な Lucene アナライザーを使用します)、それを逆索引に追加します。この索引に対して、ユーザーが入力した検索条件に基づいてクエリーを実行するという仕組みです。新しい文書が追加されると、その新しく追加された文書にも索引が付けられて、索引に追加されます。

Riak Search は、デフォルトでは無効になっています。Riak Search を使用するには、まずはこれを有効にしなければなりません。クラスター内の各ノードの rel/riakN/etc/app.config を開き、riak_search プロパティーを見つけて値を true に設定してください。この作業が終わったら、クラスター内のノードを再起動する必要があります。

Riak では、コミット前フックとコミット後フックによって、文書をバケットに追加する前後に実行する関数の名前を指定できるようになっています。例えば、文書をクラスターに追加する前に、その文書に特定の必須フィールドがあることを確認したいとします。文書を検索するには、文書に索引を付けなければなりません。それには、文書が保管されるバケットにコミット前フックをインストールしてください。そのために実行するコマンドは、$ rel/riak/bin/search-cmd install <バケット名> です。

このコマンドによって、コミット前フック riak_search_kv_hook がバケットにインストールされます。これで、このバケットに新しく文書を追加するたびに、その文書が分析されて索引に追加されることになります。デフォルトのアナライザーはホワイトスペース・アナライザーで、このアナライザーはホワイトスペースを基準に文字をトークンに変換してから索引を付けます。この他にも各種のアナライザーが用意されており、独自のアナライザーを定義することも可能です。

ほとんどの場合、Riak Search はデータに索引を付ける方法を認識しています。例えば、JSON オブジェクトがバケットに追加されると、それぞれのプロパティーの値に索引が付けられます。したがって、クエリー・ストリングに含まれるプロパティー名を使用すれば、プロパティーに対してクエリーを実行できるというわけです。リスト 5 に記載する検索の例を参照してください。構造がさらに複雑であれば、Riak Search にデータの索引付けの方法を指示する独自のスキーマを定義することができます。

文書に索引を付けた後は、これらの文書に対するクエリーを実行できなければなりません。その方法の 1 つは、Erlang シェルからクエリーを実行することです。リスト 5 に一例として、競馬 (horse racing) に関連するすべてのベットのオッズ・バケットを検索するクエリーを記載します。この検索では、保管されている項目の description プロパティーに対してクエリーを実行しています。

リスト 5. 競馬 (horse racing) に関連するベットのオッズ・バケットを検索する
$ rel/riak/bin/riak attach

search:search(<<"odds">>, <<"description:horse">>).

さらに、Riak Search には文書の検索に使用できる Solr 対応の HTTP API も用意されています。Apache Solr は、よく使用されている REST スタイルの API を備えたエンタープライズ検索サーバーです。API に Solr との互換性を持たせることにより、Solr (使用している場合) を切り替えて、代わりに Riak Search で検索を駆動することもできるようにしています。例えば、Solr インターフェースを使用して特定のイベントのオッズを検索するには、次のようにします。$ curl "http:localhost:8098/solr/odds/select?start=0&q=description:horse"

以上で検索を実行するためのセットアップができたので、探している項目の主キーを知らなくても、データストア内で目的の項目を見つけることができるはずです。


まとめ

この連載の他の記事

連載「Riak 入門」の他の記事をご覧ください。

スケーラビリティー、信頼性の高いデータ・レプリケーション機能、そして検索などの機能を備えた Riak は、負荷の高いサイトにキャッシング・ソリューションを実装するには理想的な選択肢です。Riak は既存のサイトに簡単に統合することができます。Riak を使用すると、ユーザーからの要求に直接対応できることから、アプリケーションおよびデータベース・サーバーの負荷を軽減、あるいは取り除くことができます。


ダウンロード

内容ファイル名サイズ
Article source coderiakpt2sourcecode.zip85KB

参考文献

学ぶために

製品や技術を入手するために

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Web development
ArticleID=820384
ArticleTitle=Riak 入門: 第 2 回 Web アプリケーションの頼れるキャッシング・サーバーとして Riak を統合する
publish-date=06142012