ugrás a tartalomhoz

Php - data access concurrency testing

inf · 2014. Ápr. 5. (Szo), 01.20
Maguk a PHP-s alkalmazások nem multithread-esek, az apache elég jól menedzseli ezt a témát, viszont az erőforrásoknál, amiket használnak már felléphetnek különböző concurrency-vel kapcsolatos hibák, mint lock contention, long term blocking, deadlock. Tudtok olyan költséghatékony módszerről, amivel ezekre lehet tesztelni az alkalmazást?

Úgy néztem a c# és java esetében van egy csomó külön megoldás és eszköz, a PHP-ben viszont a thread-ek hiánya miatt nem született ilyen, legalábbis én nem találtam. Hogyan lehetne ilyen eszközt készíteni, illetve hogyan kell kezelni az ilyen jellegű hibákat?

Mondok egy példát, pl lock contention akkor léphet fel, ha a kiszolgált tartalom mellé teszünk egy mezőt az adatbázisban, amin folyamatosan növeljük az olvasások számát. Így a népszerű cikkeknél ennek a mezőnek az írása folyamatosan lockolja a hozzáférést a tartalomhoz... Hogyan kerülhető el ez a probléma, és hogyan mutatható ki automatizált tesztekkel?
 
1

Deadlocks generally lead to

inf · 2014. Ápr. 5. (Szo), 01.35
Deadlocks generally lead to intermittent errors. They are almost always caused by application logic problems. These generally occur when the application does not access data in a consistent sequence. They are also very timing and data dependent, which can make them very hard to reproduce.


Ezek szerint ha mindig ugyanolyan sorrendben nyúlunk a kéréseinkben az erőforrásainkhoz, akkor a deadlock elkerülhető. Ez maximum ORM-el biztosítható, ha beteszünk hozzá egy modult, ami betartat egy bizonyos erőforrás sorrendet, de ezt kikényszeríteni elég kényelmetlen lehet. Ha kimutatható a dolog concurrency tesztekkel, akkor is el tudok képzelni olyan helyzetet, ahol a kód logikája megkívánja a sorrendet, amit megadtunk. Zsákutca, deadlock. :-)

Azt hiszem ez megint egy olyan téma, amiről könyveket lehetne írni, megoldása viszont nincsen... Azért kíváncsi vagyok kinek mi a tapasztalata a concurrencyvel?
4

A deadlock egy másik

BlaZe · 2014. Ápr. 5. (Szo), 09.20
A deadlock egy másik probléma, mint amit a bevezetőben írtál. És igen, a deadlockot úgy kerüljük el, hogy mindig ugyanabban a sorrendben alkalmazzuk a lockot. Egész pontosan a legjobb módja a deadlock gyártásnak, ha 2 párhuzamos szálból/akármiből fordított sorrendben kérünk lockot. Ebből 100%, hogy deadlock lesz :) Ez a sorrend természetesen biztosítható az alkalmazásban ORM nélkül is. De ha csak nem nagyon muszáj, kerüljük az exclusive lockokat az alkalmazásban, mert ez önmagában a concurrency legnagyobb ellensége. Egészen konkréten az a kódrészlet, amiben exclusive lock van, nem párhuzamosítható. Érdemes kicsit olvasni Amdahl törvényéről a témában, hogy hogyan skálázódik egy rendszer a nem párhuzamosítható programrészek arányában. Utána azt a szót, hogy lock kb el is fogod felejteni egy életre :)
5

Jó, de lock nélkül hogyan

inf · 2014. Ápr. 5. (Szo), 12.40
Jó, de lock nélkül hogyan védjem meg a data integrity-t?

Tegyük fel most, hogy ORM nélkül simán SQL-t küldök tranzakciókban az adatbázisnak... Az adatbázis legyen pgsql vagy mysql, kinek amelyik szimpatikusabb...
7

Ha SQL adatbázisról van szó,

BlaZe · 2014. Ápr. 5. (Szo), 13.58
Ha SQL adatbázisról van szó, akkor ez az adatbázisszerver feladata, nem a tiéd. A foreign keyk, tranzakciók ezért vannak. Az ORM ebben nem játszik, illetve csak delegálja az adatbáziskezelőnek, ha lockolni szeretnél, vagy kell. Máshogy nem is lehet, legalábbis PHP-ban. Ott nem tud nagy globális állapotot menedzselni, ahol kezeli a lockokat stb...
10

Köszi, így már összeállt,

inf · 2014. Ápr. 5. (Szo), 15.06
Köszi, így már összeállt, szóval csak azt tudom beállítani adatbázison, hogy milyen jellegű lock-okat használjon (pessimistic, optimistic, blabla), azt nem, hogy pontosan mikor és hogyan... Ezek szerint ha csak db-t használok, és más erőforrást nem, akkor feltételezhetem, hogy az összes ilyen jellegű problémát a db majd megoldja helyettem... Még nem jutottam el olvasásban addig, hogy konkrétan belenézzek a pgsql manual concurrency control fejezetébe... Lehet, hogy kicsit elhamarkodott volt a topic indítás...
11

Közben nézem, nem csak a lock

inf · 2014. Ápr. 5. (Szo), 16.26
Közben nézem, nem csak a lock modelt kell beállítani, hanem az isolation level-t is minden tranzakciónál, hogy megfelelően működjön... Ez meg már olyan dolog, amit érdemes tesztelni, hogy jól be van e állítva, vagy nincsen... Ha a locking model és az isolation level egymásnak ellentmondóan van beállítva, akkor előfordulhat, hogy egy-egy update fele elveszik konkurrens kéréseknél...

Összességében ha nincs megfelelően bekonfigolva az adatbázis, vagy egy-egy trazakció, az okozhat gondokat alkalmazás szinten, szóval nem áll meg az az érvelés, hogy mindent bízzunk az adatbázisra, muszáj a concurrency-vel kapcsolatos dolgokat beállítani kézzel. Ha meg ezeket be kell állítani kézzel, akkor hibázhatunk, tehát szükség van tesztekre ezzel kapcsolatban. Concurrency tesztek írása ilyen jelenségekre meg PHP-ben távol áll az egyszerűtől szerintem.
13

Nem értem itt mit akarsz

BlaZe · 2014. Ápr. 5. (Szo), 21.37
Nem értem itt mit akarsz tesztelni. A db-t? A drivert? Az ORM-et? Ezeket miért te akarod tesztelni? És miért akarod állítgatni az isolation levelt? Normális adatbáziskezelőkben az read committed, és teljesen jól van az úgy.

Ha a locking model és az isolation level egymásnak ellentmondóan van beállítva, akkor előfordulhat, hogy egy-egy update fele elveszik konkurrens kéréseknél...
Erre tudsz példát mondani, hogy mire gondolsz?

Összességében ha nincs megfelelően bekonfigolva az adatbázis, vagy egy-egy trazakció, az okozhat gondokat alkalmazás szinten, szóval nem áll meg az az érvelés, hogy mindent bízzunk az adatbázisra, muszáj a concurrency-vel kapcsolatos dolgokat beállítani kézzel. Ha meg ezeket be kell állítani kézzel, akkor hibázhatunk, tehát szükség van tesztekre ezzel kapcsolatban.
Nagyon durván el kéne gondolkoznom rajta, hogy csináltam-e ilyesmit valaha életemben... Szerintem nem, mert nem volt rá szükség. DB lockot se emlékszem, hogy nagyon használtam volna a select for update-en kívül. Azt azért alkalmanként igen.
15

Nem értem itt mit akarsz

inf · 2014. Ápr. 6. (V), 09.27
Nem értem itt mit akarsz tesztelni. A db-t? A drivert? Az ORM-et? Ezeket miért te akarod tesztelni? És miért akarod állítgatni az isolation levelt? Normális adatbáziskezelőkben az read committed, és teljesen jól van az úgy.

Az alkalmazást akarom tesztelni, nem a keretrendszert, adatbázist, vagy drivert, amit használok. Az isolation level állítható, és úgy nézem, hogy pl keresésnél érdemesebb serializable-t használni, mert read committed esetén előfordulhat, hogy olyan találatok kerülnek fel a listára, amelyek nem is teljesítik a keresési feltételeket, mert éppen menet közben egy másik tranzakció módosította őket.

Erre tudsz példát mondani, hogy mire gondolsz?

Olyasmire gondoltam, hogyha optimistic a locking, és nem read committed az isolation level, hanem serializable, akkor pl egy bankszámlán elveszhetnek pénzmozgások, ahogy azt a pgsql dokumentáció is írja. Nyilván nem azt szeretném tesztelni, hogy milyen isolation level beállítás van, hanem hogy az adott kód megfelelően reagál e a konkurrens kérésekre. Pl ha egy bankszámláról egyszerre vonok le 5000Ft-ot és teszek fel 6000-et, akkor az előző állapothoz képest a számla mindig 1000-es pluszban legyen.

Nagyon durván el kéne gondolkoznom rajta, hogy csináltam-e ilyesmit valaha életemben... Szerintem nem, mert nem volt rá szükség. DB lockot se emlékszem, hogy nagyon használtam volna a select for update-en kívül. Azt azért alkalmanként igen.

Hát kicsiben valószínűleg nem éri meg. Ahogy néztem .NET-hez van egy CHESS nevű tool, ami multithread concurrency problémák azonosításában segít. Olyasmiről nem tudok, ami adatbázissal kapcsolatos, de 2 napja foglalkozom a dologgal...
Jelenleg még azt sem tudom, hogy olyan automatizált concurrency tesztet hogyan lehetne írni, aminél garantált, hogy egyik tranzakció egy bizonyos módon üközik a másikkal minden esetben, vagy többszöri futásnál legalább beazonosítható, hogy milyen jellegű ütközés okozza a hibát. Nagyon bonyolult téma...
18

Az isolation level állítható,

BlaZe · 2014. Ápr. 6. (V), 21.31
Az isolation level állítható, és úgy nézem, hogy pl keresésnél érdemesebb serializable-t használni, mert read committed esetén előfordulhat, hogy olyan találatok kerülnek fel a listára, amelyek nem is teljesítik a keresési feltételeket, mert éppen menet közben egy másik tranzakció módosította őket.
Ez miért nem teljesíti a keresési feltételt? Read committed esetén az adatbázisnak mindig az aktuális állapotát látod, azon dolgozol, serializable esetén pedig egy korábbi snapshotot. Inkább mutatnál olyat, ami már nincs is a db-ben?

