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のままなので速度の劣化はないと思います。たぶん。その分メモリ使ってるけど。


実際PythonのdictだからJSONというよりただのハッシュ

使い方

ソースはこの記事の末尾


要: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()