Re: [python] db a thready

gsl na seznam.cz gsl na seznam.cz
Úterý Duben 5 09:13:32 CEST 2005


< > Neni mi jasne, jak by tohle melo fungovat, protoze i kdyz k databazi budu
< > pristupovat vyhradne pomoci jednoho objektu, tak, kdyz jeho metody budou
< > volany z ruznych threadu, a pobezi v jejich kontextu. A mel-li jsem
< > podminku, ze spojeni nesmi byt sdileno mezi thready, tak to imho muj
< > problem neresi. A nebo neco hrube nechapu.
< 
< Myslim, ze si nerozumime, ale presne nevim v cem :). Pokusim se to tedy 
< rict jinak: Pokud k jednem datum pristupuje vic klientu (z hlediska tech 
< dat je dost jedno, jestli jsou to ruzne thready nebo ruzne procesy na 
< ruchnych pocitacich) musi byt nekde mezi daty a klientem neco, co 
< zajistuje, ze pristup k datum bude atomicky (teda za predpokladu, ze 
< atomicitu potrebujete). Jedina otazka je, kde ten "serializator" bude. 
< Obecne nema smysl zajistovat atomicitu vice nez jednou, protoze vetsina 
< metod s sebou nese jistou rezii. Pokud tedy mate DB, ktera umi zajistit 
< atomicitu, nemusite ji resit ve svych programech (teda skoro, protoze 
< pak obcas musite resit limit na pocet soucasnych spojeni). Pak jsou DB, 
< ktere umoznuji vice spojeni soucasne, ale neposkytuji atomicitu -- co to 
< presne znamena, zavisi na konkretni DB. Pak je nutne vyvarovat se 
< urcitem typu soubehu pozadavku (napr. ze vic vlaken nemuze soucasne 
< zapisovat). To se nejcasteji resi tak, ze si do DB otevrete pouze jedno 
< spojeni a to obsluhuje nejaky objekt, ktery zaruci atomicitu. Obecne 
< neni nutne aby to bylo pouze jedno spojeni, jen je dulezite, aby se 
< nejvyse v jednom spojeni dela ta "kriticka operace", coz je stejne nutne 
< nekde centralne zajistit.
< (Omlouvam se, pokud vysvetluju prilisne triviality).

Nerozumime v tom, ze jsem si myslel, ze u SQLite nemohu mezi thready sdilet 
jedno spojeni a ze dve spojeni nemohou vest na jednu databazi. Po te jsem 
zjistil, ze mohou dve spojeni vest na jednu databazi, cimz se muj problem
vyresil. To ostatni je uz akademicka debata, jak by to bylo, kdyby platil
muj puvodni predpoklad. Domnivam se totiz, ze objekt resici atomicitu
pristupu k databazi neresi problem nemoznosti sdileni spojeni mezi thready.

Jinak si rozumime.
 
< Domnivam se, ze pokud pouzijete DB, ktera muze mit vic spojeni, ale 
< nesmi se zapisovat ve vice najednou, budete muset tuhle podminku nejak 
< zajistit vy sam. (Obecne lze zkusit pustit X klientu soucasne a podivat 
< se, co to s DB dela. Nicmene to, ze to pri tomhle testovani projde, nic 
< neznamena. Soubeh (race condition) je typ chyby, ktery se rad objevuje 
< az v ostrem provozu a blbe se ladi).
< 
< Takze pokud to DB nezarucuje a vy chcete mit jistotu, ze k race 
< condition nedojde, nezbyde vam, nez pouzit jedno spojeni.

Ano, presne takovou databazi pouziji. SQLite umoznuje jeden zapis, v pripade
pokusu o druhy soucasny tento selze. Lze to osetrit tak, ze po nejakem
timeoutu se o ten zapis pokusim znovu nebo ze pristup k databazi budu zamykat.
Vybral jsem si druhy zpusob, zda se mi efektivnejsi a mam jiz i prakticky
overeno na modelu, ze plne funkcni i pod stress zatezi.
 