Hát kicsiben valószínűleg nem éri meg. Ahogy néztem .NET-hez van egy CHESS nevű tool, ami multithread concurrency problémák azonosításában segít. Olyasmiről nem tudok, ami adatbázissal kapcsolatos, de 2 napja foglalkozom a dologgal...
A chesst nem ismerem, de multithreades teszteket azért már írtam párat, viszont az tökmás, mint amit te mondasz, mert ott a saját kódod biztosítja a thread safetyt. Adatbázis esetében ezt rábízod a db-re. Hogy a kódod hogy viselkedik olyan szituációban, ahol 2 hívás közben változik/nem változik ugyanaz az adat, az egy teljesen másik probléma, ahhoz nem kell adatbázist belevenni a tesztbe. Ezért kérdezgetem ennyit mit szeretnél itt valójában tesztelni, mert a kódod teszteléséről beszélsz, és közben isolation levelről, meg lockingról írsz, ami nem a te kódodról szól ezesetben.
20

Inkább mutatnál olyat, ami

inf · 2014. Ápr. 7. (H), 01.51
Inkább mutatnál olyat, ami már nincs is a db-ben?


Hát ezt szerintem döntse el maga, hogy melyik a kisebbik rossz...

A chesst nem ismerem, de multithreades teszteket azért már írtam párat, viszont az tökmás, mint amit te mondasz, mert ott a saját kódod biztosítja a thread safetyt. Adatbázis esetében ezt rábízod a db-re. Hogy a kódod hogy viselkedik olyan szituációban, ahol 2 hívás közben változik/nem változik ugyanaz az adat, az egy teljesen másik probléma, ahhoz nem kell adatbázist belevenni a tesztbe. Ezért kérdezgetem ennyit mit szeretnél itt valójában tesztelni, mert a kódod teszteléséről beszélsz, és közben isolation levelről, meg lockingról írsz, ami nem a te kódodról szól ezesetben.


Ez így nem igaz, mert a kódommal tudok lockokat beállítani, és tudom változatni, hogy milyen isolation level-t használjon a tranzakció. Szóval ugyanúgy függhet a kódomtól a dolog.

De fussunk neki még egyszer. Én azt szeretném, hogy meghatározok egy data access layer-t, és ahhoz írok concurrency teszteket, amik a layer felületét használják, és teljesen függetlenek annak a megvalósításától. Így bármikor, amikor lecserélem a megvalósítást, a tesztek ugyanúgy használhatóak lesznek concurrency issuek kizárására. Ennyi a célom. Nekem tökmindegy, hogy ezeket az issue-kat az adatbázis oldja e meg, vagy a saját kódom, a teszteknek ehhez semmi köze. Csak azt kell biztosítaniuk, hogy meg vannak oldva ezek a problémák.
22

Én azt szeretném, hogy

Hidvégi Gábor · 2014. Ápr. 7. (H), 08.28
Én azt szeretném, hogy meghatározok egy data access layer-t, és ahhoz írok concurrency teszteket, amik a layer felületét használják, és teljesen függetlenek annak a megvalósításától. Így bármikor, amikor lecserélem a megvalósítást, a tesztek ugyanúgy használhatóak lesznek concurrency issuek kizárására.
Eddig még nem írtam olyan programot, ahol párhuzamos feldolgozásra lett volna szükség, de olvastam már jópár írást a témában. Sajnos már nem emlékszem, melyikben, de valahol írták, hogy ilyen szoftvereket tesztelni/hibakeresni rendkívül nehéz, mert csak különleges esetekben lehet összeakadásokat kiváltani, azaz nehéz reprodukálni a hibákat.
24

Itt sincs szükség párhuzamos

inf · 2014. Ápr. 7. (H), 15.10
Itt sincs szükség párhuzamos feldolgozásra magában a programban. A concurrency issue-kat az okozza, ha sok felhasználó nézi egyszerre az oldalt, és összeakadnak a kéréseik. Pl ugyanazt a cikket ketten akarják ugyanabban az időpontban módosítani, stb...

A tesztelés az érdekes kérdés. Ugye egy multithread alkalmazásnál, meg itt is párhuzamosság okozza a hibákat, csak kicsit másképp... Ott a párhuzamosság a kódba van építve, itt meg a forgalomtól függ. Ugyanúgy random módon jön elő a probléma. Én úgy néztem, hogy a concurrency-vel foglalkozó eszközök, legalábbis az a kevés, ami van, az szinte mind úgy dolgozik, hogy a konkrét implementációt elemzi, és nem valami általános felületet ad a párhuzamos kérések tesztelésére. PHP-ben olyan eszközről tudok csak, amivel lehet szimulálni mondjuk egyszerre 1000 HTTP kérést, és meg lehet nézni vele, hogy nagy terhelésnél hogy skálázódik az oldal. Ennyi, amit tud, concurrency issue-k azonosítására nem igazán megfelelő. Nem tudom hogyan lehetne olyan test runner-t írni, ami egy időpontra időzít kéréseket, vagy amivel legalább sok hívás után el lehetne érni ilyesmit, és pontosan be lehetne azonosítani a sorrendet. Pl a chess-nél az van, hogy megpróbál többféle hívási sorrendet a párhuzamos kódnál, aztán ha valamelyiknél gond van, akkor azt jelzi, és egyértelműen vissza tudja játszani. De ugye ott a chess tud a thread-ekről, és mindenről, mert azok a kódba vannak építve. Itt, hogy felületet tesztelek, és nem implementációt, max azt lehet, hogy veszel két kérést, és a hívási idejüket tologatod egymáshoz képest, és így módosítod a sorrendet, hátha egyszer összeakad a kettő... Ezt mondjuk egy xdebug-hoz hozzákötve talán ki lehet számolni, hogy pontosan milyen kód milyen sorrendben hívva akadt össze a két kérésben. Szóval elméletileg lehetséges ilyet csinálni, gyakorlatilag meg nincs megfelelő test runner, ami párhuzamosan tudna kéréseket bonyolítani, mert nincs thread php-ben. Maximum a HTTP kérések szintjén lehet ilyet írni, az meg nem ugyanaz, mintha direktbe függvényeket vagy metódusokat hívnál, mert belekerülnek még fentebbi rétegek is. Talán lehet efölé a HTTP kéréses változat fölé heggeszteni egy test runner felületet, ami már képes fogadni objektumokat. Ehhez a szerializálást meg ugye a wakeup-ot kell megcsinálni ezeken az objektumokon.
26

Ide gyűjtöm a megoldásokat

inf · 2014. Ápr. 7. (H), 15.31
Ide gyűjtöm a megoldásokat párhuzamos PHP kód futtatásra:

- cURL multi - több process (párhuzamos http kérések)
- gearman - valszeg több thread
- pcntl_fork - több process
- exec - több process
- pthreads - több thread

Azt hiszem ez nagyjából egy teljes lista: http://code.hootsuite.com/parallel-processing-task-distribution-with-php/

Itt van komolyabb technikai leírás, hogy hogyan lehet kivitelezni: http://andrey.hristov.com/projects/php_stuff/pres/writing_parallel_apps_with_PHP.pdf

A phpunit-hoz van egy paratest nevű runner, ami párhuzamosan futtatja a unit test-eket. Ez nyilván nem az, amit akarok, de hasznos, mert felgyorsítja a tesztelést mind unit test-ek, mint integrációs tesztek terén.

Összességében lehetséges párhuzamosan programozni php-ben, szóval elméletben lehetséges olyan testcase-t írni, ami alkalmas olyan concurrency issuek tesztelésére, amit a közös erőforrások lockolása vagy kérések ütközése és esetleg mergelése okoz. Sajnos a gyakorlat azt mutatja, hogy senki nem csinált még ilyet, és hogy rengeteg váratlan probléma és gányolás árán lehetne csak megírni... Nekem erre jelenleg nincs meg a tudásom és a kapacitásom sem, hogy megtanuljam az ezzel kapcsolatos dolgokat. Pár helyre írtam ezzel kapcsolatban feature request-et, hátha bevállalja valaki, de erre sem látok sok esélyt, szóval erről ennyit...
2

Ezeket a zárolási problémákat

Hidvégi Gábor · 2014. Ápr. 5. (Szo), 08.21
Ezeket a zárolási problémákat maguk a használt szoftverek lerendezik, neked nem kell vele foglalkoznod, csak akkor, ha a dokumentációban kiemelik, hogy ez nem thread safe.
3

Egyetértek, azt ne akarjuk

BlaZe · 2014. Ápr. 5. (Szo), 09.13
Egyetértek, azt ne akarjuk tesztelni, hogy a tranzakció jól működik-e a db-ben. Azt se teszteljük, hogy 1+1 az 2 az adott környezetben. Amit van értelme tesztelni, hogy heavy write közben hogyan skálázódik az alkalmazásunk. De ez már a performancia tesztelés kérdésköre, és erre van nagyon jó tool is, pl a JMeter.

Egyébként picit meglepett, amit inf3rno írt. Read committed mellett a reader akkor fogja csak látni az új sort, amikor azt becommitolták. És emellett mindig lát olyan verziót ami az adott pillanatban valid, ez az mvcc lényege. Ergo a readernek nem kell várjon a writerre.
6

Egyébként picit meglepett,

inf · 2014. Ápr. 5. (Szo), 12.42
Egyébként picit meglepett, amit inf3rno írt. Read committed mellett a reader akkor fogja csak látni az új sort, amikor azt becommitolták. És emellett mindig lát olyan verziót ami az adott pillanatban valid, ez az mvcc lényege. Ergo a readernek nem kell várjon a writerre.


Nem vagyok benne biztos, hogy ez minden adatbázisra igaz. Bár kevés a tapasztalatom vele, csak az alapján írtam, amiket olvastam a témában, pl: Database Locking: What it is, Why it Matters and What to do About it

Btw. nekem egyáltalán nem tűnik 1+1 szintű problémának az, hogyha rosszul van megírva az alkalmazás, akkor deadlockot okozhat, vagy rosszul skálázódik... Érveket várok! ;-)

