気がついたら今年もあと9ヶ月ですね。そういや、また新入社員とか入ってくるのか。ああ、またそろそろ旅に出たい。酒ばっか飲んでないでお金貯めよう。えっと、あの、その、、、前回の記事でインストールから基本的な設定をして起動までできた。じゃあ次は適当にデータでも入れてみよっか。curlとPythonで遊んでみる。あ、Riakのバージョンは1.3.0ね。Pythonクライアントのバージョンは1.5.2。



クライアントからのデータ操作は、HTTP経由(ポート:8098)またはプロトコルバッファ経由(ポート:8087)となる。

0. データ構造

まず、データ構造について。いわゆるkey-valueストア。テキスト、画像など(たぶんなんでも)が保存可能。構造としては、bucketと呼ばれる箱の中にkey/valueの集合が入る感じ。バケットおよびキーはそれぞれ一意な名前である。バケットがテーブル(Cassandra風に言うとkeyspace)のようなものだと考えたら良いと思う。絵的にはこんな感じか↓?

02_riak_data_structure

 

1. HTTP APIからcurlでデータ操作

HTTPの口が開いてるってことはcurlなどよく知られたコマンドで簡単に操作できる。操作によっては必須のHTTPヘッダが存在する。このあたりは公式ドキュメントに詳しくわかりやすく説明が書かれているので、詳細はそっちを参照していただけると。Riak触って思うのは公式ドキュメントの充実度。これは開発者/運用者にとってありがたい。
1.1 Bucketの操作

いきなりデータ入れることもできるけど、まずはバケットを設定してみる。Riakはバケットごとにデータのレプリケーション数(※1)や一貫性レベル(※2)、さらにバックエンドのストレージ(Riak KV)(※3)を設定することができる。バケット操作のURIは、/buckets/BUCKET_NAME/propsとなる。BUCKET_NAMEに設定を行いたいバケット名を指定してやる。
  • ※1: レプリケーション数:データ保存時、いくつのノードにデータをレプリケーションさせるか?をバケットごとに設定することができる。
  • ※2: 一貫性レベル:Riakは結果整合性(Eventual Consistency)という思想を持っている。このへんもCassandraと一緒。例えば、レプリケーション数が3で、データを保持しているノードが3つあった場合、そのノード3つが保持しているデータは瞬間瞬間で異なっているはず(レプリケーション遅延などをイメージすると良い)。で、その3ノードのうち、1ノードのみから読み込んだデータを正とするか?すべてから読み込みこんでタイムスタンプを検証して新しいものを正とするか?どこまでデータの整合性を担保するかを調整できる。CAP定理でいうC(Consistency)とA(Availability)の調整が可能。を整合性レベルを高くすると一貫性(C)は上がるが、可用性(A)は落ちる。
  • ※3: 設定ファイルapp.configのstorage_backend項目で「riak_kv_multi_backend」を選択している場合は、バケット作成時にRiakのバックエンドとなるストレージを指定できる模様。ストレージはbitcask(設定名:riak_kv_bitcask_backend)、leveldb(設定名:riak_kv_eleveldb_backend)、memory(設定名:riak_kv_memory_backend)の三つが選択可能で、それぞれの特徴に従って振る舞う。MySQLのストレージエンジンのようにプラバブルなデータストアとなっている。。http://docs.basho.com/riak/latest/tutorials/choosing-a-backend/にそれぞれのストレージの特徴や弱点が記述されている。
バケットの設定

で、バケットの設定。バケットの設定をする場合、Content-Type: application/jsonとして'{“props”:{}}’をPUTメソッドで送りつける。上述したレプリケーション数や一貫性レベルを指定する場合は{“props”:{“n_val”:1}}などとして、jsonの属性を指定する。設定に成功するとHTTPステータスコード204が返却される。

1
2
3
4
5
6
7
# レプリケーション数を「1」として、バケット「ore」を設定する。

curl -i -X PUT -H "Content-Type: application/json" -d '{"props":{"n_val":1}}' http://localhost:8098/buckets/ore/props

# レプリケーション数を「5」として、バケット「omae」を設定する。

curl -i -X PUT -H "Content-Type: application/json" -d '{"props":{"n_val":5}}' http://localhost:8098/buckets/omae/props

バケットの参照

