Do what you have to do
Nerelační databáze Redis a Django
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.
| Vytisknout | Tento příspěvek napsal cx, 15.10.2010 v 08:00 do rubriky Django, Práce. Můžete sledovat komentáře na tento článek pomocí RSS 2.0. Můžete taktéž zanechat komentář nebo odkázat z Vašeho vlastního webu. |


1 rok zpět
Funkci gen_key máš chybně, správně má vypadat takto:
def gen_key(self): """Generate new key""" if not self.key: self.key = code(rcon.incr(KEY_ID)) return self.key1 rok zpět
Chytré, moje neznalost, opravím.
1 rok zpět
RS mi zdemoloval odsazení a uvozovky, tak si to nějak sprav.
1 rok zpět
Fixnuto
1 rok zpět
K funkci code bych měl tuto poznámku:
http://docs.python.org/release/1.5/lib/node181.html
1 rok zpět
Taky chytré. Base64 znám, tak nevím proč mě napadlo to dělat takhle lišácky
1 rok zpět
V Redisu se rýpu od chvíle, kdy jsi napsal článek na Roota. Mám ho už na 4 strojích. Funkce code mi Base64 silně připomněla. I když vlastně nevím, k čemu je funkce code nutná.
V Pythonu jsem nikdy nedělal. Asi proto, že jsem se zatím setkal jen s odstrašujícími zdrojáky
1 rok zpět
No vidíš, už blbnu. Base64 v tomto případě nejde použít. Jsem na tebe reagoval, když jsem přijel z předělávání střechy, tak mi to nemyslelo. V tomto případě má funkce code() udělat jednu věc a to převést např. číslo 26 na F (takhle to asi nevyjde, jen pro ukázku). Tím zkrátíš zápis v URL o jeden znak. Používám to u služeb pro Twitter, kde na délce URL záleží.
1 rok zpět
Však nemusím mít vždy pravdu.
Mně jenom připadá použití takových funkcí poměrně (časově) drahé a proto jsem poukázal na možné použití knihovní funkce.
Osobně nemám rád explicitní cykly ve skriptovacích jazycích. Kdysi jsem si takový jazyk napsal a vím, že je to drahá funkčnost. Ušetřený 1 byte ve tweetu však může mít svůj význam.
1 rok zpět
Redis má svůj význam nejen kvůli tomu, že jsou data v paměti s občasným ukládáním na disk, ale zejména kvůli možnosti sdílení těchto dat mezi různými procesy. Kdybychom tuto vlastnost nepotřebovali, vystačili bychom s běžnými datovými strukturami poskytovanými použitými programovacími prostředky.
Například v Perlu je zajímavý příkaz „tie“, ale netuším, jak by se vypořádal s kolizí při konkurenčním zápisu. Možná je to už vyřešeno.
1 rok zpět
Jen tak z legrace jsem si spustil Redis 3x – na IP 127.0.0.1, 127.0.0.2 a 127.0.0.3. Jedou nezávisle na sobě, každý proces má svůj konfigurační i datový soubor. To jen pokud by někomu nestačil SELECT pro oddělení úloh – například pro více uživatelů na jednom fyzickém serveru. V tom případě doporučuji AUTH s kvalitním heslem.
Instalovat a spustit se dá i v neprivilegovaném režimu.