Azt olvastam, hogy a deadlock ellen védettek az adatbázisok, de gondolom a fájlrendszer nem az... Ha van két controller action-öm, amik fordított sorrendben lockolnak két fájlt, akkor az kapásból okozhat deadlock-ot... Egy ilyen hibát hogyan kezelek, egyáltalán mit lát a user? Csak vár-vár, aztán az egyik script elszáll timout-al mindenféle magyarázat nélkül, az adatok, amiket begépelt meg elvesznek...
8

Nem vagyok benne biztos, hogy

BlaZe · 2014. Ápr. 5. (Szo), 14.18
Nem vagyok benne biztos, hogy ez minden adatbázisra igaz.
Azok az RDBMS-ek, amikkel én dolgoztam eddig ezt mind tudták (jó, mysql myisammal nem, de hát...). Kell is, enélkül működésképtelenek lennének.

Btw. nekem egyáltalán nem tűnik 1+1 szintű problémának az, hogyha rosszul van megírva az alkalmazás, akkor deadlockot okozhat, vagy rosszul skálázódik... Érveket várok! ;-)
Nem is erről szólt az 1+1, hanem hogy azt ne teszteljük, hogy az RDBMS jól kezeli-e a tranzakciókat. Az nem a mi alkalmazástesztjeink feladata.

Az, hogy hogy skálázódik, az performancia probléma, és ennek megoldása feladatfüggő. Általánosságban azt lehet elmondani, amit múltkor is írtam: ne akarjunk egy erőforrást párhuzamosan írni. Ahol lehet, használjunk aszinkron műveleteket, azok mentén jól lehet skálázni stb. De ez tényleg elég általános.

Azt olvastam, hogy a deadlock ellen védettek az adatbázisok
Hát inkább magukat nem deadlockolják. Te tudsz deadlockot okozni, ha 2 exclusive lockot kérsz valamire fordított sorrendben. Ez értelemszerűen mindig deadlockot fog okozni. Ezeket a lockokat (és mindent, ami más párhuzamos folyamat eredményére vár) ezért praktikusan timeouttal szokás kérni, hogy megadjuk az alkalmazásnak az esélyt rá, hogy kijöjjön az ilyen szituációkból. Nyilván ez az adott requestet el fogja kaszálni, de legalább van rá esély, hogy a többi sikeres lesz. Míg ha lefogod a szálakat/processzeket egy (több) deadlockkal, akkor ott kb csak a restart fog segíteni.

Egyébként pedig a párhuzamos file írkálás webről szerintem eléggé antipattern a fentiek miatt. Ennél az sokkal jobb, ha egy queueba rámolod az ilyen jellegű requesteket, amiket valami háttérfolyamattal tudsz feldolgozni, és a folyamat eredményét tudod pollozni, vagy valami ilyesmi. Úgy adott esetben lock sem kell, ráadásul kontrollált keretek között párhuzamosítható.
9

Tudnál példákat mondani, hogy

inf · 2014. Ápr. 5. (Szo), 15.04
Tudnál példákat mondani, hogy milyen ilyen jellegű hibákba futottál már bele PHP-val kapcsolatban (skálázódás, deadlock, stb...), és hogyan derítetted fel, illetve kezelted őket? Nem igazán találok PHP-val kapcsolatban semmi ilyesmit...

Egyébként pedig a párhuzamos file írkálás webről szerintem eléggé antipattern a fentiek miatt. Ennél az sokkal jobb, ha egy queueba rámolod az ilyen jellegű requesteket, amiket valami háttérfolyamattal tudsz feldolgozni, és a folyamat eredményét tudod pollozni, vagy valami ilyesmi. Úgy adott esetben lock sem kell, ráadásul kontrollált keretek között párhuzamosítható.


Nem muszáj 2 fájl példájánál maradni, legyen az egyik erőforrás fájl, a másik meg adatbázis. Mondjuk kép feltöltés, album szerkesztés, törlés, stb... esetében elfordulhat ez a kombináció... Itt is két erőforrás van, és ha két controller-ben fordított sorrendben lockoljuk őket, akkor az deadlock-ot okozhat. Ez is ugyanúgy fájlrendszeres probléma...

A loggolás szintén egy érdekes kérdés, mert bárhova loggolsz, az lehet szűk keresztmetszet egy oldalnál... PHP-ban nem nagyon van lehetőség arra, hogy rábízd az ilyesmi egy deamon-ra úgy, hogy nem is foglalkozol azzal, hogy sikerült e loggolni, vagy nem, és csak a kérést küldöd el neki...
14

Tudnál példákat mondani, hogy

BlaZe · 2014. Ápr. 5. (Szo), 22.06
Tudnál példákat mondani, hogy milyen ilyen jellegű hibákba futottál már bele PHP-val kapcsolatban (skálázódás, deadlock, stb...), és hogyan derítetted fel, illetve kezelted őket? Nem igazán találok PHP-val kapcsolatban semmi ilyesmit...
PHP kapcsán nem emlékszem ilyesmire, kb 10 éve nem foglalkozom PHP-val munka szinten, leszámítva 1-2 kisebb alkalmi melót. Javaban láttam deadlockot, adatbázisban is. Mindkettő képes felismerni, és infót adni arról, hogy ki lockol kit (Java esetében ez kb addig igaz, amíg synchronized-ot használsz). De kijavítani azért az alkalmazástól függően bonyolult is lehet. Ez nem feltétlenül triviális probléma. Sokkal egyszerűbb jól megírni egy ilyet, mint utólag bogarászni :) Persze ezt könnyű mondani :)

A skálázódáshoz: ahogy fentebb írtam, valami teljesítmény méréshez használatos toollal tudod kideríteni. Én használtam régebben siege-t, és JMetert, utóbbi azért lényegesen többet tud. Ebből megkapod hogyan skálázódik, hogy mi okozza magát az esetleges rossz skálázódást, azt meg leginkább valami profilerrel, kódbogarászással lehet megtalálni. PHP kapcsán ilyesmivel nem vagyok képben. Általánosságban azt lehet elmondani, hogy az olvasás jól skálázható, az írás nem. Ez CPU/memória szinttől adatbázisig így van. Tehát ez nem nyelvfüggő dolog.

Itt is két erőforrás van, és ha két controller-ben fordított sorrendben lockoljuk őket, akkor az deadlock-ot okozhat. Ez is ugyanúgy fájlrendszeres probléma...
Ez kód probléma, semmi köze a filerendszerhez azon kívül, hogy esetleg abban tartod a lockot magát, vagy hogy a lockkal védett programrészed dolgozik vele.

A loggolás szintén egy érdekes kérdés, mert bárhova loggolsz, az lehet szűk keresztmetszet egy oldalnál... PHP-ban nem nagyon van lehetőség arra, hogy rábízd az ilyesmi egy deamon-ra úgy, hogy nem is foglalkozol azzal, hogy sikerült e loggolni, vagy nem, és csak a kérést küldöd el neki...
Mi a baj pl a sysloggal?
16

Ez kód probléma, semmi köze a

inf · 2014. Ápr. 6. (V), 09.30
Ez kód probléma, semmi köze a filerendszerhez azon kívül, hogy esetleg abban tartod a lockot magát, vagy hogy a lockkal védett programrészed dolgozik vele.

Persze, hogy kód probléma, ezért kell automatizált teszt, vagy valamilyen tool rá, hogy kimutassa...

Mi a baj pl a sysloggal?

Semmi. Közben átgondoltam, a loggolással úgy általánosságban csak akkor lehet baj, ha pessimistic lock-ot használsz az írásához. Akkor baromi rosszul fog skálázódni...
28

A fájlrendszeres problémára

inf · 2014. Ápr. 9. (Sze), 03.34
A fájlrendszeres problémára is van megoldás. Érdemes a command pattern-t használni, automatikusan rendeztetni pl fájlnév abc, vagy bármi egyéb szerint, illetve az adatbázis tranzakciót mindig a végére hagyni. Így az adatbázist a beépített rollback-jével lehet visszaállítani, a fájlrendszerhez meg kézzel kell írni egyet. Ez sem tökéletes megoldás, de kezdetnek elég.

Ezen kívül még vannak olyan fájlrendszerek, amiknél lehetséges fájlrendszer tranzakciókat használni. Ez mondjuk nagyon ritka még szerintem.

A tesztelés részéhez még visszatérek később, most sajnos nincs időm rá. Egyébként is elakadtam vele, mert nem találok megfelelő parallel test runner-t, ami minden platformon és különösebb telepítgetés, fordítgatás nélkül is elmegy.

os-php-multitask

running-multiple-processes-in-php
simple-async-php-without-pcntl-stuff
ParallelProcessing
node/view/254
adapt-php-parallel-execution-function-for-windows

Csak azért írom fel őket, hogy később ne kelljen keresgélni megint... Szinte mindegyik proc_open-el meg aszinkron streameléssel variál valamit, de még nem teljesen értem, nem volt időm tanulmányozni a logikáját.
29

Tisztelt Kolléga

Vilmos · 2014. Ápr. 9. (Sze), 09.14
Inf3rno 24:
Itt sincs szükség párhuzamos feldolgozásra magában a programban. A concurrency issue-kat az okozza, ha sok felhasználó nézi egyszerre az oldalt, és összeakadnak a kéréseik. Pl ugyanazt a cikket ketten akarják ugyanabban az időpontban módosítani, stb...

Pontosan. Az ütközést a párhuzamos munka okozza, mármint az egyidejű írási kérés. Ez akkor gond, ha jó eredmény csak soros feldolgozás esetén lehetséges. Megoldás, nem tesszük lehetővé a párhuzamos írást. A zárolás éppen a sorba rendezést szolgálja. Minden írási feladat konkurencia nélkül futhat, a többi addig vár. Rejtélyes, hogyan lett ebből a tiszta képből teljes párhuzamosítás.

Inf3rno 28:
Ezen kívül még vannak olyan fájlrendszerek, amiknél lehetséges fájlrendszer tranzakciókat használni. Ez mondjuk nagyon ritka még szerintem.

A már kihalt "Novell" op.rendszerben volt ilyen, 20 évvel ezelőtt. Adatbázis és tranzakció kezelés hiányában használhattad tetszőleges fájlokra. Lehet, valamelyik Linux,BSD ismét feltalálta. Szerintem jobb adatbázissal dolgozni.

