まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:memcached

自作のごくシンプルなウェブアプリを memcached 対応(要は「データベースが memcached でも動くようにする」対応)させようとしてハマってしまった時の様子と、なんとか対応できた結果をブログにまとめました:
2022022300


そのシンプルなウェブアプリではデータの CRUD 処理を行います。その CRUD 処理の中に「全データを取得」という API を作っていました。データベースが MySQL なら MySQL の特定テーブルの全データを、MongoDB なら MongoDB 内の全データを取り出す、という API でした。これの memcached 版を作ろうとした時の話です。ちなみにアプリケーションは Node.js で作っていたので、Node.js で実装できる必要があります。memcached を扱うためのライブラリとしてはおそらく最も一般的なライブラリである npm memcached を使っていました(必ずしもこのライブラリでなくてもいいです)。

memcached はいわゆる Key-Value 型のデータストアです。key の値を頼りにして value を取り出す、というものです。key さえ分かれば value が取り出せるので、「全データを取り出す」のは「全キーが分かれば全データがわかる」ということになります、、、のですが、npm memcached の解説のどこを見てもそんな API は提供されていないようでした。当初は「えー!? ただ全キーを取り出したいだけなのに、その機能がないの!?」と思いました。ちなみに memcached とメモリDBの双璧をなすもうひとつの Redis では keys() というキーの検索メソッドが用意されていて、keys( '*' ) のようにキーにワイルドカードを指定して実行することで全キーを取り出すことができていました。memcached にも同様の機能があるものと期待していたのですが、、、StackOverflow など英語圏の情報も含めていろいろ調べてみたのですが、どうも一筋縄ではいかないらしいことが分かってきました。。さてどうしよう。。


ここで手助けになったのはネットの情報というよりも、実際に telnet コマンドを叩いて調べてみる方法でした。「試行錯誤」というやつです。そうしているうちに「CLI コマンドであれば、コマンドを何回か繰り返して実行する必要はあるが、キー一覧を取り出すことはできそう」だと分かってきました:

2022022301

2022022302


結論としてのコマンド順序は、
 1. "stats items" コマンドを実行し、各 slab に何件のデータが登録されているのかを調べ、
 2. 1. で調べた結果に従って、"stats cachedump" コマンドで slab 内をダンプする
 3. key 一覧だけでなく value の一覧が必要な場合は getMulti() 関数で value も取り出す
という感じでできそうでした。


参考: 
https://shim0mura.hatenadiary.jp/entry/20140125/1390647044


後はこの 1, 2, 3 の処理を Node.js と memcached ライブラリで実装すればよい、ということになります。こんな感じで作ってみました:
var Memcached = require( 'memcached' );
var memcached = new Memcached( "localhost:11211" );   // memcached が localhost:11211 で稼働している場合

async function getAll(){
  return new Promise( async ( resolve, reject ) => {
    if( memcached ){
      memcached.items( function( err, results ){
        if( err ){
          resolve( { status: false, error: err } );
        }else{
          var cnt = 0;
          var values = [];
          results.forEach( function( result ){
            var obj_keys = Object.keys( result );
            if( obj_keys.length == 0 ){
              resolve( { status: true, results: [] } );
            }else{
              Object.keys( result ).forEach( function( slabid ){
                if( slabid != 'server' ){
                  memcached.cachedump( result.server, parseInt( slabid ), result[slabid].number, function( err, key_results ){
                    var keys = [];
                    if( 'length' in key_results ){
                      key_results.forEach( function( key_result ){
                        var key = key_result['key'];
                        keys.push( key );
                      });
                    }else{
                      var key = key_results['key'];
                      keys.push( key );
                    }
  
                    memcached.getMulti( keys, function( err, data ){
                      if( !err ){
                        keys.forEach( function( key ){
                          values.push( data[key] );
                        });
                      }

                      cnt ++;
                      if( cnt == results.length ){
                        resolve( { status: true, results: values } );
                      }
                    });
                  });
                }
              });
            }
          });
        }
      });
    }else{
      resolve( { status: false, error: 'no db' } );
    }
  });
};

これで、
var result = await getAll();

といった感じで全データ( result.results )が取得できるようになりました。

自分であちこち見て回った上で、「難しそう」とか「できないわけではない・・・」といった情報は得られたのですが、実際に取得できる Node.js サンプルソースコードを見つけることができなかったので、同じように悩んでいる人のお役にたてば。

PHP の MVC フレームワークの1つである CakePHP は、デフォルト設定の場合はその内部キャッシュにファイルシステムが使われています。