バケットの設定を参照してみる。バケットore(/buckets/ore/props)とバケットomae(/buckets/omae/props)にGETを投げてやるとそれぞれの設定内容がjsonで取得できる。レプリケーション数を表す「n_val」がそれぞれ指定した値(oreは1、omaeは5)になっていることが確認できる。

1
2
3
4
5
6
7
8
9
10
11
# さっき設定した「ore」を参照

curl -i http://localhost:8098/buckets/ore/props

{"props":{"allow_mult":false,"basic_quorum":false,"big_vclock":50,"chash_keyfun":{"mod":"riak_core_util","fun":"chash_std_keyfun"},"dw":"quorum","last_write_wins":false,"linkfun":{"mod":"riak_kv_wm_link_walker","fun":"mapreduce_linkfun"},"n_val":1,"name":"ore","notfound_ok":true,"old_vclock":86400,"postcommit":[],"pr":0,"precommit":[],"pw":0,"r":"quorum","rw":"quorum","small_vclock":50,"w":"quorum","young_vclock":20}}

# さっき設定した「omae」を参照

curl -i http://localhost:8098/buckets/omae/props

{"props":{"allow_mult":false,"basic_quorum":false,"big_vclock":50,"chash_keyfun":{"mod":"riak_core_util","fun":"chash_std_keyfun"},"dw":"quorum","last_write_wins":false,"linkfun":{"mod":"riak_kv_wm_link_walker","fun":"mapreduce_linkfun"},"n_val":5,"name":"omae","notfound_ok":true,"old_vclock":86400,"postcommit":[],"pr":0,"precommit":[],"pw":0,"r":"quorum","rw":"quorum","small_vclock":50,"w":"quorum","young_vclock":20}}

バケットの設定解除

DELETEメソッドを投げつけてやることで、バケットの設定をすべてデフォルトの状態にすることができる。成功するとHTTPステータスコード204が返却される。

1
2
3
# oreの設定をすべてデフォルトにする

curl -i -X DELETE http://localhost:8098/buckets/ore/props

 

1.2 オブジェクトの操作

続いてオブジェクト操作。オブジェクトは保存(PUT)/参照(GET)/削除(DELETE)の操作が可能。上述した一貫性レベルはオブジェクト操作時に指定することもできる。一貫性レベルを指定しないと、バケットの設定に従った一貫性レベルによって操作が行われる模様。

オブジェクトを操作するためのURIは/buckets/BUCKET_NAME/keys/KEY_NAMEである。オブジェクトを保存は保存するデータの形式に沿ったContent-Typeを指定してあげる。オブジェクトを取得するときは保存するときに指定されたContent-Typeがデータ取得時のContent-Typeになるようだ。

適当な文字列を保存/取得/削除

Content-Type: text/plainで「111text」という文字列をtextバケットにキー111として入れる/取り出す。

1
2
3
4
5
6
7
8
9
10
11
# 文字列の保存

curl -i -X PUT -H "Content-Type: text/plain" -d "111text" http://localhost:8098/buckets/text/keys/111

# 取得

curl -i http://localhost:8098/buckets/text/keys/111

# 削除

curl -i -X DELETE http://localhost:8098/buckets/text/keys/111

テキストファイルの内容を保存/取得/削除

同じ要領で適当に/etc/yum.repos.d/amzn-main.repoの内容を保存してみる。

1
2
3
4
5
6
7
8
9
10
11
# 保存

curl -i -X PUT -H "Content-Type: text/plain" -T "/etc/yum.repos.d/amzn-main.repo" http://localhost:8098/buckets/textfile/keys/111

# 取得

curl -i http://localhost:8098/buckets/textfile/keys/111

# 削除

curl -i -X DELETE http://localhost:8098/buckets/textfile/keys/111

画像を保存/取得/削除

画像の場合はContent-Type: image/png, jpegなどで保存してやる必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 適当な画像ファイルをローカルにおいて

wget https://hiroakis.com/blog/wp-content/uploads/2013/03/mysql_backup.png

# 保存

curl -i -X PUT -H "Content-Type: image/png" -T "./mysql_backup.png" http://localhost:8098/buckets/imagefile/keys/111

# ブラウザからアクセスしてみるとちゃんと画像が表示される

http://ec2-xxxxxx.amazonaws.com:8098/buckets/imagefile/keys/111

# 削除

curl -i -X DELETE http://localhost:8098/buckets/imagefile/keys/111

