KVSのTokyoTyrantで名前空間をマッピングしてJSONなモデルでヒャッハーするラッパーを書いた
あとでgithubに置く
(追記: *1279411405*) 置いた mizchi's PyTTScheme at master - GitHub http://github.com/mizchi/PyTTScheme
ざっくりこんなこんな感じ
from ttsec import TScheme #スキーマ作る twitter_updates = {"user":{"screen_name":"", "user_id":""}, "text":"", "post_id":"" } #スキーマと名前空間を指定してラッパーを生成 tsc = TScheme(scheme=twitter_updates,header = "twitter_timeline") #浅いコピーで雛形をもらってくる item = twitter_updates.copy() #要素を代入 item = {"user":{"screen-name","mizchi" "user-id":"1231454323"}, "text":"ほゲェ", "post-id":"41314314155" } #保存 tsc.put(item) #検索 tsc.get(where="text",number=0) #=> [ {"text":"ほゲェ"} ]
動機
昨日ドワンゴ勉強会のUSTREAMを見ていたんですが、お題は分散関連技術。HadoopとかKVSの話題が主でした。
その中で気になったのが、単純なKVSはデータモデルが貧弱で使いにくいよね!というのが共通見解っぽかったこと。
Cassandraが採用されるのも、結局はレプリケーションのしやすさと、カラムという概念でスキーマフリーに記述できるからとのことです。
で、じゃあ適当な名前空間決めてラッパ通してアクセスすりゃいいんじゃね?と思って、思いつくままに100行ほど書きました。再帰とか久しぶりに書いた。
基本的に、中身はKVSのままなので速度の劣化はないと思います。たぶん。その分メモリ使ってるけど。
使い方
ソースはこの記事の末尾
要:TokyoTyrant pytyrant
sudo easy_install pytyrant
など
dictオブジェクトでSchemeを書いてからインスタンスを作る。
headerはそのプロジェクト用の名前空間なので、ユニークなものを選ぶように。
ttsec.TScheme(scheme,header)
from ttsec import TScheme twitter_updates = {"user":{"screen_name":"", "user_id":""}, "text":"", "post_id":"" } tsc = TScheme(scheme=twitter_updates,header = "twitter_timeline")
put(dic,number=-1)
dict型を渡すとパラメータをシリアライズして保存します。numberを渡すとそのidsを上書きします。渡さない場合は最新レコードとして保存されます
たぶん、Schemeに初期パラメータを書いといて、インスタンスの浅いコピー繰り返すのが便利。今回は使ってないけど。
(itemは架空のものです)
item1= twitter_timeline.copy() item1 = {"user":{"screen-name","mizchi" "user-id":"1231454323"}, "text":"ほゲェ", "post-id":"41314314155" } item2= twitter_timeline.copy() item2 = {"user":{"screen-name","kyoujin" "user-id":"118979854323"}, "text":"狂った", "post-id":"243242355" } tsc.put(item1) tsc.put(item2)
_:/ は内部でスプリッターとして使ってるので、要素名で用いるとちゃんと動きません。
get(where=(str/list),number=int)
for i in tsc.get() :print i {'++unique++': '0'} {'++unique++': '1'} {'post-id': '243242355'} {'post-id': '41314314155'} {'text': '\xe3\x81\xbb\xe3\x82\xb2\xe3\x82\xa7'} {'text': '\xe7\x8b\x82\xe3\x81\xa3\xe3\x81\x9f'} {'user': {'screen-name': 'kyoujin'}} {'user': {'screen-name': 'mizchi'}} {'user': {'user-id': '118979854323'}} {'user': {'user-id': '1231454323'}} for i in tsc.get(where="user") :print i {'user': {'screen-name': 'kyoujin'}} {'user': {'screen-name': 'mizchi'}} {'user': {'user-id': '118979854323'}} {'user': {'user-id': '1231454323'}} for i in tsc.get(where="user/screen-name") :print i {'user': {'screen-name': 'kyoujin'}} {'user': {'screen-name': 'mizchi'}} for i in tsc.get(number=1) :print i {'ids': '1', '++unique++': '1'} {'ids': '1', 'post-id': '243242355'} {'text': '\xe7\x8b\x82\xe3\x81\xa3\xe3\x81\x9f', 'ids': '1'} {'user': {'screen-name': 'kyoujin'}, 'ids': '1'} {'user': {'user-id': '118979854323'}, 'ids': '1'}
dump()
スキーマの構造を表示
tsc.dump() /text /++unique++ int /user/user-id /user/screen_name /post-id
uniqueってのは判別用のキーです
raw_get(where=str,number=list)
TokyoTyrantの生のキーテーブルを表示します
for i in tsc.get() :print i twitter-timeline:/++unique++_0 twitter-timeline:/++unique++_1 twitter-timeline:/user/user-id_0 twitter-timeline:/user/user-id_1 twitter-timeline:/user/screen-name_1 twitter-timeline:/user/screen-name_0 twitter-timeline:/text_0 twitter-timeline:/text_1 twitter-timeline:/post-id_0 twitter-timeline:/post-id_1
get_length()
tsc.get_length() 2
保持するレコードの数です
delete_all()
この名前空間にある全てのオブジェクトを破棄します
ソース
ttsec.py
#/usr/bin/python #-*- encoding:utf-8 -*- from pytyrant import PyTyrant class TScheme(object) : def __init__(self,scheme,header=""): scheme["++unique++"]="int" self.__scheme = scheme self.quantity = 0 self.header=header+":" def dump(self): self.__dump(self.__scheme) def __dump(self,scheme,header=""): for k,v in scheme.iteritems(): if type(v)==dict : self.__dump(v,header+"/"+k) elif type(v) == str : print header+"/"+k+"\t"+v def raw_get(self,where="",number=-1): obj = {} query = self.header+"/"+where con = PyTyrant.open("localhost") ret = con.prefix_keys(query) for i in sorted(ret): if number==-1 or int(i.split("_")[-1]) == number : obj[i]=con[i] con.close() return obj def get(self,where="",number=-1): if type(where)==str : where = [where] objs=[] con = PyTyrant.open("localhost") ret = [] for i in where : query = self.header+"/"+i ret += con.prefix_keys(query) for i in sorted(ret): ids=i.split("_")[-1] if number==-1 or ids == str(number) : item = self.__parse(i,con[i]) item.update({"ids":ids}) objs.append(item ) con.close() return sorted(objs) def __parse(self,key,value): params = key.split("_")[0].split("/")[1:] item = self.__mkval(params,value) return item # self.__mkval(params,value) def put(self,dic,number=-1): if number == -1: number=self.get_length() dic["++unique++"]=str(number) con = PyTyrant.open("localhost") self.__put(dic,str(number),self.header) con.close() def __put(self,scheme,number,header=""): for k,v in scheme.iteritems(): if type(v)==dict : self.__put(v,number,header+"/"+k) elif type(v) == str : query = header + "/" + k+"_"+number con = PyTyrant.open("localhost") con[query] = v con.close() def get_length(self): con = PyTyrant.open("localhost") query = self.header+"/"+"++unique++" length= len(con.prefix_keys(query)) con.close() return length def __mkval(self,path,dic="val"): if not path: return dic else : ndic ={path[-1] :dic } return self.__mkval(path[0:-1],ndic) def delete_all(self): con=PyTyrant.open("localhost") ret = con.prefix_keys(self.header) for i in ret: del con[i] con.close()