もちろんこれでも動くのですが、このキャッシュを memcached(メモリサーバー)にすることで、比較的簡単に高速化を実現することができます。以下は CakePHP 2.x を使う前提ですが、キャッシュに memcached を利用する方法を紹介します。


まずは memcached と、PHP から memcached を利用するための pecl-memcache をインストールしておきます:
# yum install memcached php-pecl-memcache -y

このコマンドが失敗する場合は yum リポジトリが足りていない可能性が高いので、以下のコマンドを実行してから再度 yum install してみてください(RHEL 6.x x86_64 の場合):
# rpm -Uhv http://ftp.riken.jp/Linux/repoforge/redhat/el6/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

memcached と pecl-memcache がインストールできたら memcached を起動し、httpd を再起動します:
# /etc/init.d/memcached start
# chkconfig memcached on
# /etc/init.d/httpd restart

これで memcached の用意ができました。では CakePHP 側の設定を変更して、この memcached を利用するようにしてみましょう。app/Config/core.php ファイルを編集し、以下の設定を加えます:
# vi app/Config/core.php

  :
  :
//. デフォルトのキャッシュ設定(Cache::config で始まっている設定)が有効になっていたらコメントで無効にする //. Cache::config( "default", array( "engine" => "File" ) );
:
: //. デフォルトでは memcached にキャッシュするよう設定を追加 Cache::config( "default", array( "engine" => "Memcache", // 保存先は memcache "duration" => 3600, // キャッシュの有効時間は3600秒 "probability" => 100, // キャッシュ再作成率は100%(全て) "prefix" => Inflector::slug( APP_DIR ) . "_", // キャッシュ名のプレフィクスは定数 APP_DIR + "_" "servers" => array( "127.0.0.1:11211" ), // memcached サーバーとポート番号 "compress" => false // キャッシュは圧縮しない ));
: :

では現在までに作成されたキャッシュを全てクリアします:
# cd app/tmp/cache
# rf -rf ./*

この後に CakePHP を使ってみて、app/tmp/cache 以下にフォルダが作成されていないことが確認できれば成功です。

IBM Bluemix では様々な API がサービスとして提供されていますが、その中の1つでメモリキャッシュの機能を提供する Memcached Cloud があります:
2016051501


同サービスは IBM のビジネスパートナーである Redis Labs から提供されているものです。同社からは他にも Redis のサービスである Redis Cloud も提供されています。

この Memcached Cloud サービスを使ってみようと試みた時に、自分が躓いた点がありました。これまで自分が memcached を使う時はユーザーやパスワードの認証を使ったことがなく、そのため認証を前提としない JAR ライブラリを用意して使っていたのですが、Memcached Cloud ではユーザー&パスワードの認証が必要になります。というわけで、認証に対応したライブラリを用意する必要がありました。その手順を見つけるのに苦労したり、ドキュメントに書かれている通りには動かなかったりしたので、備忘録として手順をここに記録しておくことにします。


まず、認証に対応した Java の Memcached ライブラリとしては spymemcached を使うことにします:
2016051502


spymemcached は上記のように github からソースコードが提供されています。どこかからバイナリを探してダウンロードしてもいいのですが、自分は(最新版で試したいという意味もあったので)このソースからビルドしました。

ビルドの手順はこんな感じです。Linux(自分の場合は CentOS 6)のコマンドラインから以下のように入力して、ソースコードをダウンロードし、ant でビルドしました:
# cd /usr/local/src
# git clone https://github.com/dustin/java-memcached-client
# cd java-memcached-client
# ant

ant のビルドが成功すると、build/jars 以下に spymemcached のバイナリ jar ファイルが作られます(この例ではバージョンは 2.11.4 です。以下のコードはこのバージョンでの動作を確認しています。またファイル名に test が付いている方はこの後では不要です)。このファイルを使って Java のコードを記述します:
# cd build/jars
# ls
spymemcached-2.11.4.jar  spymemcached-test-2.11.4.jar

実際の Java コードはこのような感じになります。例えば memcached クライアントである MemcachedClient インスタンスを取得するには以下のように記述します(赤字の部分がユーザー名、パスワード、サーバー名です):
  :
  :
import net.spy.memcached.*;
import net.spy.memcached.auth.*;
  :
  :

  MemcachedClient mc = null;
		
  try{
    PlainCallbackHandler ph = new PlainCallbackHandler( "username", "password" );
    AuthDescriptor ad = new AuthDescriptor( new String[] { "PLAIN" }, ph );
    mc = new MemcachedClient(
      new ConnectionFactoryBuilder()
        .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
        .setAuthDescriptor( ad ).build(),
      AddrUtil.getAddresses( "server.test.com:19606" ) );
    if( mc != null ){
      :
      :
    }
  }catch( Exception e ){
    e.printStackTrace();
  }