jsonを保存/取得/削除

jsonを保存する場合はちゃんとContent-Type: application/jsonで保存してやる。

1
2
3
4
5
6
7
8
9
10
11
# 保存

curl -i -X PUT -H "Content-Type: application/json" -d '{"name":"ore", "sex":"female", "website":"https://hiroakis.com/"}' http://localhost:8098/buckets/person/keys/111

# 取得

curl -i  http://localhost:8098/buckets/person/keys/111

# 削除

curl -i -X DELETE http://localhost:8098/buckets/person/keys/111

2. Pythonからの接続

Java, Python, PHP, Ryby, Erlangは公式クライアントがある。こん中から選ぶならPythonだろ(おれ的には)。クライアントの使い方の流れをまとめると下記のようになる。クライアント取得 → バケット選択 → オブジェクトをフェッチ/セット → オブジェクト操作。
  • クライアント取得
    • def __init__(self, host=’127.0.0.1′, port=8098, prefix=’riak’, mapred_prefix=’mapred’, transport_class=None, client_id=None, solr_transport_class=None, transport_options=None):
  • バケット選択
    • def bucket(self, name):
  • オブジェクトをフェッチ/セット
    • 取得/削除時はキーを指定して対象のオブジェクトをフェッチ:def get(self, key, r=None, pr=None):
    • 保存時はnewメソッドでキー/データ/コンテンツタイプをセット:def new(self, key=None, data=None, content_type=’application/json’):
      • どうやらデフォルトのコンテンツタイプはjsonの模様。
  • オブジェクトを操作
    • オブジェクトの内容を参照:def get_data(self):
    • オブジェクトRiakに保存:def store(self, w=None, dw=None, pw=None, return_body=True, if_none_match=False):
    • オブジェクトをRiakから削除:def delete(self, rw=None, r=None, w=None, dw=None, pr=None, pw=None):
2.1 HTTPインターフェイス

で、やってみる。

適当な文字列を保存/取得/削除

下記の例では毎回clientを取得してるけど、実際には一回でOK。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# store object
client = riak.RiakClient("ec2-xxx.amazonaws.com", 8098) # クライアント取得
bucket = client.bucket('text') # バケット選択
obj = bucket.new('111', '111text', content_type='text/plain') # オブジェクトにキー/データ設定
obj.store() # 保存

# get object
client = riak.RiakClient("ec2-xxx.amazonaws.com", 8098)
bucket = client.bucket('text')
obj = bucket.get('111')
print obj.get_data()

# delete object
client = riak.RiakClient("ec2-xxx.amazonaws.com", 8098)
bucket = client.bucket('text')
obj = bucket.get('111')
obj.delete()

めんどくさくなってきちゃったから画像の保存とjsonの保存の例だけ…(‘A`)

画像の保存

オブジェクトにキー/データを設定する際にはbucket.new()ではなく、bucket.new_binary()を用いる。

1
2
3
4
5
client = riak.RiakClient("ec2-xxx.amazonaws.com", 8098)
bucket = client.bucket('image')
png = open('mysql_backup.png', 'rb').read()
obj = bucket.new_binary('3123', png, content_type='image/png')
obj.store()

jsonの保存

jsonのデータは辞書型で指定することができる。

1
2
3
4
client = riak.RiakClient("ec2-xxx.amazonaws.com", 8098)
bucket = client.bucket('json')
obj = bucket.new('111', {"aa":"bb"}, content_type='application/json')
obj.store()

2.2 Protocol Buffers

続いてプロトコルバッファでも…ってとこなんだけど、githubに上がってるやつとpipで入るやつ、ソースが違う…と思ったら、1.5系のやつはブランチに上がってるやつらしい。なので、そっちのReadMeを見ながら…。こんな感じで↓。

1
2
3
4
5
6
7
8
9
# set json object via Protocol Buffers

client = riak.RiakClient(
"ec2-xxx.com",
8087,
transport_class=riak.RiakPbcTransport)
bucket = client.bucket('json')
obj = bucket.new('aaaa', {"aa":"bb"}, content_type='application/json')
obj.store()

クライアント取得時にトランスポートクラスを指示してやるだけでプロトコルバッファを喋ってくれるみたい。

今日はここまで。次はクラスタでも組んでみるか…

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <img localsrc="" alt="">

Set your Twitter account name in your settings to use the TwitterBar Section.