Blaze 27:
Ha olyan a feladat, hogy van lehetőség async írásra, akkor az egy opció, hogy egy queueba rámoljuk a write requesteket, és majd megtörténik, amikor megtörténik.

Lehet így is. A háttérben futó program egy "démon", azzal kell kommunikálni. A "démon" feladata a sorba rendezés, nem a miénk. Adatok átküldve szabvány serializált formában, némi kódolt algoritmussal vegyítve. Értesítés a feldolgozásról, polling vagy call-back ... Nem egyszerű.
34

Pontosan. Az ütközést a

inf · 2014. Ápr. 9. (Sze), 14.00
Pontosan. Az ütközést a párhuzamos munka okozza, mármint az egyidejű írási kérés. Ez akkor gond, ha jó eredmény csak soros feldolgozás esetén lehetséges. Megoldás, nem tesszük lehetővé a párhuzamos írást. A zárolás éppen a sorba rendezést szolgálja. Minden írási feladat konkurencia nélkül futhat, a többi addig vár. Rejtélyes, hogyan lett ebből a tiszta képből teljes párhuzamosítás.


Az, amit írsz a pessimistic locking, és azért nem használják, mert nagyon rossul skálázható. Minimális forgalom felett a látogatóid elkezdenek várni a lockok feloldására, és nagyon belassul az oldal...

A már kihalt "Novell" op.rendszerben volt ilyen, 20 évvel ezelőtt. Adatbázis és tranzakció kezelés hiányában használhattad tetszőleges fájlokra. Lehet, valamelyik Linux,BSD ismét feltalálta. Szerintem jobb adatbázissal dolgozni.


Nem most inkább windows közeli rendszereknél divat:
http://msdn.microsoft.com/en-us/magazine/cc163388.aspx

Lehet így is. A háttérben futó program egy "démon", azzal kell kommunikálni. A "démon" feladata a sorba rendezés, nem a miénk. Adatok átküldve szabvány serializált formában, némi kódolt algoritmussal vegyítve. Értesítés a feldolgozásról, polling vagy call-back ... Nem egyszerű.

Persze a daemonok megoldás, de ahhoz általában dedicated server kell, és ha már daemonokat kell használni, és van egy dedicated server-em, akkor fix, hogy nem php lesz a nyelv, amit használni fogok, hanem valami olyan, amiben könnyen lehet ilyesmit írni, mondjuk nodejs.
38

Nem tudható milyen terhelésre

Vilmos · 2014. Ápr. 9. (Sze), 15.05
Nem tudható milyen terhelésre készülsz ... Sejthetően démont akarsz.
Egyébként a zárolás nem azért van hogy az olvasási műveleteket gátolja. Tudomásom szerint nyitott tranzakció alatt gond nélkül olvasható az adatbázis. A zárolás megoldható úgy is, hogy minden művelet lehetetlenné váljon, de úgy is, hogy csak a konkurens írást akadályozza. Egyszer próbáltam ki a "lock table"-t mysql 5.akárhánynál, vagy nem olvastam figyelmesen vagy nem említik, de utána másik felhasználó nem fért hozzá az adatbázishoz sem. Ezt tényleg ne.

Egyébként eset függő mit lehet használni: stackoverflow locking

A Microsoft-ot megnéztem. API szinten diszkrét fájl műveleteket "véd". Ez nem azonos az egykori Novell tudásával. Ott az volt a lényeg, a fájlok írása esett tranzakciós védelem alá. Például, egy nagyméretű text fájlnál írhattál a közepébe, meg mindenfelé, ez mind véglegesíthető vagy visszagörgethető volt.
12

Eddig a peldaid rosszak...

Sanyiii · 2014. Ápr. 5. (Szo), 21.04
A peldad santit. Egyszeru low_priority update a cikkek olvasasanak update-je. A masik, a loggolas is santit, az sima insert delay, ekkor a php nem var az eredmenyre.

Osszegezve, lockolast szinte soha sehova, csak ha meg tudod indokolni (eddig nagyon ritkan lattam en magam olyat, ahova ez kellett. Olyat szoktam latni, ahol a hozza nem ertes miatt lockoltak, mert pl. a megfelelo eljarast nem ismertek). Az izolacios szintekhez se nyulj hozza lecci. Mind a 4-nek megvan a hatranya. 99,9999999999%-ban az alap repeatable read a legjobb (legkisebb rossz, bar nem olyan rossz az).
17

Osszegezve, lockolast szinte

inf · 2014. Ápr. 6. (V), 09.40
Osszegezve, lockolast szinte soha sehova, csak ha meg tudod indokolni (eddig nagyon ritkan lattam en magam olyat, ahova ez kellett.

Én elég sűrűn láttam olyat, hogy select for update.

Az izolacios szintekhez se nyulj hozza lecci. Mind a 4-nek megvan a hatranya.

Én úgy néztem, hogy érdemes kérés típustól függően megválasztani az izolációs szintet. Mondjuk ha írni akarsz, akkor jó az alap read committed, ha meg keresni, akkor jobb a serializable. Mást pgsql nem tud, legalábbis a 8.4-es verzió, amit most használok.

A peldad santit. Egyszeru low_priority update a cikkek olvasasanak update-je.

Ezt kifejtenéd?
19

Egyszerre több táblát kell

H.Z. · 2014. Ápr. 6. (V), 22.19
Egyszerre több táblát kell módosítani, azt hogy oldod meg lock nélkül, a konzisztencia megtartása mellett?
(és milyen lockról beszélünk? Tábla? Sor? Egyéb?)
21

Egyszerre több táblát kell

inf · 2014. Ápr. 7. (H), 01.56
Egyszerre több táblát kell módosítani, azt hogy oldod meg lock nélkül, a konzisztencia megtartása mellett?
(és milyen lockról beszélünk? Tábla? Sor? Egyéb?)


Lock az mindenképp lesz, inkább arról volt szó eddig, hogy kézzel lockoljunk, vagy konfigoljuk be úgy az adatbázist, hogy ő csinálja meg helyettünk a lockot egy bizonyos módon, stb... Az, hogy éppen milyen lockról van szó teljesen kérés függő.
23

lock

H.Z. · 2014. Ápr. 7. (H), 11.29
Osszegezve, lockolast szinte soha sehova, csak ha meg tudod indokolni (eddig nagyon ritkan lattam en magam olyat, ahova ez kellett. Olyat szoktam latni, ahol a hozza nem ertes miatt lockoltak, mert pl. a megfelelo eljarast nem ismertek)

Nekem ebből az jött le, hogy úgy általában elutasítja a lockolást, ezért kérdeztem.
Hogy lock parancsot nem használunk sűrűn, az egy másik téma.
25

Ja bocs, benéztem, azt hittem

inf · 2014. Ápr. 7. (H), 15.12
Ja bocs, benéztem, azt hittem nekem írtad a kérdést... :-)
27

Ha olyan a feladat, hogy van

BlaZe · 2014. Ápr. 7. (H), 20.40
Ha olyan a feladat, hogy van lehetőség async írásra, akkor az egy opció, hogy egy queueba rámoljuk a write requesteket, és majd megtörténik, amikor megtörténik. Ez párhuzamosítható a háttérben, és ha hirtelen kapunk egy nagy loadot, akkor nem fog letérdelni az egész cucc. Ezt még meg lehet spékelni azzal, hogy elosztani a párhuzamosított írásokat a writererek között: pl ha egy cikk updateről beszélünk, akkor aminek az id-ja 1-re végződik, az az 1-es writerre... a 0-s a 10-es writerre stb. Így párhuzamos is, lock sem kell, biztosított a sorrend is, tervezhető módon skálázódik stb. De ez nyilván sokkal bonyolultabb, mint egy lock, legtöbb helyen nincs olyan terhelés, hogy ilyen megoldásokra szükség legyen. Meg hát az igény a lockra önmagában is elég ritka. Csak mint opció, ha már szóba került, hogy vannak alternatívák.
30

Meg hát az igény a lockra

szjanihu · 2014. Ápr. 9. (Sze), 10.11
Meg hát az igény a lockra önmagában is elég ritka.

Ezzel vitatkoznék. Ha aggregált értéket tárolsz egy táblában, már szükség is van rá. Ha aszinkron dolgozod fel a requesteket, akkor is fennáll a probléma, mivel lazán lehet egy időben később elküldött requestben régebbi verzió (vagyis régebbi állapot alapján küldte a client a requestet)

Ezt a wiki oldalt most találtam és bár csak bele-bele olvasgattam, egész jónak tűnik.
35

Ha aggregált értéket tárolsz

inf · 2014. Ápr. 9. (Sze), 14.06
Ha aggregált értéket tárolsz egy táblában, már szükség is van rá. Ha aszinkron dolgozod fel a requesteket, akkor is fennáll a probléma, mivel lazán lehet egy időben később elküldött requestben régebbi verzió (vagyis régebbi állapot alapján küldte a client a requestet)

Persze, kb ugyanezt írják pgsql manualban is.

Majd még teszek fel blogmarkokat ezzel kapcsolatban, van 1-2 jó anyag, csak még át kell rágnom magam rajtuk...
43

Nem teljesen értem mire

BlaZe · 2014. Ápr. 9. (Sze), 21.49
Nem teljesen értem mire gondolsz a régebbi állapottal, weben jöhet bármi, ezt lockkal se véded ki. A lényeg, hogy serializálni kell bizonyos műveleteket, ennek a legegyszerűbb módja a lockolás, ami alatt most a web request mutex jellegű lockolását értem. De ez megvalósítható máshogy is, pl úgy is ahogy írtam. Nem csak async feldolgozást írtam, hanem queuet. A web request lockolásához képest ez nem váratja azt, cserébe viszont pollozni kell az eredményt, de a lockkal ellentétben ez legalább skálázható. A háttérben persze kellhet lockolni, de sok esetben (és itt pl ugyanazon entitás update-elésére gondolok) még az sem kell. Egy weboldalnál, ami párszáz requestet szolgál ki per nap ez nem feltétlenül szempont, de ahol megszórják requesttel, ott már nem mindegy, hogy lockoljuk-e a webet (vagy bármilyen poolt), vagy kontrolláltan serializáljuk bizonyos háttérfolyamatait.