< > Ano, o necem takovem jsem uvazoval, server by mel frontu, do ktere by 
< > komunikacni thready ukladaly pozadavky a jeden thread obsluhujici databazi
< > by je vyrizoval. Pro ukladani dat by to bylo skvele, ale ri cteni dat bych
< > musel resit, jak nactena data predat prislusnemu threadu, ktery by je pak
< > vratil klientovi a to uz se mi zdalo moc komplikovane, skvela prilezitost
< > k rade nahodnych tezko detekovatelnych chyb a tak se mi do toho nechtelo.
< 
< No moje prvni myslenka byla, ze by zapis byl asynchrnoni, zatimco cteni 
< synchrnonni. Tedy asi takhle:
< 
< dataQueue = []
< 
< def save(data):
< 	"""Data ulozi do bufferu a vraci se. Nepristupuje do DB."""	
< 	dataQueue.append(data)
< 	return
< 
< 
< def load(condition):
< 	"""Blokujici. Nacte data z DB a vati je."""
< 	dataSet = dbConnection.command("SELECT firstField FROM mytable WHERE 
< " + condition)	#cteni z DB
< 
< 	data = gainDataFrom(dataSet) #nejak dostane data z recodSetu, co vratila DB
< 	return data;
< 
< 
< Pak musi jeste existovat thread, ktery bude posilat data z dataQueue:
< 
< # ...
< 
< serverIsRunning = true;
< 
< def run():
< 	while(serverIsRunning):
< 		while(len(dataQueue) > 0): sleep(200)
< 		data = dataQueue.popo(0)
< 		dbConnection.command("INSERT \"%s\" INTO mytable" , data) #zapis do DB
< 		
< (omlouvam se za przneni Jazyka, ale v P. jsem uz dlouho nic nepsal, tak 
< jsem trochu pozapomnel)

No, ten load je prave kamen urazu, protoze ten bych volal z ruznych threadu 
a on by bezel v jejich kontextu a sdilel tak mezi nimi spojeni, coz odporuje 
zadani nesdilet spojeni mezi thready.

< Takze zapis (casta operace) se provede rychle a klienta nezdrzuje, 
< zatimco cteni musi pockat na precteni dat z DB (a je tedy pomalejsi).
< 
< K tomu kodu jeste tri poznamky:
< 
< 1) obecne je lepsi pouzivat nejake synchronizacni primitivum na 
< komunikaci mezi thready. Idealni jsou condition variables. Ve fci save 
< by se provadelo neco jako
< dataQueue.notify()	#dava najevo, ze v bufferu cekaji data
< 
< a run by cekal na data :
< 
< dataQueue.wait() 	# misto sleep. Pokud nejsou data usne, a probudi ho 
< dataQueue.notify()

< Syntaxe condition variables asi neodpovida Jazyku. Nikdy jsem v P. cv 
< nepouzival a jsme linej to hledat, tak jen odhaduju.

Jasne, jde o princip. Ja tomu rozumim, thready mi nejsou cizi.
 
< 2) Muze se stat, ze load uvidi nejaky starsi stav DB, protoze data, 
< ktera prisla jeste pred volanim load jsou v dataQueue a nejsou zapsana v 
< DB. To se da vyresit tak, ze load pocka, nez se vsechna doposud dosla 
< data zapisou a az pak cte z DB.

To je take pravda a byl by to problem. Protoze ja budu potrebovat nacitat
aktualni stav, nemohu pripustit, aby si thready (resp. datove terminaly)
meli nesynchronizovana data popr. si prepisovali zaznamy starymi daty.
A zdrzovani loadu si take nemohu dovolit, protoze bych tim zpomaloval
odezvu terminalu, nemohu davat prednost ani cteni ani zapisu, pozadavky
musim vyrizovat tak jak chodi.
 
< Jeste vlastne 4 poznamka. Pokud bude komunikace probihat na urovni TCP a 
< ne treba pomoci RPC, je to jeste jednodussi. Zapis do soketu je 
< neblokujici (pokud se nezaplni buffery u prijemce), takze neni potreba 
< psat zadnou metodu save (jsme uz tak z toho middleware tak zblblej, ze 
< me tohle ocividny reseni ani nenapadlo). Predpokkladam, ze kazdej klient 
< bude mit svy otevreni TCP spojeni na serveru, takze pak staci akorat dat 
< neco jako select/poll na tahle spojeni, a ty co obsahuji nejaka data 
< postupne zpracovat (fce bude velmi podobna fci run()). Load se bude 
< delat bud zvlastnim spojenim, ktery navaze klient se serverem nebo 
< pomoci toho jiz existujiciho predanim nejakeho priznaku, ze jde o 
< operaci load.

Pouziti selectu je vlastne pouziti nethreadoveho reseni obsluhy vice 
pripojeni. Nad tim jsem take uvazoval a pouzil bych ho, kdybych nevyresil
problem sdileni spojeni mezi thready. Davam ale prednost threadovemu reseni,
vyhodnotil jsem si ho jako lepsi pro me potreby. Nektere pozadavky z
terminalu budou casove narocnejsi na zpracovani a behem neho bych blokoval
ostatni spojeni a tim zhorsoval jejich odezvu vic nez u threadoveho reseni.

Dekuji za prinosnou reakci.

Petr Mach
____________________________________________________________
http://www.bezpecnyinternet.cz
http://ad.seznam.cz/clickthru?spotId=94734



Další informace o konferenci Python