Suportan mínima concurrència entre processos a les libdb de Berkeley
August 29, 2007 – 2:21 pmEl support per tenir concurrència entre threads en un backend de les libdb de Berkeley es una de les features incloses en aquesta llibreria, ara bé mitjançant l'enmagatzematge de l'estat d'aquesta concurrencia en el que ells anomenen filesytem files ens permet tenir aquesta concurrència entre processos.
Les libdb de Berkeley a part de tenir un sistema d'integritat referencial propi i fins i tot un sistema complert de transaccionalitat pot ésser configurada per funcionar sota el que poden anomenar global locks, on aquests poden compartir no només informació entre threads - mateix espai d'adreces - si no també entre processos aillats - espai d'adreces separats.
Aquest mode s'anomena Concurrent Data Store Aplications, que és possible inicialitzar mitjançant l'us del que anomenem Environments.
Què és doncs un CDS ? Basicament el que permet configurar aquest entorn es un sistema de multiples lectors concurrents i un únic escriptor a la base de dades, mitjançant locks globals sobre les bases de dades. Per tant la granulitat no serà la pàgina/registre sino tota la base de dades.
Què és un Environment ? Per a poder generar tot un conjunt de metadades associades a la base de dades les libdb de berkeley utilizen aquest mecanisme per poder serialitzar-les en memoria o bé en disc. Entre aquestes metadades podem trobar tot el sistema de concurrència. L'el.lecció d'alguns flags o opcions ens permetran suportar concurrencia entre processo aillats i que no comparteixen espai d'adreces.
Tot i que aquest sistema incorpora el que s'anomena un anti deadlock, proveir concurrència entre processos pot portar-nos a que aquests es generin, en veurem algun exemple.
Com inicialitzar un CDS. Mitjançant el pedaç de codi podem veure els flags utilitzats per a crear un Environment tal i com l'hem plantejat.
if ((ret = db_env_create(&dbenv, 0)) != 0) {
printf("%s: %sn", progname, db_strerror(ret));
goto err;
}
dbenv->set_errpfx(dbenv, progname);
if ( (ret = dbenv->open(dbenv, ROOTPATH, DB_CREATE | DB_INIT_CDB | DB_INIT_MPOOL, 0)) != 0)
{
dbenv->err(dbenv, ret, "environment open: %s", ROOTPATH);
goto err;
}
Tot i no especificar directament que la informació ha de estar serializada en arxius de sistema les libdb de Berkeley realizen aquesta opció per defecte i per tant aquesta podra ser compartida per processos que no s'executen en el mateix espai d'adreces, en cas contrari podem forçar un comportament pure thread mitjançant l'us del flag DB_PRIVATE en el moment de fer la crida open del Environment.
És interessan també comentar lús del flag DB_INIT_POOL a la funció open. Aquest flag força que es regeneri el que s'anomena memory pool subsystem, utilizat per tots els processos que instancien aquest Environment i utilizen algun métode d'accés a la base de dades. Aquesta flag ens pot despertar - com a mínim a mí - certa intriga, i fer-nos dubtar de quina ha de ser la seva utilització i sobretot en quin moment s'ha de fer servir si parlem de processos aillats.
L'arquitectura interna de les libdb de Berkeley no esta documentada i potser caldria referir-se a aquesta inexistent documentació per arribar a una conclusió émpirica, pero en tot cas jo n'he tret algunes deduccions, no exemptes d'estar errònies.
Com ja hem vist el kid de la questió es la comunicació entre processos que no comparteixen espai d'adreces i que aquesta comunicació - suport per a la concurrència - és realitza mitjançant l'us d'arxius, entre ells existeix l'arxiu anomenat memory pool subsystem. Aquest arxiu - suposicions - conté informació important i suceptible per a qualsevol procés que realitzi accesos a les bases de dades que estan contingudes en un mateix Environment, a més a més de serialitzar-se amb unes estructures concretes. Per tant és lògic pensar que l'ús a cada opertura del Environment no és una opció justificada i aquest només hauria de ser utilitzat en el moment de crear el Environment per primera vegada.
Tot i que pugui semblar una justificació força encertada, certes proves amb processos aillats accedint a la mateixa base de dades i obrin el mateix Environment no han confirmat les meves suposicions, i utilitzant un us indiscriminat d'aquest flag no sembla tenir consequencies greus en el funcionament global de la base de dades.
Una vegada creat el Environment podem crear el que anomenarem consumidors de la base de dades, on aquest poden ser processos aillats i que gracies al que hem exposat mantindran una concurrència mínima. Com podem veure en el següent pedaç de codi del consumidor - amb poca utilitat per cert - s'alternen escriptures, lectures i lecutres multiples.
while ( 1 )
{
// random get or put or cursor
if ( op == 0 )
{
..........
ret = dbp->put(dbp, NULL, &key, &data, DB_NOOVERWRITE);
if ( ret != 0 )
{
dbp->err(dbp, ret, "DB->put key = %d value %d", i, i );
}
..........
op++;
}
else if ( op == 1 )
{
..........
ret = dbp->get(dbp, NULL, &key, &data, 0);
if ( ret != 0 )
{
dbp->err(dbp, ret, "DB->get key = %d ", i);
}
..........
op++;
}
else
{
..........
dbp->cursor(dbp, 0, &cursorp, DB_WRITECURSOR);
............
while ( (ret= cursorp->c_get(cursorp, &key, &data, DB_NEXT)) == 0 )
sleep(1);
Tot i ometre de forma voluntaria part del codi, podem veure que durant un bucle infinit es realitzen les tres operacions comentades, entre elles voldria fer notar la tercera, la lectura multiple mitjançant un cursor.
Aquest cursor com podeu veure es crea amb el flag DB_WRITECURSOR, aquest flag tot indica que la seguent lectura implicara en el algun moment alguna escriptura a la base de dades, i tot i que aixo no es cert ja que no en realitzarem cap forçara a les llibreries a obrir un cursor en mode exclusiu i per tant evitant la concurrència d'altres lectors a la base de dades, que és en definitiva el que volem testejar.
En aquesta captura de pantalla podem observar dos consumidors de la base de dades, un d'ells bloquejat a l'espera que l'altre acabi i tanqui el cursor, especialment preparat per forçar un lock.
Podeu trobar si voleu tan el codi que crea el Environment aquí i el dels consumidors aqui, per acabar de completar les línies de codi omeses per falta d'espai i interes global.
Tal i com ja he comentat al principi amb aquest disseny no estem salvats de possibles dead locks, un exemple força aclaridor és una situació com la següent :
Imaginem aquest dos consumidors corren en paral.lel on un d'ells manté el cursor amb propietats d'escriptura activades i un altre a l'espera que aquest sigui alliberat, si durant aqusetes circumstàncies matem mitjançant un signal el consumidor amb el cursor actiu la base de dades quedarà completament bloquejada.
Per tant seria optim que en qualsevol circumstància i sobretot en la descrita - ús de signals - es tinguin presents els possibles problemes de concurència inherents a un sistema com el presentat i s'adoptin les solucions que impossibilitin circumstàncies tan desagradables com un bloqueig de la base de dades.