A fentebb írt requestek particionálását egyébként a jelenlegi projectemen használjuk, és nagyban megkönnyíti a munkánkat. Tény, hogy nem db-vel kapcsolatban (hanem szálakhoz irányítjuk eventek feldolgozását) meg az is, hogy az egyes eventekhez kapcsolódó entitások semmilyen relációban nem állnak egymással, tehát egyszerűbb a terep, mint egy relációs adatbázisnál. De az elv működik.
52

Igazat adok neked, amennyiben

szjanihu · 2014. Ápr. 10. (Cs), 17.17
Igazat adok neked, amennyiben te magad serializálod a folyamatot, úgy valóban nem kell lock, mivel nincsenek párhuzamos folyamatok. Bár attól függ, mit nevezünk párhuzamos folyamatnak. Ha csak azt, ami a szerveren a queue feldolgozása körül történik, ott valóban nincsenek párhuzamos folyamatok. Viszont ha az oldal böngészőben történő betöltésénél indul a startpisztoly és odáig tart a folyamat, hogy feldolgozzuk a queue-ból kijövő write requestet, akkor már lehetnek.

1. A user read <- version 1
2. B user read <- version 1
3. B user write (version 1) -> version 2 on server
4. A user write (version 1) -> Ouch
Arra a problémára gondoltam, hogy a client olvasáskor megkapja az (entity, aggregate) aktuális verziót, amit íráskor el kell küldenie a szervernek. Ezáltal a szerver eldobhatja a kérést, amennyiben az adatot időközben más módosította. Ha az összes bejövő write requestet queue-ba rakod, nem tudod a clientnek odaadni az aktuális verziót. Queue-ba nem rakhatsz mindent. Aszinkron, queue-ba rakott feladatokkal eventually consistency érhető el, ami fontos és jó dolog, de nem mindig elégséges.

Egy optimistic lock version fielddel szerintem annyira kicsi overheadet jelent, hogy minden más esetben érdemes használni (no nem minden táblában, hanem aggregatekben gondolkodva csak az aggregate root táblájába, de ez már más téma).
31

Terhelés

Hidvégi Gábor · 2014. Ápr. 9. (Sze), 11.08
Mekkora a szerver terhelése, ahol problémát okoznak a zárolások?
33

A zárak problémáit nem

MadBence · 2014. Ápr. 9. (Sze), 13.40
A zárak problémáit nem feltétlenül a terhelés okozza, hanem a rossz zárhasználat (a threadben korábban már mutatott is valaki egy egyszerű példát a deadlockra)
32

ab

Hidvégi Gábor · 2014. Ápr. 9. (Sze), 11.12
Az Apache Benchmark pont ilyen tesztelésre való, paraméterben megadható neki, hogy hány párhuzamos kérést indítson.
36

A gond nem ezzel a részével

inf · 2014. Ápr. 9. (Sze), 14.12
A gond nem ezzel a részével van, lehet pl curl multi-val is meg exec-el is párhuzamos kéréseket indítani, pofon egyszerű...

A probléma ott van, hogy pl egy olyat hogyan csinálsz, hogy

parallel(
    function () use ($resource){
        //task1
    },
    function () use ($resource){
        //task2
    }
);
Miközben mindkettőtől kapsz eredményt, és rátolsz egy xdebug-ot, hogy tudjad pontosan mi akad össze.

pcntl-el lehetne, de az csak linux-nál működik...

minden másnál meg valahogy szerializálni kell az aktuális szituációt, és úgy eljuttatni a futtatókörnyezetbe...
37

A php-t nem igazán párhuzamos

Hidvégi Gábor · 2014. Ápr. 9. (Sze), 14.19
A php-t nem igazán párhuzamos feldolgozásra találták ki. Nem tudod máshogy megoldani a problémát?
39

Hát agyalok rajta...

inf · 2014. Ápr. 9. (Sze), 16.44
Hát agyalok rajta... Elméletileg nincs szükség ilyen task runner-re, csak valami hasonlóra, mint amit a forkolás csinál. Még megosztott erőforrásokra sincs szükség, sőt talán jobb is, ha külön adatbázis kapcsolatot, meg minden egyebet épít a két task magának... Annyi a lényeg, hogy a jelentést valahogy juttassák el a központi feldolgozóba...

Alapból ilyesmire gondoltam, mint kezdetleges concurrency test-re:

public function testMoneyIsNotLostByConcurrentTransfers(){
    $accountRepository = DataAccessLayer::getBankAccountRepository();
    $accountOfTom = $accountRepository->create(array(
        'owner' => 'Tom',
        'balance' => new Money(10000)
    ));
    $accountOfBob = $accountRepository->create(array(
        'owner' => 'Bob',
        'balance' => new Money(10000)
    ));
    $accountOfSusanne = $accountRepository->create(array(
        'owner' => 'Susanne',
        'balance' => new Money(10000)
    ));

    $this->concurrentExecution(
        function () use ($accountOfTom, $accountOfBob){
            $accountOfTom->transfer($accountOfBob, new Money(5000));
        },
        function() use ($accountOfTom, $accountOfSusanne){
            $accountOfSusanne->transfer($accountOfTom, new Money(5000));
        }
    );

    $this->assertEquals($accountOfTom->getBalanceAmount(), 10000);
    $this->assertEquals($accountOfBob->getBalanceAmount(), 15000);
    $this->assertEquals($accountOfSusanne->getBalanceAmount(), 5000);
}
Ezt át lehetne alakítani valahogy így:


/** @concurrency("task1", "task2") */

public function testMoneyIsNotLostByConcurrentTransfers($concurrency){
	
	$accountRepository = DataAccessLayer::getBankAccountRepository();
	
	if ($concurrency->isTestProcess()) {

		$accountOfTom = $accountRepository->create(array(
			'owner' => 'Tom',
			'balance' => new Money(10000)
		));
		$accountOfBob = $accountRepository->create(array(
			'owner' => 'Bob',
			'balance' => new Money(10000)
		));
		$accountOfSusanne = $accountRepository->create(array(
			'owner' => 'Susanne',
			'balance' => new Money(10000)
		));
		
		$this->waitForParallels($accountOfTom->getId(), $accountOfBob->getId(), $accountOfSusanne->getId());
		
		$this->assertEquals($accountOfTom->getBalanceAmount(), 10000);
		$this->assertEquals($accountOfBob->getBalanceAmount(), 15000);
		$this->assertEquals($accountOfSusanne->getBalanceAmount(), 5000);
	}
	else {
		
		$accountOfTom = $accountRepository->findById($concurrency->nextParam());
		$accountOfBob = $accountRepository->findById($concurrency->nextParam());
		$accountOfSusanne = $accountRepository->findById($concurrency->nextParam());
		
		if ($concurrency->isProcess('task1')){
			$accountOfTom->transfer($accountOfBob, new Money(5000));
		}
		else {
			$accountOfSusanne->transfer($accountOfTom, new Money(5000));
		}
	}

}
Ez nem túl szép, de működne, és tovább refaktorálható viszonylag szép kóddá.

class MoneyTransactionTest extends ParallelTestCase {
	/** @concurrency("task1", "task2") */

	public function testMoneyIsNotLostByConcurrentTransfers($concurrency){
		
		$accountRepository = DataAccessLayer::getBankAccountRepository();

		$accountOfTom = $accountRepository->create(array(
			'owner' => 'Tom',
			'balance' => new Money(10000)
		));
		$accountOfBob = $accountRepository->create(array(
			'owner' => 'Bob',
			'balance' => new Money(10000)
		));
		$accountOfSusanne = $accountRepository->create(array(
			'owner' => 'Susanne',
			'balance' => new Money(10000)
		));
		
		$this->process($accountOfTom, $accountOfBob, $accountOfSusanne);
		
		$this->assertEquals($accountOfTom->getBalanceAmount(), 10000);
		$this->assertEquals($accountOfBob->getBalanceAmount(), 15000);
		$this->assertEquals($accountOfSusanne->getBalanceAmount(), 5000);
		
	}
	
	/** @process("task1") */
	public function task1($accountOfTom, $accountOfBob, $accountOfSusanne){
		$accountOfTom->transfer($accountOfBob, new Money(5000));
	}
	
	/** @process("task2") */
	public function task2($accountOfTom, $accountOfBob, $accountOfSusanne){
		$accountOfSusanne->transfer($accountOfTom, new Money(5000));
	}
	
	/** @serializer */
	public function exportResources($accountOfTom, $accountOfBob, $accountOfSusanne){
		return array($accountOfTom->getId(), $accountOfBob->getId(), $accountOfSusanne->getId());
	}
	
	/** @unserializer */
	public function importResources($accountOfTomId, $accountOfBobId, $accountOfSusanneId){
		$accountRepository = DataAccessLayer::getBankAccountRepository();
		$accountOfTom = $accountRepository->findById($accountOfTomId);
		$accountOfBob = $accountRepository->findById($accountOfBobId);
		$accountOfSusanne = $accountRepository->findById($accountOfSusanneId);
		return array($accountOfTom, $accountOfBob, $accountOfSusanne);
	}
}
Olyat sajnos nem tudok csinálni, mint ami az elején volt, vagy hát fel kellene hozzá dolgozni az egész metódust, és az alapján fájlokba újraírni, hogy a különböző process-ekben mi fusson:

//test process

class MoneyTransactionTest extends ParallelTestCase {
	/** @concurrency("task1", "task2") */

	public function testMoneyIsNotLostByConcurrentTransfers($concurrency){
		
		$accountRepository = DataAccessLayer::getBankAccountRepository();

		$accountOfTom = $accountRepository->create(array(
			'owner' => 'Tom',
			'balance' => new Money(10000)
		));
		$accountOfBob = $accountRepository->create(array(
			'owner' => 'Bob',
			'balance' => new Money(10000)
		));
		$accountOfSusanne = $accountRepository->create(array(
			'owner' => 'Susanne',
			'balance' => new Money(10000)
		));
		
		$export = $this->exportResources($accountOfTom, $accountOfBob, $accountOfSusanne);
		$this->runProcess('task1', $export);
		$this->runProcess('task2', $export);
		
		//remélhetőleg szinkron várja meg a végüket, és nem kell callback hozzá
		
		$this->assertEquals($accountOfTom->getBalanceAmount(), 10000);
		$this->assertEquals($accountOfBob->getBalanceAmount(), 15000);
		$this->assertEquals($accountOfSusanne->getBalanceAmount(), 5000);
		
	}
		
