Do what you have to do
Štítky Redis
Nerelační databáze Redis a Django
15. Říj
O databázi Redis jsem minulý týden psal na server Zdrojak.cz, kde to Martin Malý vzal všemi dvaceti. Na team buildingu Internet Infa jsme se pak navíc ještě dlouho bavili o nerelačních databázích a i když mě odrazoval od vytváření projektu na míru řešení, nedalo mi to a rozhodl jsem se to vyzkoušet. Potřeboval jsem vědět před čím stojím a jaké jsou problémy implementace. Zároveň jsem měl možnost si vyzkoušel login přes Twitter, ale o tom jindy.
Abych Redis trochu představil. Jak již bylo řečeno, je to nerelační key-value nebo taky keystore databáze. Pod klíčem jsou zde uloženy různé datové typy jako množiny, seznamy, hashovací tabulky nebo klasické stringy. Nad jednotlivými typy pak umí databáze dělat různé operace. Nejdůležitější je, že běží téměř celá v paměti s tím, že se dají nepoužívané hodnoty odsunout na disk. To ale není výchozí chování a ani jsem ho nezkoušel. Díky tomu, že databáze ukládá data hlavně do paměti, je fast as hell a ideální pro určitou stabilní, ale velkou množinu dat, se kterými se permanentně pracuje.
Můj cíl bylo vytvořit aplikaci, která poběží na Djangu (bez databázového enginu). Chvilku jsem si dokonce hrál s myšlenkou, že databázový django engine pro Redis napíšu. Nebylo by to nereálné, ale jsou tu komplikace, které by nebylo jednoduché překonat. Jednotlivé implementace databázových enginů v Djangu mají každá kolem 500 řádků, ale jsou velmi SQL centrické. Lepší volbou by proto byl nějaký keystore module, jakým je třeba django-kvstore. Víceméně jediné, co je potřeba udělat, je nějaká třída Field, ve které se implementují metody save() a load(), přidá se trochu roští okolo a bude to fungovat.
Začal jsem tím, že jsem do settings.py přidal následující řádky:
-
## Redis
-
-
REDIS_HOST = "localhost"
-
REDIS_PORT = 6379
-
REDIS_DB = 1
-
REDIS_PASSWORD = None
-
-
## Nový přístup do redisu
-
-
import redis
-
-
rcon = redis.Redis(
-
host=REDIS_HOST,
-
port=REDIS_PORT,
-
db=REDIS_DB,
-
password=REDIS_PASSWORD,
-
)
-
Není to ideální, do nastavení to prostě nepatří, ale teď postačí.
Za projekt jsem vybral Twitter kalendář, takže cílem bylo na začátku vytvořit třídu event, jež by obsahovala metodu save() a statickou metodu load(). Dále bylo potřeba se postarat o generování klíčů, aby každý event měl klíč jiný. K tomu jsem použil inkrementaci hodnot pod klíčem v Redisu. Ve dvou dotazech na databázi mám jedinečný klíč v ruce. Celé to vypadá takhle:
-
def gen_key(self):
-
"""Generate new key"""
-
-
if not self.key:
-
self.key = code(rcon.incr(KEY_ID))
-
-
return self.key
Rcon je API k Redisu, funkce code, změní číslo na takovéto abVc5 a pro zajímavost vypadá takto:
-
def code(num):
-
num = int(num)
-
-
ch = ["0","1","2","3","4","5","6","7","8","9","-",
-
"+","a","b","c","d","e","f","g","h","i","j","k","l","m",
-
"n","o","p","q","r","s","t","u","v","w","x","y","z",
-
"A","B","C","D","E","F","G","H","I","J","K","L","M",
-
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
-
-
c = []
-
-
while num != 0:
-
c.append(ch[num%len(ch)])
-
num /= len(ch)
-
c.reverse()
-
return "".join(c)
Když máme klíč, můžeme objekt serializovat, resp. z něj udělat řetězec a nic nám nebrání ho uložit. O serializaci a deserializaci se stará modul pickle. Původně jsem chtěl použít modul json, ale pak jsem zjistil, že pickle umí serializovat cokoli a navíc má cčkovovou variantu, která si v rychlosti s Redisem sedne, když bude potřeba. Tady jsou ty důležité funkce:
-
import pickle
-
-
def ksez(input):
-
"""Serializace – JSON
-
"""
-
-
return pickle.dumps(input)
-
-
def kdez(input):
-
"""Deserializace – JSON
-
"""
-
-
return pickle.loads(input)
Používám je už v druhém projektu. Mám serializaci objektů rád. Když víme, jak objekt serializovat, můžeme ho uložit. Na první pohled se může zdát, že dávat to do samostatných funkcí je zbytečné, ale v okamžiku, kdy se rozhodnete změnit serializační metodu, budete za to ještě rádi.
-
def save(self):
-
"""Save data to Redis"""
-
-
self.gen_key()
-
-
data = ksez(self)
-
-
return rcon.set(KEY_EVENT % self.key, data)
Při ukládání zavoláme metodu gen_key(), ta se podívá, jestli je potřeba vygenerovat klíč a případně to udělá. Pak se objekt sám serializuje a uloží se pod klíč event:KEY. Funkce/metoda set() ochotné vrátí stav operace, takže ho hodíme ven.
Načítání objektů jsem chtěl mít ala Django, tedy něco jako event.load(key) a tak se mi naskytla možnost poprvé použít statickou metodu. Ta si vezme jako parametr klíč, jak už jste určitě pochopili, najde ho v Redisu, hodnotu, která se pod ním skrývá deserializuje a vrátí.
-
@staticmethod
-
def load(key):
-
"""Return event object"""
-
-
data = rcon.get(KEY_EVENT % key)
-
return kdez(data)
Tady by se určitě hodila alespoň jedna výjimka, která by ošetřila neexistenci klíče. Když se něco stane, resp. klíč nebude existovat, dostaneme nějakou takto prázdnou hodnotu a určitě ne objekt.
A tady to končí. Objekt může mít atributů kolik chcete a díky serializaci jde krásně uložit i se svými datovými typy do Redisu a dostanete ho zpátky bez škrábnutí. Rozhodně doporučuji prostudovat modul django-kvstore a při vytváření enginu si nezavírat vrátka k Redis funkcím, jako to dělá zmíněný modul.