ドキュメントとの差異としては、AuthDescriptor のコンストラクタへ渡す第一引数の型は String ではなく String 配列にする必要があるようです。というわけで上記青字のような感じにしました。








 

Couchbase サーバーの特長の1つに「memcached互換」があります。

(ディスクではなく)メモリを一時キャッシュとして利用する memcached サーバーは比較的遅いリレーショナルデータベースのフロントエンドプロセッサーとして配置することで、部分的/仮想的に高速なデータベースサーバーを実現できます。ただし、あくまでメモリ上にのみ存在させて、ディスクへの読み書きを最小限に減らすことで高速化を実現しているため、そのままサーバーを落とすと中身が無くなり、再度メモリ上に展開して初めて再度利用することができるようになります。

Couchbase はこの memcached と互換性があります。memcached そのもののようにディスクへの退避のないデータストアとして利用することもできますし、データを非同期にディスクへ退避させつつメモリ中心のアクセスで高速化をはかる、という利用方法も選択できます(バケット単位で種類を指定します)。ちなみにデフォルト設定では後者になります。 ただいずれのモードでも単に memcached の挙動を真似ているというだけでなく、互換性があります。

具体的には、memcached は 11211 番ポートに TCP/IP で接続してデータを読み書きすることができますが、全く同じ TCP/IP リクエストで Couchbase サーバーのデータも読み書きできます。

実際に試してみましょう。Couchbase サーバーの環境構築についてはこちらを参照して、予め Couchbase サーバーが稼働している状態を用意しておきます:
CentOS に CouchBase サーバーを導入する

また、この手順では telnet を利用するので、telnet クライアントを用意しておきます。CentOS であれば以下のコマンドでインストールできます:
# yum install telnet

では実際に telnet で Couchbase サーバーに接続します。以下のコマンドの "couchbase.test.com" 部分を Couchbase サーバーのホスト名か IP アドレスに変更して実行してください:
# telnet couchbase.test.com 11211
Trying couchbase.test.com...
Connected to couchbase.test.com (127.0.0.1).
Escape character is '^]'.

上記のように "Escape character is '^]'. " と表示されれば接続できています。あっさり。

試しにこのプロトコルでデータを作成してみます。以下の様なコマンドを実行して、"my-third-document" という ID で、値が "My name is Couchbase." というドキュメントを作成してみます:
set my-third-document 0 0 21 My name is Couchbase.(改行)

memcached では set コマンドでデータを作成します。最初のパラメータは ID、2番目のパラメータは flag ですがここでは0を指定します。3番目のパラメータはデータの有効期限を秒単位で指定しますが、ゼロを指定すると無制限(ずっと残るデータ)になります。4番目のパラメータはデータサイズ、そして最後に実際のデータを入力します。

memcached(Couchbase)内のデータを参照するには get コマンドで ID を指定します:
get my-third-document(改行)
VALUE my-third-document 0 21
My name is Couchbase.
END

"My name is Couchbase." という入力した値が正しく取得できました。Couchbase サーバーに対して、memcached のプロトコルで値の読み書きができることが確認できました。

というわけで、Couchbase は memcached と同じように使えることが分かりました。memcached 用のライブラリの多くがそのまま Couchbase に対しても使えることになるので、これは便利な互換性といえます。

ちなみに、Couchbase サーバーへの telnet 接続を終了するには CTRL + ] を押して、プロンプトで quit を入力です:
(CTRL + ] を入力)
telnet> quit (telnetプロンプトで quit と入力)
# (元のシェルに戻る)







オープンソースのメモリキャッシュシステムとして、恐らく最もメジャーな memcached を CentOS にインストールする手順を紹介します:


インストールは yum で行います。memcached は rpmforge にあるので、まずは rpmforge のレポジトリを用意します。以下の例は 64bit 版 CentOS 6.x 向けのリポジトリですが、バージョンやアーキテクチャが異なる場合は適宜変更してください:
# rpm -Uhv http://apt.sw.be/redhat/el6/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm

で、yum インストール:
# yum install memcached

インストールが完了したら設定を確認します。必要に応じてこれらの値を変更します:
# cat /etc/sysconfig/memcached
PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="-l 127.0.0.1"

最後にデーモン起動と自動起動設定:
# /etc/init.d/memcached start
# chkconfig memcached on









 

このページのトップヘ