	/** @serializer */
	protected function exportResources($accountOfTom, $accountOfBob, $accountOfSusanne){
		return array($accountOfTom->getId(), $accountOfBob->getId(), $accountOfSusanne->getId());
	}
	
}


//task1 process

class MoneyTransactionTest extends ParallelTestCase {

	public function run($task, array $import){
		//a call_user_func_array-t, képzeljétek oda
		$resources = $this->importResources($import);
		$this->$task($resources)
	}

	/** @process("task1") */
	protected function task1($accountOfTom, $accountOfBob, $accountOfSusanne){
		$accountOfTom->transfer($accountOfBob, new Money(5000));
	}

	/** @unserializer */
	protected function importResources($accountOfTomId, $accountOfBobId, $accountOfSusanneId){
		$accountRepository = DataAccessLayer::getBankAccountRepository();
		$accountOfTom = $accountRepository->findById($accountOfTomId);
		$accountOfBob = $accountRepository->findById($accountOfBobId);
		$accountOfSusanne = $accountRepository->findById($accountOfSusanneId);
		return array($accountOfTom, $accountOfBob, $accountOfSusanne);
	}
	
}

//task2 process

class MoneyTransactionTest extends ParallelTestCase {

	public function run($task, array $import){
		//a call_user_func_array-t, képzeljétek oda
		$resources = $this->importResources($import);
		$this->$task($resources)
	}

	/** @process("task2") */
	protected function task2($accountOfTom, $accountOfBob, $accountOfSusanne){
		$accountOfSusanne->transfer($accountOfTom, new Money(5000));
	}

	/** @unserializer */
	protected function importResources($accountOfTomId, $accountOfBobId, $accountOfSusanneId){
		$accountRepository = DataAccessLayer::getBankAccountRepository();
		$accountOfTom = $accountRepository->findById($accountOfTomId);
		$accountOfBob = $accountRepository->findById($accountOfBobId);
		$accountOfSusanne = $accountRepository->findById($accountOfSusanneId);
		return array($accountOfTom, $accountOfBob, $accountOfSusanne);
	}
	
}



Erre most nekem nincs kapacitásom. A token-get-all-al megoldható a parsolás, és az újra építés, és ha elég okosra megírod a parsert, akkor szinte bármilyen szintaxisa lehet a tesztednek...
40

Párhuzamosítás nélkül

inf · 2014. Ápr. 9. (Sze), 18.29
Párhuzamosítás nélkül biztosan nem megy. A multi process megoldások is nagyon gányak, azokkal biztosan nem csinálom. Ami van még az a pthreads. Ez xdebug-gal nem megy, viszont talán a backtrace-be bekerül a futási sorrend, és aki ért a thread-ekhez, annak nagyjából egyértelmű lesz a teszt kódja is. Ezzel fogok kísérletezni, ha meglehet csinálni vele, akkor király, egyébként meg az összes többi megoldást hagyom a rákosba...
41

A pthreads-szel tudsz

BlaZe · 2014. Ápr. 9. (Sze), 21.11
A pthreads-szel tudsz szinkronizálni a szálak között, és tudod azokat altatni, ébresztgetni. Ez működik (gondolom a php-s implementációjában is). Ami a kihívás, hogy hogy vezérled a tesztet. A korábbi példádnál maradva hogy A szál megvárja, míg B szál commitol, aztán felébredjen és folytassa a futását, és ellenőrizhesse, hogy B szál nem zavart-e bele a korábbi állapotába. Egy proxyval az interface mentén még csak-csak tudsz ilyet csinálni, de pl ha egy metóduson belül kéne így megállítani, az már nehezebb, phpban nem tudom van-e más megoldásod, mint az, hogy az ilyen pontokon hívj egy callbacket, amiben a tesztből tudsz waitelni. Összességében ez így menni fog, de nem egyszerűsíted vele az életed... Ha már tényleg ilyesmire van szükséged, megfontolandó, hogy ezt nem phpban kéne csinálni. Amellett, hogy ezzel igazából azt teszteled, hogy a másik módszer (pl db esetén az izolációs szint) valóban jól viselkedik-e.

Ha onnan közelíted a dolgot, hogy a php kódod a valóságban teljesen izoláltan fog futni, és a db-n keresztül lehet rá hatással egy másik php process, nézheted onnan is, hogy a kódod védett, ha ezt kizárod az adott request egészére, vagy egy adott blokkra. Lockolás, izoláció, vagy amire szükséged van. Ezesetben elég az, hogy azt vizsgálod tesztből, hogy a kódod meghívja/elengedi a várt pontokon a lockot, beállítja az isolation levelt stb, hisz azok megbízhatóak. Innen vizsgálva egyszerűbb a feladat. Biztos nem lenne elég ez a megközelítés? Persze ha neked kell megvalósítanod magát a szinkronizálást, akkor sok lehetőséged nincs a tesztelésére...
42

Én pont ezt szeretném

inf · 2014. Ápr. 9. (Sze), 21.34
Én pont ezt szeretném elkerülni, amit írsz, hogy alacsony absztrakciós szintű dolgokba bonyolódjak. Csak annyit szeretnék tesztelni, hogy ha mondjuk két átutalást csinál párhuzamosan ugyanarról a számláról a rendszer, akkor mindkettő sikeresen célba ér, és nem jön létre vagy veszik el eközben pénz. Szerintem itt egyedül ennek van értelme. Azt utána ki tudom nyomozni, hogy mi okozza a gondot, mondjuk az izolációs szint rossz beállítása, vagy bármi egyéb, ha éppen adatbázisról van szó...

A pthreads-en kívül még a proc_open, amit még visszaveszek lehetséges megoldások közé. Az tud külön fájlok nélkül php kódot futtatni, és lehet vele kommunikálni pipe-on keresztül. Ami hátránya, hogy úgy egyébként semmit nem tud a külvilágról. Ha a pthreads is ugyanilyen, tehát nem tudok változókat átadni neki szerializálás nélkül, akkor egyszerűbb a proc_open-el kezdeni. Ezt még ellenőrizni fogom.

A tesztelést úgy gondoltam, hogy csak megadom a 2 task-et, automatikusan többször lefuttatom rájuk a tesztet eltérő wait időközökkel, és ha valahol gond van, akkor annak a scenarionak az adatait, hívási sorrendjeit valahogy külön visszaadom. Ez mondjuk elég fapados, de ennél komolyabbat nem hiszem, hogy tudnék csinálni, ha nem kezdek beleírni alacsonyabb absztrakciós szintű kódokba...
44

Jó a pthreads nyert

inf · 2014. Ápr. 9. (Sze), 21.50
Jó a pthreads nyert php-and-multi-thread-on-windows itt egy az egyben vesz át paramétereket kívülről különösebb erőlködés nélkül... Lehet, hogy a resource meg az objektumok már kevésbé mennek, de ez már pont elég ahhoz, hogy inkább ennél maradjak, mint a process-eknél...

Olvastam még olyat, hogy 5.3.x-nél nem elég stabil, inkább 5.4 felett ajánlják. Hát megnézem... Most 5.3.28-ra kellene valami...

Közben megtaláltam, hogy körülbelül mi kell az aszinkron híváshoz: Async::call
47

Az aszinkron hívás nem elég.

BlaZe · 2014. Ápr. 9. (Sze), 22.48
Az aszinkron hívás nem elég. Ha tesztelni akarod a párhuzamosságot, akkor neked kell tesztből vezérelni, hogy mikor melyik szál fut, melyik vár melyikre stb - notify/wait/join, ilyesmik.

Valahogy így néz ki:
class ConcurrentObject implements Concurrent {
private Callback callback = new NoopCallback();

public ConcurrentObject(){}

ConcurrentObject(Callback callback){this.callback=callback;}

public void doSomething() {
  foo();
  callback.onAfterFoo();
  bar();
}
}
Teszt:
concurrentObject = new Concurrent(new Callback(){
  onAfterFoo() {
    thisThread.wait();
  }
}).doSomething();
masik szalban:
thisThread.notify();
A business logic szintjén nem kell lemenned alacsony szintre. Sőt, tesztből se, ha a szálkezelést elfeded:
class Barrier {
  private Thread thread;

  Barrier(Thread thread){this.thread=thread;}

  public void waitForEvent(){thread.wait();}

  public void eventHappened(){thread.notify();}
}

// test
barrier = new Barrier(concurrentObjectRunnerThread);
concurrentObject = new ConcurrentObject(new Callback(){
  onAfterFoo() {
    barrier.waitForEvent();
  }
}).doSomething();

// masik szal
[...]
barrier.eventHappened();
[...]
Szóval lehet ezt viszonylag szépen csinálni, de ez azért olyan a phpnek, mint amikor Őzike eltéved a sikátorban... :) A php-s pthreadset nem ismerem, de a lényeg remélem átjön.
49

Egyelőre valami ismeretlen

inf · 2014. Ápr. 10. (Cs), 00.43
Egyelőre valami ismeretlen okból nem sikerült feltenni pthreads-et, nyomozok utána... Addig erre nem tudok érdemben reagálni.
50

Na közben összejött,

inf · 2014. Ápr. 10. (Cs), 15.27
Na közben összejött, kiderült, hogy IIS7.5-el nem kompatibilis a pthreads, egyszerűen elszáll tőle 500-as hibaüzenettel, és azt hiszem tudom is, hogy miért. Valamiért 2 http response-t küld neki, nem csak egyet... Ami a thread-ben történik azt teljesen külön response-ban kapja. Ez mondjuk nem feltétlen kellene, hogy server error-t okozzon, de nem ismerem az IIS felépítését ennyire...

A php.exe-vel viszont tudok normálisan dolgozni, a probléma csak a php-cgi.exe-t érinti... Valószínűleg van megoldása, de most hanyagoljuk... Összességében hasonlóan a proc_open-es megoldáshoz nem tud valami sokat, ugyanúgy muszáj szerializálni mindent, amit át akarok küldeni, pl Closure-t egyáltalán nem tudok átvinni (egyelőre).

update:
Jah, bug volt, javította a srác elvileg...

Elvileg van mód dolgok megosztására szerializálás nélkül, a phpthreads objectek sosem kerülnek szerializálásra, tehát ha egy ilyenbe tesztek egy erőforrást, closure-t, vagy bármi mást, akkor azt át lehet adni a többi thread-nek anélkül, hogy szerializálással kellene szenvedni... Ezt még csekkolom...
51

