NoSQLデータベースを試してみる

第3回様々なデータ型を扱えるTokyoTyrant

はじめに

今回は、永続性key-valueストアの代表としてTokyoTyrantを取り上げます。TokyoTyrantというのはTokyoCabinetのネットワークインタフェースです。名前が似ていてわかりにくいですが、TokyoCabinetがkey-valueストアの機能(データの保存や読み出し)を持ったデータストレージであり、TokyoTyrantはTokyoCabinetをネットワーク越しに操作できるようにしたラッパーです。キャビネット(内閣)を傀儡にするタイラント(僭主)ということで、このような名前が付けられたそうです

今回も利用したコードやプログラムはgithubに置いてあるので適宜参照してください。

TokyoTyrantの特徴

key-valueストアという点ではmemcachedと同じですが、TokyoTyrantサーバを停止してもデータが消えない(データの永続性がある)ことが最大の特徴です。 expiresを設定することはできませんが、それ以外はほぼmemcachedと同じように扱えます。

また、TCHDB(ハッシュデータベース、※1⁠、TCTDB(テーブルデータベース、※2などといった様々なデータ型があり、それらを用途に応じて使い分けることができることも特徴です[3]⁠。

表1 それぞれのデータ型に対応するクラス名とファイル名
データ型TokyoTyrantTokyoCabinetファイル名
ハッシュデータベースRDBHDB*.tch
テーブルデータベースRDBTBLTDB*.tct
B+treeデータベースRDBBDB*.tcb
オンメモリデータベースRDBADB*

どんなところに使える?

もっとも簡単な利用方法は、前回紹介したmemcachedの代替として利用するというものです。memcached互換プロトコルが実装されているため、memcachedを利用している場合ポート番号を変えるだけでTokyoTyrantに差し替えることが可能です。これだけで簡単にデータの永続性が得られます。

また、テーブルデータベースを利用すれば一つのkeyに対して複数のvalueを持たせることが可能であり、keyだけでなく任意のvalueを条件としてデータの検索を行うこともできます。JOINやGROUP BYといった処理は行えませんが、それ以外のほとんどの検索条件を扱えます。レスポンスはRDBMSに比べて高速なので、アクセス数が多いテーブルをTokyoTyrantのテーブルデータベースに載せ換える、といった利用方法も効果的かもしれません。

具体的な利用シーン
  • memcachedの代替としての利用
  • 複雑な検索条件を用いつつも高速なレスポンスが要求される

TokyoCabinetを試す

さて、まずはTokyoCabinetを試してみます。TokyoCabinetはvalueとして文字列しか扱えません。配列やハッシュなどをvalueとして保存したい場合には、明示的にシリアライズ(取り出すときにはデシリアライズ)してあげる必要があります。

リスト1 TokyoCabinetを使った具体的なコード例
class TokyocabinetController < ApplicationController
  require 'json'
  require 'tokyocabinet'
  include TokyoCabinet

  before_filter :do_init
  after_filter :do_finalize

  def set
    @hdb['key1'] = 100
    @hdb['key2'] = ["foo", "bar"].to_json
    @hdb['key3'] = {"a" => "ほげ", "b" => "ふが"}.to_json
  end

  def get
    p @hdb['key1']             # "100"
    p JSON.parse(@hdb['key2']) # ["foo", "bar"]
    p JSON.parse(@hdb['key3']) # {"a" => "ほげ", "b" => "ふが"}
  end

  private

  def do_init
    @hdb = HDB.new # ハッシュデータベースを指定
    @hdb.open('sample.tch', HDB::OWRITER | HDB::OCREAT)
  end

  def do_finalize
    @hdb.close
  end
end

TokyoTyrantを試す

TokyoCabinetでデータの保存や読み出しが出来ることが確認できました。しかし、TokyoCabinetではネットワーク越しのアクセス(例えば別サーバにアクセスするなど)ができません。これを実現するものがTokyoTyrantです。

では、TokyoTyrantを利用してみましょう。前回と同じように、localhostに3台のTokyoTyrantサーバを別々のポートで起動してみます。

プロンプト1 ハッシュデータベース用のTokyoTyrantサーバを別々のポートで複数起動する
sudo ttserver -port 1978 -pid /var/ttserver/pid_1978 -dmn /var/ttserver/ttcache_1978.tch
sudo ttserver -port 1979 -pid /var/ttserver/pid_1979 -dmn /var/ttserver/ttcache_1979.tch
sudo ttserver -port 1980 -pid /var/ttserver/pid_1980 -dmn /var/ttserver/ttcache_1980.tch

memcached互換プロトコル

もちろんTokyoTyrant独自プロトコルを使って利用することも可能ですが、ハッシュデータベースであればmemcached互換プロトコルが利用できます。前回memcachedを利用したときのコードの一部を以下のように変えるだけです。

コンストラクタの引数を変えるだけで利用できる
def do_init
  @cache = MemCache.new(
-   ['localhost:11211', 'localhost:11212', 'localhost:11213'], # memcached
+   ['localhost:1978', 'localhost:1979', 'localhost:1980'],    # TokyoTyrant
    :logger => Logger.new(STDOUT)
   )
end

memcached互換プロトコルを利用する場合、Consistent Hashingによって処理を分散させるのも非常に簡単です。実際試してみると、別々のポートで立てたTokyoTyrantサーバに分散してデータの保存/読み出しが行われていることが確認できます。

プロンプト2 コンソールログ
# key4は1978に、key2, key3は1979に、key1は1980に保存されている
set key1 to <MemCache::Server: localhost:1980 [1] (CONNECTED)>: 5
set key2 to <MemCache::Server: localhost:1979 [1] (CONNECTED)>: 19
set key3 to <MemCache::Server: localhost:1979 [1] (CONNECTED)>: 16
set key4 to <MemCache::Server: localhost:1978 [1] (CONNECTED)>: 19

テーブルデータベース

次に、もう一つの用途として考えられるテーブルデータベースを扱ってみましょう。テーブルデータベースではmemcached互換プロトコルは使えないため、TokyoTyrantの独自プロトコルを利用します。

まず、テーブルデータベース用のTokyoTyrantサーバを立ち上げます。先ほどとは保存先の拡張子が違うことに注意してください。

プロンプト3 テーブルデータベース用のTokyoTyrantサーバをマスター/スレーブ構成で起動する
sudo ttserver -port 1981 -ulog /var/ttserver/ulog_master -sid 1 -pid /var/ttserver/pid_1981 -dmn /var/ttserver/ttcache_1981.tct
sudo ttserver -port 1982 -ulog /var/ttserver/ulog_slave_1982 -sid 2 -mhost localhost -mport 1981 -rts /var/ttserver/3.rts -pid /var/ttserver/pid_1982 -dmn /var/ttserver/ttcache_1982.tct
sudo ttserver -port 1983 -ulog /var/ttserver/ulog_slave_1983 -sid 3 -mhost localhost -mport 1981 -rts /var/ttserver/3.rts -pid /var/ttserver/pid_1983 -dmn /var/ttserver/ttcache_1983.tct

TokyoTyrantはmemcachedと同じようにクライアント側の実装によって処理を分散させる必要があります。ただし、Consistent Hashingのような仕組みはなく、レプリケーションで処理(主に読み込み部分)を分散させるようです。-mhost, -mportでマスターのホストとポートを指定し、-rtsでレプリケーションタイムスタンプの記録を、-ulogでレプリケーション用のログを出力しています[4]⁠。

テーブルデータベースを利用した具体的なコードは以下のようになります。簡単な実装ですが、set_connectionメソッドでマスター、スレーブを設定し、do_initメソッドで書き込みはマスターに、読み込みはスレーブに処理を振り分けています。

リスト2 テーブルデータベースを利用した具体的なコード例
class TokyotyrantTdbController < ApplicationController
  require 'tokyotyrant'
  include TokyoTyrant

  before_filter :set_connection, :do_init
  after_filter :do_finalize

  def set
    @rdb['1'] = {"name" => "山田花子", "sex" => "female", "birthday" => "20050321"}
    @rdb['2'] = {"name" => "鈴木太郎", "sex" => "male", "birthday" => "20060601"}
    @rdb['3'] = {"name" => "佐藤洋子", "sex" => "female", "birthday" => "20070311"}
    @rdb['4'] = {"name" => "山田健一", "sex" => "male", "birthday"=> "20070523"}
  end
  
  def get
    qry = RDBQRY::new(@rdb)
    qry.addcond("name", RDBQRY::QCSTRBW, "山田")
    qry.setorder("birthday", RDBQRY::QONUMDESC)
    res = qry.search()

    p res # ["4", "1"] (keyが返ってくる)
    p @rdb.get(res[0]) # {"name" => "山田健一", "sex" => "male", "birthday"=> "20070523"} 
    p @rdb.get(res[1]) # {"name" => "山田花子", "sex" => "female", "birthday" => "20050321"}
  end

  private

  def set_connection
    @connection = {
      :master => ['localhost', 1981],
      :slave => [
        ['localhost', 1982],
        ['localhost', 1983]
      ]
    }
  end

  def do_init
    @rdb = RDBTBL::new() # テーブルデータベース

    case action_name
    when 'set'
      _con = @connection[:master]
      @rdb.open(_con[0], _con[1])
      logger.info "write to master: #{_con[0]}:#{_con[1]}"
    when 'get'
      _con = @connection[:slave].sort_by{rand}.first
      @rdb.open(_con[0], _con[1])
      logger.info "read from slave: #{_con[0]}:#{_con[1]}"
    end

    @rdb.setindex("name", RDBTBL::ITLEXICAL) # インデックス
  end

  def do_finalize
    @rdb.close()
  end
end

例では、名前が⁠山田⁠で始まるデータを誕生日の降順で検索してみました。このようにテーブルデータベースではRDBMSのように検索条件を利用することができます。ただ、実際に使うとなると独自の定数がたくさん出てくるため、扱いにくいなぁというのが正直なところです(強調部分⁠⁠。紙面の都合上、詳しくは扱いませんが、MiyazakiResistanceActiveTokyoCabinetを使うとテーブルデータベースをActiveRecordのように扱えます。

まとめ

今回はTokyoTyrantについて見てみました。memcachedは何といっても手軽なのですが、どうしてもデータ消失のリスクがあります。TokyoTyrantをmemcached互換プロトコルで利用すれば手軽にデータの永続性が保証できるため、非常に便利ではないでしょうか。また、テーブルデータベースは導入の敷居は高いですが、RDBMSのように扱えて高速に処理できるというのは魅力的です。

おすすめ記事

記事・ニュース一覧