public function

inf · 2014. Ápr. 10. (Cs), 17.09

public function testMoneyIsNotLostByConcurrentTransfers(){
    $accountRepository = DataAccessLayer::getBankAccountRepository();
    $accountOfTom = $accountRepository->create(array(
        'owner' => 'Tom',
        'balance' => new Money(10000)
    ));
    $accountOfBob = $accountRepository->create(array(
        'owner' => 'Bob',
        'balance' => new Money(10000)
    ));
    $accountOfSusanne = $accountRepository->create(array(
        'owner' => 'Susanne',
        'balance' => new Money(10000)
    ));

    $this->concurrentExecution(
        function () use ($accountOfTom, $accountOfBob){
            $accountOfTom->transfer($accountOfBob, new Money(5000));
        },
        function() use ($accountOfTom, $accountOfSusanne){
            $accountOfSusanne->transfer($accountOfTom, new Money(5000));
        }
    );

    $this->assertEquals($accountOfTom->getBalanceAmount(), 10000);
    $this->assertEquals($accountOfBob->getBalanceAmount(), 15000);
    $this->assertEquals($accountOfSusanne->getBalanceAmount(), 5000);
}
Én csak valami ilyesmit szeretnék. Azt, hogy a concurrentExecution egymáshoz képest milyen delay-el tolja el a konkurrens kéréseket nanosleep-el szeretném változtatni több egymás utáni futásnál. Így elvileg lehet tologatni egymáshoz képest a thread-ek futását, és el lehet érni, hogy bizonyos beállításoknál összeakadjanak a lockok. Szóval nem akarom kézzel szinkronizálni a teszteket, mert ahhoz szerintem szét kéne bontanom az repository-k kódját, és olyan event-eket betenni, amik jelzik, hogy mikor jön a másik szál. Persze lehet, hogy valamit félreértek, javíts ki, ha tévednék...

(A a concurrentExecution-t valószínűleg nem tudom megvalósítani a fenti formában, de azért megpróbálok valami hasonló, könnyen érthető interface-t csinálni a dolognak.)
53

Nem determinisztikus

BlaZe · 2014. Ápr. 10. (Cs), 21.18
Azt, hogy a concurrentExecution egymáshoz képest milyen delay-el tolja el a konkurrens kéréseket nanosleep-el szeretném változtatni több egymás utáni futásnál. Így elvileg lehet tologatni egymáshoz képest a thread-ek futását, és el lehet érni, hogy bizonyos beállításoknál összeakadjanak a lockok. Szóval nem akarom kézzel szinkronizálni a teszteket, mert ahhoz szerintem szét kéne bontanom az repository-k kódját, és olyan event-eket betenni, amik jelzik, hogy mikor jön a másik szál. Persze lehet, hogy valamit félreértek, javíts ki, ha tévednék...
Ezzel az a baj, hogy a teszt lefutásakor nem lehetsz benne biztos, hogy olyan csillagegyüttállás volt, amit szerettél volna. Vagyis hogy a teszted valóban sikeres. De ha pl a repositoryd egy sqldriveren keresztül intézi a db kezelését, akkor injektálhatsz neki egy proxyzott sqldrivert, és annak a megfelelő metódusaiban (pl acquireLock(), releaseLock()) megvalósíthatod a szinkronizációt. De az tény, hogy a tervezésnél erre is gondolnod kell, hogy mi mentén akarod a párhuzamos tesztelést megvalósítani. PHPban szerintem ennél nincs jobb lehetőséged, vagy legalábbis én nem tudok. Lehet pl weavinggel kicsit finomabban is csinálni ilyet olyan nyelveknél, amik támogatják, pl Javahoz van egy ThreadWeaver project.
54

Ezzel az a baj, hogy a teszt

inf · 2014. Ápr. 10. (Cs), 21.50
Ezzel az a baj, hogy a teszt lefutásakor nem lehetsz benne biztos, hogy olyan csillagegyüttállás volt, amit szerettél volna. Vagyis hogy a teszted valóban sikeres.


Igen, ez sajnos így van, de ezen kívül szerintem csak úgy lehetne megoldani, hogy manuálisan vagy parsolással a kódok megfelelő részébe eventeket csempészek. Hmm, azt hiszem, hogy csinálok egy keretrendszert resource-ok kezelésére, és ott a test scenario-ba bármit belerakhatok, ami csak tetszik. Így magát az alkalmazást nem kell tesztelni concurrency-re, csak a concurrency manager-t.

Valami ilyesmi felületre gondoltam (a példa gagyi):

try {
    $transactions = new MultiTransactionManager();

    $transactions->add(new FileSystemTransaction(
        function () use ($file1, $file2){
            $file1->delete();
            $file2->create();
        }
    ));

    $transactions->add(new DatabaseTransaction(
        function ($db) use ($file1, $file2){
            $stmt = $db->prepare('UPDATE files SET name = :new_name WHERE name = :old_name');
            $stmt->execute(array(
                'old_name' => $file1->name,
                'new_name' => $file2->name
            ));
        }
    ));
    $transactions->commit();
}
catch(Exception $e){
    $transactions->rollback();
    throw $e;
}
Itt a manager tudja ellenőrizni, hogy a fájlok abc sorrendben kerültek lockolásra. Azt is tudja ellenőrizni, hogy az adatbázis tranzakció fut le utoljára, és csak azután engedi fel a lockokat mondjuk a fájlrendszeres dolgoknál akár egy __destroy-al. Nem akarom túlbonyolítani a dolgot, pessimistic locking elég lesz, meg melléje vagy a memóriában vagy fájlrendszerben valami ideiglenes tároló a fájlok előző verzióinak, hogyha vissza kell csinálni a változtatásokat, akkor az menjen.

Imho ez egy pár napos projekt keretében megvalósítható.

Lehet pl weavinggel kicsit finomabban is csinálni ilyet olyan nyelveknél, amik támogatják, pl Javahoz van egy ThreadWeaver project.


Sajnos most nincs időm java-t nézegetni. :S Egyébként ja, alapból Thread-el rendelkező nyelveknél teljesen más a helyzet, ott megvannak a megfelelő eszközök bármi ilyesmire.


update:

Közben megtaláltam, hogy magát a problémát distributed transaction-nek nevezik. Nyilván szükség van egy filesystem transaction-re is, illetve szükség van valamilyen distributed transaction mintára, ami a két tranzakciót szinkronban tartja. Ez két külön rendszert igényel. A teszteléshez a pthreads teljesen jó mindkettőre... Egy thread-ben indítok egy tranzakciót, a másikban egy másikat, aztán összehangolom őket event-ekkel, és próbálgatom, hogy hol lehetnek hibák. Ezek alapján nincs is szükség külön teszt rendszerre, jó úgy, ahogy írtad, hogy eventekkel meg szál szinkronizációval...

Köszi a segítséget! Nélküled nem hiszem, hogy eljutottam volna idáig... :-)
55

Amit előbb írtam (proxy),

BlaZe · 2014. Ápr. 10. (Cs), 23.26
Amit előbb írtam (proxy), valami ilyesmire gondoltam, nem tudom az egyértelmű volt-e:

// tegyuk fel, hogy adott egy ilyen repositoryd egy SQLDriver fuggoseggel
class Repository {
  private SQLDriver driver;
  
  Repository(SQLDriver driver) {this.driver=driver;}
  
  public void doLogic() {
    driver.acquireLock();
    driver.statement("...");
    driver.releaseLock();
  }
}

// akkor

interface ProxyCallback {
  void beforeAcquireLock();
  void afterAcquireLock();
  void beforeReleaseLock();
  void afterReleaseLock();
}

class SQLDriverProxy implements SQLDriver {
  private SQLDriver driver;
  private ProxyCallback callback;
  
  SQLDriverProxy(SQLDriver driver, ProxyCallback callback) {
    this.driver=driver;
    this.callback = callback;
  }
  
  void acquireLock() {
    callback.beforeAcquireLock();
    driver.acquireLock();
    callback.afterAcquireLock();
  }
  
  void releaseLock() {
    callback.beforeReleaseLock();
    driver.releaseLock();
    callback.afterReleaseLock();
  }
}

ProxyCallback forThreadA = new ProxyCallback() {
  void beforeAcquireLock() {
    threadA.wait();
  }
}

ProxyCallback forThreadB = new ProxyCallback() {
  void afterReleaseLock() {
    threadA.notify();
  }
}

// test
threadA.run() {
  new Repository(new SQLDriverProxy(driver, forThreadA)).doLogic();
}

threadB.run() {
  new Repository(new SQLDriverProxy(driver, forThreadB)).doLogic();
}
Így A thread megvárja, amíg B szál lockol, és elengedi azt (hacsak el nem toltam :)). A repositoryd megmarad egyszerűnek, de tesztben rendesen körbe tudod proxyzni, és a driver hívások mentén tudsz szinkronizálni. Használsz rendesen patterneket, úgyhogy ezek mentén viszonylag szépen meg tudod ezt valósítani. (Íme egy valós példa miért nem butaság patterneket és laza csatolást használni :))

Közben megtaláltam, hogy magát a problémát distributed transaction-nek nevezik.
Erre írtam tegnap az XA-t. De most gyorsan rákeresve, PHP-ban ezt nem olyan trivális megoldani.

Köszi a segítséget! Nélküled nem hiszem, hogy eljutottam volna idáig... :-)
Örülök, ha tudtam segíteni benne :)
56

Yepp :-)Agyalok most a fájl

inf · 2014. Ápr. 10. (Cs), 23.34
Yepp :-)

Agyalok most a fájl - adatbázis témán, és gondban vagyok.

Egy hétköznapi példa, meg akarod változtatni a profile picture-t fácsén, maga a kép fájlrendszerben van tárolva a neve meg be van téve a user táblába.

$db->begin();
$uploadedPicture = $uploads[0];
$profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);
$fs->delete($profilePicture);
$newProfilePicture = $fs->unique('images/*.png');
$fs->move($uploadedPicture, $newProfilePicture);
$db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);
$db->commit();
Itt csak a db tranzakción belülre tudnám elhelyezni a fájlrendszeres tranzakciót... :S Hogyan lehet az ilyen szituációiknál automatikusan megoldani, hogy legyen fájlrendszeres tranzakció is, és visszavonja a fájl törlést és áthelyezést, ha az adatbázissal gondok vannak?

A multithread vagy valami hasonló megoldaná ezt a szétválasztási problémát:

$fs = new FileSystemTransaction(function ($db){
	$profilePicture = $fs->wait();
	$uploadedPicture = $uploads[0];
	$fs->delete($profilePicture);
	$newProfilePicture = $fs->unique('images/*.png');
	$fs->move($uploadedPicture, $newProfilePicture);
	$db->notify($newProfilePicture);
});

$db = new DatabaseTransaction(function ($fs){
	$profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);
	$fs->notify($profilePicture);
	$newProfilePicture = $db->wait();
	$db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);
});

$dis = new DistributedTransaction($fs, $db);
$dis->commit();
Ezen kívül talán callback-ekkel meg lehet csinálni, passz :S

Tök érdekes mindenesetre, hogy kapásból előkerültek a thread-ek, mint megoldás. Ahogy nézem coroutine-al szintén megoldható a dolog, felesleges thread-et alkalmazni hozzá...

A váltogatás egyik task-ről a másikra, meg egymás bevárása elvileg megoldható simán coroutine-al. Ha lemegyünk egy-egy kérés szintjére, akkor nincs is szükség thread-ekre, hogy concurrency-t szimuláljunk, bőven elég a multitask...

A probléma max az lehet, hogy mennyire kemény dió a doctrine driver-ébe beleírni... Esetleg lehetne inkább csak magába a PDO driverébe, amit használ, ott simán lehet loggolni 1-1 kérést, én már csináltam ilyet. Ja, gondolom te a PDO-ra gondoltál driver néven...

Alakul a molekula... :-)

Ja, szóval az elejétől kezdve azt javasoltad, hogy cooperative multitasking-et toljak, és tegyek breakpoint-okat a statement-ek közé, amiknél át tud váltani a másik task-re a php. Jóvan, lassan esett le, de ja, ez tényleg működőképes megoldás. Ha mellé még csinálnok egy distributed transaction implementációt valami egyszerű módon, akkor sínen van a dolog. Le lehet választani egy külön rétegbe a problémát, ahol jól tesztelve meg is lehet oldani anélkül, hogy magasabb absztrakciós szinten hozzá kellene nyúlnom bármihez is, vagy tesztelnem kellene concurrency-re.
57

Létre is hoztam egy repo-t a

inf · 2014. Ápr. 11. (P), 01.00
Létre is hoztam egy repo-t a projekthez, aki akar beszállhat. :-)
58

Az a baj a fenti cooperative

inf · 2014. Ápr. 12. (Szo), 06.00
Az a baj a fenti cooperative multitasking-es példával, hogy az egymást beváró task-ek szinkron kódnál csak generátorokkal vagy promise-al működnének. Kénytelen leszek promise-okat használni, hogy meg legyen a php 5.3-as támogatás is. A generátorok csak php 5.4-től vannak sajnos, amin meg most dolgozom az egy 5.3.28-as projekt.

Valahogy így nézne ki callback hell-el:
    $fs = new FileSystemTransaction(function ($dis) use ($fs, $uploads){  
        $uploadedPicture = $uploads[0];  
        $dis->get('profilePicture', function ($profilePicture) use ($fs, $uploadedPicture){
            $fs->delete($profilePicture);  
            $newProfilePicture = $fs->unique('images/*.png');  
            $fs->move($uploadedPicture, $newProfilePicture);  
            $dis->set('newProfilePicture', $newProfilePicture);  
        });  
    });  
      
    $db = new DatabaseTransaction(function ($dis) use ($db, $id){  
        $profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);  
        $dis->set('profilePicture', $profilePicture);  
        $dis->get('newProfilePicture', function ($newProfilePicture){
            $db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);  
        });
    });  
      
    $dis = new DistributedTransaction($fs, $db);  
    $dis->commit();  
így egy kis tákolással

	$dis = new DistributedTransaction();

	$dis->add(new FileSystemTransaction(function ($fs) use ($uploads){
		$fs	
			->then(function ($profilePicture) use ($fs, $uploads){
				$uploadedPicture = $uploads[0];
				$fs->delete($profilePicture);    
				$newProfilePicture = $fs->unique('images/*.png');    
				$fs->move($uploadedPicture, $newProfilePicture);    
				$fs->yield(array(
					'newProfilePicture' => $newProfilePicture
				));
			})
	}));
	
	$dis->add(new DatabaseTransaction(function ($db) use ($id){
		$db
			->then(function () use ($db, $id){
				$profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);    
				$db->yield(array(
					'profilePicture' => $profilePicture
				));
			})
			->then(function ($newProfilePicture) use ($db){ 
				$db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);    
			})
	}));
	
    $dis->commit();    
ami igazán érdekes, hogy talán token parserrel automatikusan generáltatni lehet ezeket valami ilyesmi formából is akár

	$dis = new DistributedTransaction();

	$dis->add(new FileSystemTransaction(function ($fs, $profilePicture) use ($uploads){
		$uploadedPicture = $uploads[0];
		$fs->delete($profilePicture);    
		$newProfilePicture = $fs->unique('images/*.png');    
		$fs->move($uploadedPicture, $newProfilePicture);    
	}));
	
	$dis->add(new DatabaseTransaction(function ($db, $newProfilePicture) use ($id){
		$profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);    
		$db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);    
	}));
	
    $dis->commit();    
Nem tudom mennyire lehet tokenek alapján closure-t építeni, és hogy milyen hatásfokkal lehet átadni neki a paramétereket, kísérletezni fogok vele...
update: megnéztem a lehetőségeket, nem látom értelmét energiát belefektetni, ugyanis a generátorok php 5.5-től teljesen ugyanezt tudják. Annyi a különbség, hogy a töréspontokon nem kell új closure-t létrehozni, hanem elég egy yield-et kitenni, hogy tudja a rendszer, hogy ott most szünetet tart, és később majd onnan folytatja a függvény futását.

	$dis = new DistributedTransaction();

	$dis->add(new FileSystemTransaction(function ($fs) use ($uploads){
		$profilePicture = yield();
		$fs->delete($profilePicture);    
		$newProfilePicture = $fs->unique('images/*.png');   
		$uploadedPicture = $uploads[0];		
		$fs->move($uploadedPicture, $newProfilePicture);  
		yield($newProfilePicture);
	}));
	
	$dis->add(new DatabaseTransaction(function ($db) use ($id){
		$profilePicture = $db->q('SELECT user.profilePicture FROM user WHERE user.id = ? FOR UPDATE', $id);  
		$newProfilePicture = yield($profilePicture);
		$db->q('UPDATE user SET user.profilePicture = ?', $newProfilePicture);    
	}));
	
	$dis->commit();    
45

Csak annyit szeretnék

BlaZe · 2014. Ápr. 9. (Sze), 22.19
Csak annyit szeretnék tesztelni, hogy ha mondjuk két átutalást csinál párhuzamosan ugyanarról a számláról a rendszer, akkor mindkettő sikeresen célba ér, és nem jön létre vagy veszik el eközben pénz. Szerintem itt egyedül ennek van értelme.
Ez pont az, amit írtam a request particionálásos példámnál. Ha ez a célod, én sokkal szívesebben ajánlanám a queue+háttérfolyamatot, minthogy belecsavarodj akár a pthread phpba hackelésébe, vagy process indítgatásba. A számla párhuzamos hozzáférését úgy ki tudod védeni.

Másik szálakban látom nézegeted az MQ-kat, ha jól láttam az ActiveMQ még nem volt. Én ezt konkrétan még nem próbáltam, csak más JMS implementációkat, de ennek van php kliense is, azért említem. Tranzakcionális, sőt tud XA-t is, bár azt nem tudom, hogy a php ehhez nyújt-e supportot.

Ha ilyen feature-ökre van szükséged, és ha ebben a domainben mozogsz - nem csak a példád jön innen - én elgondolkodnék, hogy másra nyergeljek át php helyett. És amúgy is, amik lejönnek a postjaidból hogy mik után olvasgatsz, rendesen feszítik itt-ott a php kereteit :) off-peroff :)
46

A php most ehhez a projekthez

inf · 2014. Ápr. 9. (Sze), 22.31
A php most ehhez a projekthez kell, ez a konkurrencia tesztelés csak úgy beugrott menet közben érdekességként, de nem feltétlen csinálom meg hozzá. Egyébként ha kifejezetten aszinkron jellegű lenne a projekt, akkor nodejs-ben állnék neki... Nekem egyedül a {} helyett \t, ami akadály nyelvek területén. ;-)

Másik szálakban látom nézegeted az MQ-kat, ha jól láttam az ActiveMQ még nem volt. Én ezt konkrétan még nem próbáltam, csak más JMS implementációkat, de ennek van php kliense is, azért említem. Tranzakcionális, sőt tud XA-t is, bár azt nem tudom, hogy a php ehhez nyújt-e supportot.


Köszi, megnézem azt is. MQ használata emiatt a probléma miatt szerintem erősen ágyúval verébre jellegű. Gondoltam már rá, én is emiatt néztem meg őket, mert valahol írták, de ide felesleges. Máshova hasznát fogom venni...

A pthreads nekem egyáltalán nem tűnik bonyolultnak, csak sima start meg join van benne, ami azért nem egy atomreaktor... A szinkronizálás részén el fogok még gondolkodni.
48

Köszi, megnézem azt is. MQ

BlaZe · 2014. Ápr. 9. (Sze), 22.59
Köszi, megnézem azt is. MQ használata emiatt a probléma miatt szerintem erősen ágyúval verébre jellegű. Gondoltam már rá, én is emiatt néztem meg őket, mert valahol írták, de ide felesleges.
Nem feltétlenül kell hozzá MQ. Még jórég phpval csináltunk egy oldalt, ahol elég összetett keresés volt, és le lehetett vele térdepeltetni rendesen a gépet. A requesteket betettük egy táblába, amiket egy daemon jellegű php dolgozott fel és tette át egy másikba a válaszokat. Jóval egyszerűbb, de ugyanezt tudja. Kevésbé ágyúval verébre, mint a párhuzamos tesztek írása phphoz :)