Optimistic Lock

Fórumok

Lehet, hogy nem jól értek valamit.

Van 2 alkalmazás szerver. Nincs az Entity cache szinkronban.

Amikor kiíratom az SQL-t amit optimistic lock esetén végrehajt, egy ilyesmit látok: update version=?version+1 where version=?version and id=?id; Ez alapján úgy gondolnám, hogy nem lehet 2 írás ugyanazon a rekordon. Vagy mégis? Úgy tűnik mintha ennek ellenére 2x íródna nagyon ritkán ugyanaz a rekord, nem dob Exception-t.

Hozzászólások

Az optimistic lock lényege az, hogy nem az adatbázis dolga az, hogy ne legyen két írás, ami egy sort érint, hanem azt megoldja a környezete, nincs tranzakció az adatbázisban... forráskódrészleted van amúgy?

nem, az optimistic lock egy elv, ahol a felteves az, hogy nem fog utkozni az iras, es ezert erre az esetre optimalizalunk. vagyis az utkozes lesz draga, nem a 'nem-utkozes'.

 

sql eseten a transaction isolation-t kellene megnezni, ha ilyen gebasz van. pl. serializable eseten frankon pessimistic locking-al fogja sorositani az optimistic locking-ot megvalosito rendszert, kinyirva a teljesitmeny-nyereseget.

alapvetoen optimistic locking-hoz a document store idealis, nem a sql.

nem, az optimistic lock egy elv, ahol a felteves az, hogy nem fog utkozni az iras, es ezert erre az esetre optimalizalunk. vagyis az utkozes lesz draga, nem a 'nem-utkozes'.

Ezt írtam én is, nem az adatbázis dolga, hanem a környezete oldja meg, hogy általánosságban ne legyen két írás egyidőben ugyanarra a rekordra, ha pedig van, akkor egy atomikusan kezelt - általában version nevű - mező várt vagy nem várt értékéből lehet kideríteni, hogy más is írt-e közben.

sql eseten a transaction isolation-t kellene megnezni, ha ilyen gebasz van. pl. serializable eseten frankon pessimistic locking-al fogja sorositani az optimistic locking-ot megvalosito rendszert, kinyirva a teljesitmeny-nyereseget.

Ennek semmi értelme.

alapvetoen optimistic locking-hoz a document store idealis, nem a sql.

Ez meg faszság.

tovabbra sem erveltel, csak ismetled, amit eddig irtal. Miert lenne sql idealis optimistic locking-hoz? a tobbtucat tablaba szejjelszort adathalmazzal, vs. 1 document? kb. nincs olyan eset, amikor a sql jobb megoldas lenne, csak tipikusan legtobb ember hideg medencebe bemaszas feeling-et kap, amikor nosql-el kell dolgoznia 20 ev sql utan.

en ugrottam, es nem bantam meg. azota utalom a sql-t, csak sajnos gyakran kenytelen vagyok hasznalni, mert a team is fontos.

Miert lenne sql idealis optimistic locking-hoz?

Mert az optimistic lock egy technológia agnosztikus dolog.

Miert lenne sql idealis optimistic locking-hoz? a tobbtucat tablaba szejjelszort adathalmazzal, vs. 1 document?

Ez most ismét egy MongoDB vs fasz-se-tudja összehasonlítás? Kb. mindegyik RDBMS tud flexible document alapú sémát.

kb. nincs olyan eset, amikor a sql jobb megoldas lenne, csak tipikusan legtobb ember hideg medencebe bemaszas feeling-et kap, amikor nosql-el kell dolgoznia 20 ev sql utan.

Na, így se hülyézték még le például a Spotify mérnökeit, pedig eléggé sok mindenre használnak PostgreSQL adatbázist. Meg még egy csomó egyéb cég is, akik nem ragadtak le ott, hogy a MongoDB mindenre megoldás.

en ugrottam, es nem bantam meg. azota utalom a sql-t, csak sajnos gyakran kenytelen vagyok hasznalni, mert a team is fontos.

Szerintem neked az a fő bajod, hogy nem ismered a lehetőségeket, illetve soha nem vetted a fáradságot, hogy rendesen megtanulj valamit.

Ami még fontos, hogy Oracle adatbázis és amikor ütközés van akkor a BatchUpdate-ra panaszkodik, hogy 1-et kellett volna visszaadnia a update-nek, de 0-át adott vissza.

Elég bonyolult a forráskód. Nem is mondanám tiszta kódnak. Inkább leírom a működést.

Van egy időzítő, ami időnként ráfut. Ez mivel nincs nagyon szinkronizálva (nem quartz-ot használ), így ráfuthat ugyanaz a gép is mégegyszer a feladatra, ha az előző nem fejezeződött be időre és a másik gép is.

Egy rest híváson keresztül kiveszi az aktuális max. sorszámot egy másik rendszerből, majd elkezdi feldolgozni x darabonként ezt. Külön tranzakciót nyit TransactionAttribute(REQUIRES_NEW)-al, lekéri az utoljára feldolgozott csomagszámot egy táblából (ezen van az optimistic lock). Elkezdi berakni egy táblába ezeket a csomagokat, hogy majd lekéri. Majd a végén beállítja az utoljára feldolgozott csomagszámot az entitáson és flush-t nyom rá. Ilyenkor ha optimistic lock van sokszor dobódik is. Majd visszatér a hívó metódushoz, ami az előző új tranzakción kívül van a berakott rekordok azonosítójival és bedobálja egy queba, későbbi feldolgozásra.

Itt időnként keletkeznek duplikált rekordok, ugyanazzal a csomagszámmal (ahova a csomagszámokat rekordonként berakta). Ha az optimistic lock miatt elbukna a program, akkor visszagörgetné a beíllesztett rekordokat is, ám úgy tűnik ritkán, de nem teszi.
A program úgy látom nem arra lett készítve, hogy több alkalmazásszerveren dolgozzon és nem ekkora mennyiségű adat feldolgozására. Most csak a mókolás a megengedett. Nem szabad túl sok fejlesztést beleölni.

A quartz egy masszivan tulbonyolitott rendszer ahhoz kepest, amire tipikusan hasznaljak - tisztelet a kivetelnek, de semmit se lattam, amit a spring @schedule es a schedlock ne oldott volna meg tizedannyi komplexitasbol.

Abbol, amit irtal, azt tudom javasolni, vizsgald meg kozelebbrol, hogy mukodik az optimistic lock exception-t kezelo kod. hasonlot ugyanis lattam mar, ott az volt a gebasz, hogy a DBMS nem mindig ugyanazt a hibat adta, es a java + spring addig transformalgatta az exception-oket, hogy nem OptimisticLockingException lett a vegen, hanem valami egyeb.

A transaction isolation-t erdemes meg megnezni - esetfuggo, de a REPEATABLE_READ ugye egy tranzakcion belul ugyanazt adja vissza akkor is, ha a DB-ben mar megvaltozott a version... aztan meg commit-nal dob egy hatast. READ_COMMITTED a celszeru.

 

A program úgy látom nem arra lett készítve, hogy több alkalmazásszerveren dolgozzon és nem ekkora mennyiségű adat feldolgozására. Most csak a mókolás a megengedett. Nem szabad túl sok fejlesztést beleölni.

Nem bantani akarlak, de ennek alapjan en nem toltenek tobb idot azon a helyen a feltetlen szuksegesnel. Celszeru korbenezni, ennel csak jobbat fogsz talalni.

A transaction isolation-t erdemes meg megnezni - esetfuggo, de a REPEATABLE_READ ugye egy tranzakcion belul ugyanazt adja vissza akkor is, ha a DB-ben mar megvaltozott a version... aztan meg commit-nal dob egy hatast. READ_COMMITTED a celszeru.

Ahogy már írtam, az optimistic lock lényege az, hogy _nincs_ adatbázis oldali tranzakció, tehát izoláció sincs. Semmi értelme optimistic lock használatának akkor, ha amúgy is van tranzakció és izoláció az adatbázis műveletre.

Pont azt magyarázza, hogy amikor optimistic lock-ról beszélünk, akkor kikerüljük az adatbáziskezelő által biztosított locking mechanizmust, és egy "parasztlock"-ot csinálunk, amely

el fog szállni, ha közben valaki más is módosította azt a táblát, ahhoz képest, amit mi korábban láttunk. Ezután a folyamatnak kell biztosítania, hogy ezzel a művelettel valami történjen,

a legegyszerűbb az, hogy értesítjük a felhasználót, hogy bocsi, valaki már módosította a rekordot menet közben, küldjed be újra.

tovabbra se magyaraztad meg, hogy ha 50 tablas az adatmodelled (mert sql ekkora fos), akkor a 20. tabla utan fellepo hibat hogyan gorgeted vissza *massziv* extra kezi admin + ezzel jaro komplexitas nelkul.

Egy darab rollback használatával. Semmi komplexitás nincs benne.

hint: sehogy

:D

dehogynem! hiba eseten hogyan rollback-elsz?

SAGA pattern használatával, ez az ún. compensating transaction.

mikor az adatmodell 50 tabla, es a 20. tabla update utan van utkozes, vagy barmi huba, azt hogy oldod fel?

Ha nem tudok SAGA pattern alapján visszaállni, akkor nem használok optimistic lock-ot, mert semmi értelme ilyen esetben. Ha 20 táblát kell egyszerre módosítani, egy tranzakcióban, ráadásul READ_COMMITTED izolációval, egyben visszavonva a 20 tábla módosításait, akkor ott teljesen értelmetlen bármilyen optimistic lock használata, mert az adatbázis motor menedzseli a teljes tranzakciót, dugig tele van a tranzakció írásra és olvasásra zárolt sorokkal és táblákkal.

Ezzel akkora extra komplexitas krealsz mint kodban, mint DB-ben, mint design-ban, ami tobb tizezer LoC-ra fog rugni. Vs. egy egyszeru document store kb. 5 sorban megold, mert:

- a schema a kodod modellje
- a de/ser -t ingyen kapod
- a document 1 object
- document optimistic lock-ot ingyen kapod
- az 1 document operation ACID

Na, ismét egy MongoDB vs fasz-se-tudja összehasonlítás. Amúgy ezeket tudja egy tetszőleges mai RDBMS is...

- az 1 document operation ACID

Ez oximoron, az ACID több document-re értendő, a MongoDB ACID 4.0 óta van, amióta össze lehet fogni egy tranzakcióba több document-et, illetve 4.2 óta van shared ACID, amikor nem kell egy shard-ban lennie mindannak, amit egy tranzakcióban akarok kezelni.

Neked alapvető tudáshiányod van adatbázis terminológiában (is).

- a schema a kodod modellje

Tokre nem minden esetben, noSQL-je valogatja sot akar noSQL serveren beluli indexenkent lehet kulonbozo

- a de/ser -t ingyen kapod

Mar amikor :D

- document optimistic lock-ot ingyen kapod

Na persze, biztos ha nagyon optimistak vagyunk :D Oszt a distributed systemekkel mi van, ahol mondjuk egy kuldo akar 4-5 fogadora is kuldi az adatot amit mindegyik le is rak a backend storage-ba (aminek majd lesz egy deduplikacios processze ami neha lefut)

- az 1 document operation ACID

Van olyan is, de amugy egy lofaszt altalanosithatunk :D

Nagyjából stimmt, de itt vannak pontatlanságok.

Az "Optimistic Lock", vagy a nekem jobban tetsző nevén az "Optimistic Concurrency Control" egy párhuzamossági modell, amiben "lock" nem nagyon van a hagyományos értelemben.
Hogy ez hol valósul meg, szinte lényegtelen az elv szempontjából. Lehet az adatbázis dolga, lehet egy java framework dolga, vagy lekódolhatjuk mi is, sokféleképpen realizálható.

Megvalósulhat Postgres vagy Oracle alatt két "Repeatable read" tranzakció között, ami ugyanazon sort próbálja modositani, amit egyszer kiolvasott a "SELECT" paranccsal ami transparensen rögzíti a sor verzióját, majd az UPDATE segítségével később megpróbálja átirni -  de mivel az izolációs szinte az elöbb emlitett "repeatable read" igy az egyik tranzakciót  nem tudjuk committal lezárni (ERROR: could not serialize access due to concurrent update). MySQL alatti a Repeatable read más, ott pesszimista lock van pl.

Ugyanez megvalósulhat pl a Hibernate Version annotációval (adatbáyisban READ COMMITTED izolacios szint), ahol ugyanez az elv, csak egy explicit "version" oszlop van rá. A hibernate fogja ezt a verziot nyilvantartani, hogy mi volt az, amit kiolvasott (666), es ott a tranzakcio rollbackelve lesz, ha az "UPDATE xyz SET version = 667 WHERE version = 666" parancs 0 frissített sort ad vissza, (hiszen egy másik tranzakcio már frissítette ezt a sort). Ez az eset a téma indito problémája.

Illetve ugyanez megvalósulhat noSQL környezetben (polygot SQL + noSQL, stb... stb...) lekódolva, SAGA pattern, meg tudomisén mivel.

A pesszimista lock használata esetén nem kell felkészülni (nagyon) az ütközés esetére, hiszen ezek a tranzakciók majd sorban egymás után fognak lebonyolódni. (feltéve persze ha nem lesz deadlock - minden tranzakció ugyanabban a sorrendben foglalják le az erőforrást és oldják fel, stb stb stb)

Az optimista lock esetén pedig fel kell (kéne) készülni az ütközés lehetőségére, hiszen itt optimistán nincs lock, csak egy hibajelzés ha van ütközés. Megvalósitható mondjuk pl egy retry mechanismussal, ami megkísérli az egész db update folyamatot újra legelőről.

Szerkesztve: 2023. 03. 11., szo – 23:11

Optimistic locknál azt kellene látnod, hogy az update-nél a verzió is benne van (ahol a többi mező update is).

Ez neked külön update-ben van? update version=?version+1 where version=?version and id=?id vagy csak lehagytad a többi mezőt?

Ha ugyanabban az update-ben van, akkor csak az egyik alkalmazás szerver fog tudni update-elni.

Esetleg még az is egy kérdés lehet, hogy milyen tranzakció izolációs szint van beállítva.

Ez neked külön update-ben van? update version=?version+1 where version=?version and id=?id vagy csak lehagytad a többi mezőt? Ha ugyanabban az update-ben van, akkor csak az egyik alkalmazás szerver fog tudni update-elni.

Nekem amúgy az a gyanús, hogy olyan helyen és módon van használva az optimistic lock, ahol annak semmi értelmes szerepe nincs és így nyilván nem működik.

--

Tipikus (mondhatni tankönyvi) optimistic lock use-case az, hogy van egy balance(id, value, version) táblám. Meg kell nézzem, hogy van-e 100 pénze az illetőnek, ha van, le kell vonnom 100 pénzt az egyenlegből és vissza kell írnom, mindezt tranzakció nélkül.

Ilyenkor az történik, hogy van egy (123456789, 120, 0) értékem, lefut egy `SELECT * FROM balance WHERE id=123456789;`, ebből megkapom, hogy van 120 pénz és 0 a version, tehát beírhatom, hogy az új egyenleg 20 pénz. Lefuttatok egy `UPDATE balance SET value=20, version=1 WHERE id=123456789 AND version=0;` utasítást, ami csak akkor fog lefutni, ha a version értéke 0. Ha közben egy másik szálon, másik szerveren, másik programból ugyanerre a sorra valaki más levont pénzt (mondjuk 50 pénzt), akkor ez az UPDATE nem fog változtatást okozni, mert a version értéke már 1. A programom észre kell vegye, hogy nem futott le üzemszerűen az UPDATE, szóval újra kell futnia, ekkor már azt kapja, hogy (123456789, 70, 1) az értéke a sornak és nincs már 100 pénz az egyenlegen. Az adatbázisból tipikusan nem jön vissza exception, a futó programnak kell tudnia, hogy mikor kell megismételni a futást.

Lehet én írtam félreérthetően, de pont a példádban szereplő hasonló eset forog fent itt is. Addig nem rakhatok el egy csomagot, amíg nem tudom, hogy el van-e már rakva az a csomag. Ez pedig abból derül ki, hogy melyik csomagnál tartott az előző futás.

Ebből a szempontból itt felesleges is lenne a párhuzamosan fusson több gépen. De mivel több időzítő is van több feladatra, így egy gép nem bírná el. Gondolom ezért raktak be több gépet és ezért is próbálkoztak optimistic lock-al. Ami megint csak nem pont a legjobb, mert nagy terhelésnél akár 3-4 szál is futhat egyszerre amelyik ezzel foglalkozik, majd a végén csak egy hajtódik végre, majd a többit eldobja. De mint látjuk, idpnként nem dobja el sem és emiatt duplikált csomagok keletkeznek.

Tehát az lenne az ideális eset ha pl. lenne egy DB-ben állapotot kezelő Quartz, ami nem engedné ráfuttatni ugyanazt az időzítőt még egyszer amíg a ugyanabból az időzítőből valamelyik fut. Viszont ekkora átírást most nem tudnak bevállalni.

Egyébként tesztekkel nem sikerült még előidéznünk, hogy nálunk duplikálódjón egy csomag. Mindig Optimistic lock keletkezett.

Szerintem itt az alapvető probléma az, hogy több alkalmazásszerveren fut a motyó és nincs szinkronban az entity cache... de ez csak tipp.

Hozzátenném, hogy nem értem az optimistic lock hasznosságát ebben az esetben, lehetne sima sor szintű lock, a meglévő tranzakció részeként, adatbázis által kezelve és minden problémád megoldódna. Mi volt az ötlet alapja, hogy erre az optimistic lock a legjobb megoldás?

Azt, hogy mi volt az ötlet alapja - nem tudom. Nem én írtam a programot. :)

A pesszimista zároláson már én is gondolkodtam. Csak ott nem tudom milyen egyéb gondok jöhetnek még elő a mostani kódon. Ezeket még át kell gondolnom. Nem akarok egy hiba cunnamit elindítani. 

Ezért szokták inkább inserttel csinálni az update-et úgy, hogy a version unique. Akkor már lesz version=1 rekord és az insert elfekszik a második esetben.

JPA tipikusan UPDATE műveletet hajt végre, abból is kiderül egyértelműen, hogy változott-e a version.

Nem, nincs rajta semmilyen lock. Ha egy másik folyamat közben lefuttatott egy UPDATE hívást, akkor az enyém nem fog lefutni, mert nincs már meg a WHERE feltételben az a version érték, amit figyelek.

Tehát van egy version=0 értékem, akkor beolvas két process egy azonos sort, majd lefuttatná ezt a két UPDATE-et:

UPDATE balance SET value=20, version=1 WHERE id=123456789 AND version=0;

UPDATE balance SET value=80, version=1 WHERE id=123456789 AND version=0;

Amelyik előbb fut, az atomikusan átírja a version értékét és a második UPDATE nem fog módosítani, mert nincs már version=0, mindegy, hogy melyik fut le előbb. Az adatbázis saját működésén kívül nincs lock a rendszerben, nem kell a két process számára mutex-et kezelni az adatbázisban, hogy ki más olvas és módosít időben átlapolt tranzakcióban azonos sorokat.

Az optimistic lock nem egy silver bullet, ahogy a normál tranzakció se az ördögtől való.

Nézzük meg!

https://github.com/hibernate/hibernate-orm/search?q=OptimisticEntityLoc…

A Hibernate így csinálja és itt úgy látom csak a version mezőjét nézi:

https://github.com/hibernate/hibernate-orm/blob/7d30b57f15617f679a20aa1…

Nekem ez gyanús, mert nem foglalkozik vele, hogy sikerült-e updatelni sort vagy sem. Az igazán korrekt az lenne ha update után nyom egy selectet és összehasonlítja a rekordot a db-ben azzal amivel be akarta updatelni.

 

Node1 | Node2

Av0 | Av0

Bv0 | Cv0 <- erre változtatunk

------------- verzió check ----------

getEntry().version := 0 | getEntry().version := 0

minden ok | minden ok 

---------------- commit ------------------

update v=1 where v=0 OK |

--- szerk: ezek biztosan egymás után történnek mivel az update atomi ----

                                               update v=1 where v=0 SEMMI <- ezt le kéne kezelni de ilyen nincs a kódban, legalábbis onnan nem dob OptimisticLockingException-t

Bv1

--------------------------

vagy itt kéne megnéznie, hogy Bv1 ?= Cv0 és akkor kiderülne, hogy 

Nyilván, de a végén akkor is OptimisticLockingException-nek kéne belőle lenni ha egy optimistic lock sértés, de a github source alapján nincs olyan hely ahol ilyet csinál.

Hm... tehát sikerült a Hibernate komplex és összetett forrásából pár perc alatt levezetned azt, hogy nincs olyan hely, ahol ez kijön, míg OP szerint:

Egyébként tesztekkel nem sikerült még előidéznünk, hogy nálunk duplikálódjón egy csomag. Mindig Optimistic lock keletkezett.

Szerintem kicsit több időt kellene azért eltölteni a Hibernate forrását elemezve, hogy ilyen kijelentéseket tegyél...

Sajna véges időm van Hibernate forrást nézegetni, ezért abból kell levonnom következtetést amire ez alatt jutok.

Nem azt mondtam, hogy nincs olyan hely hanem, hogy gyanús. Kizárod az elméleti lehetőségét, hogy előálljon az a helyzet amit példaként írtam a jelenlegi információink alapján?

Ad1: használj szekvenciá(ka)t

Ad2: tárold külön a semver értékeit

Ad3: legyen computed column a full version string

mindez atomi.

Az ORM sokszor az ördög maga. Kritikus dolgokra mindig ott az SQL, de azt már sajnos elfedik és sokan nem is akarják megérteni. Nem feltétlenül neked írom, de számomra az ORM tanulási görbéje lehet h meredekebb, de a facepalm és a debug faktora is. :-)

Megvan a helye a JPA-nak, ami nem csak egy ORM, ugye. A nagyobb probléma általában abból van, hogy olyakor és olyanra is használják, amire nem kellene; illetve a kisebb probléma általában abból van, hogy olyankor és olyanra nem használják, mikor és amire lehetne. És akkor a végkövetkeztetés az, hogy JPA a szar, pedig szimplán csak nem értenek hozzá. Ez sok mindenre igaz, csak a JPA esetén igen nagyot is lehet szopni.

dehátpedig mixing sql és jpa implementációért/alkalamzásért botütés jár közszemlén a daily standupon!!!444 négy

 

 Olyan hogy JPA melllett nativ sql megoldásokat _merj_ alkalmazni, mindkettőhöz érteni kell. Abban az országban, ahol fontosfile_final2_veglegesv4_utolso_geza.xlsx tök normális :-)

dehátpedig mixing sql és jpa implementációért/alkalamzásért botütés jár közszemlén a daily standupon!!!444 négy

Miért ne lehetne? A legnagyobb hülyeség az ilyen "nálunk ezt nem lehet" és "mi nem szoktuk" jellehű önkorlátozás valami múltbeli szokásjog alapján, aminek az oka már rég a ködbe veszett.

Olyan hogy JPA melllett nativ sql megoldásokat _merj_ alkalmazni, mindkettőhöz érteni kell.

Igen. És?

Lehet. Én is ezt vallom. De amikor a fent emlitett entity cache meg egyéb dolgok kapcsan mindenki megijed, hogy jajj KÉTFÉLE móron akarjuk szegény perzisztens layer állapotát bizergalni, jön a sikítás meg a futás szaladás. :-)

Hirtelen át kell ugye gondolni hol kell lehet invalidálni a cache-t, egyáltalán kell-e oda ORM vagy csak lusták vagyunk. Esetleg az egészet wrappelni kéne feljebb egy saját AdatElérőManagelő rétegbe h "kint" ne kelljen foglalkozni vele? Ekkor a teljes adat szerkezetet ismerni kell állapotostul elévüléstül életciklusostúl. Pedig csak egy entity.save() volt eddig. :)

 

ezt a Crud muveletek final2.docx ben olvastan, ami lehet nem az utolso valtozat!:-)

No. Kicsit beszélgettem ChatGPT barátunkkal (aki eléggé szeret félrebeszélni és tévutakra vezetni sajnos). Kb. ugyanoda lukadtunk ki a Pessimistic lock-al mint az optimistic lock-al. Az oka egyszerű. A különböző Entity Cache-k különböző állapotokat tárolhatnak és nem mindig vánszorog le szerinte a pessimistic lock sem a DB szintre és nem is mindig vánszorognak onnan fel a friss adatok, ha pl. a cache-ből szedi ki. Valamint az egész tranzakció végéig futhat több példány ismét, mert sokszor csak akkor tolja le a DB szintre a write lock-ot így akkor derül csak ki, hogy ütközés van.

Valami olyasmit javasolt végül, hogy külön tranzakcióban nyomjam le a foglaltságot a DB-be és külön EntityMAnager-ben:
 

@Stateless
public class ControlService {

    @PersistenceContext
    private EntityManager entityManager;

    private static final String CONTROL_NAME = "processing_control";

    public boolean isProcessing() {
        EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
        try {
            em.getTransaction().begin();
            Control control = em.find(Control.class, CONTROL_NAME, LockModeType.PESSIMISTIC_WRITE);
            if (control == null) {
                control = new Control();
                control.setName(CONTROL_NAME);
                control.setStatus("fut");
                control.setLastStarted(new Date());
                em.persist(control);
                em.getTransaction().commit();
                return false;
            } else {
                if (control.getStatus() == null) {
                    control.setStatus("fut");
                    control.setLastStarted(new Date());
                    em.merge(control);
                    em.getTransaction().commit();
                    return false;
                } else {
                    em.getTransaction().commit();
                    return true;
                }
            }
        } catch (Exception e) {
            em.getTransaction().rollback();
            // handle exception
        } finally {
            em.close();
        }
        return false;
    }

    public void finishProcessing() {
        try {
            Control control = entityManager.find(Control.class, CONTROL_NAME, LockModeType.PESSIMISTIC_WRITE);
            control.setStatus(null);
            entityManager.merge(control);
        } catch (Exception e) {
            // handle exception
        }
    }
}

 

Ez a megoldás sem tűnik jónak, mert ha jól tudom az EntityCache az EntityManagerFactory-nként van. Tehát megint csak beleesek a különböző Entity Cache problémáiba. Eddig volt olyan helyem ahol volt entity cache de csak egy nagy monolitikusban, volt ahol ki volt kapcsolva, ilyen még nem volt, hogy van is meg nincs is. :)
Esetleg egy külön DS megoldhatja ezt a gondot?

és nem mindig vánszorog le szerinte a pessimistic lock sem a DB szintre

Ne higgy a ChatGPT-nek, az egy olyan senior kolléga, aki időnként trollkodik. Ez elég nagy baj lenne, ha a tranzakcióban nem vándorolna le a lock az adatbázisba.

Igen, már tapasztaltam. :) Nemrég megkérdeztem máshogy. Erre eléggé mást válaszolt:

Igen, ha egy tranzakcióban futtatja az em.find(ControlTable.class, "PROCESS_CONTROL", LockModeType.PESSIMISTIC_WRITE) metódust, akkor a lock csak a tranzakció végén kerül érvényesítésre, amikor a tranzakció commit-olódik. A lockot addig tartja a JPA, hogy a tranzakció lefutása során ne lehessen más tranzakcióknak hozzáférni az adatokhoz.

Mondjuk még ez is félreérthető volt, így még egyszer rákérdezve már azt a választ adta, hogy amikor kiadjuk a find-ot, akkor direktbe bekérdez a DB-be. Nem használja a cache-t, mert lock is van a metódus hívásban. És attól kezdődően él a lock a DB szinten is a tranzakció végéig.

A locking nem kizárólag adatbázis (pláne nem kizárólag RDBMS vagy SQL) téma.
Érdemes az elméleti alapjaival tisztába jönni először és utána ránézni a konkrét implementációra hogy az mennyire bugos.

Az adott esetben bugos implementáció nem az elvet minősíti.

A pessimistic lock az biztosan sorba állít és biztosan van benne egy drága lockolás. Az egy másik kérdés hogy mennyire durva a lock (table lock vs row-level lock például)

Az optimistic lock nem biztos hogy sorba állít viszont ha megpukkan akkor még az előzőnél is drágább helyrerakni a dolgokat. 
Mind runtime drágább mint kódolásilag.

Gábriel Ákos

Ha tudnád hogy hány feldolgozó szál fut párhuzamosan, akkor egyszerűen modulo-val szűrhetnéd hogy a szálak ne lépjenek egymás lábára, mind csa a saját tortaszeletével foglalkozzon .

Másolj ide be légyszi két ugyanolyan rekordot amit beírt a rendszer és nem kellett volna.

Szerkesztve: 2023. 03. 13., h – 12:33

A fent írt utasítás a következő kontextusban működik:


for (siker=0, error=0; !siker && !error; ) {
    SELECT ...,version INTO ..., :version FROM table WHERE id=:id;
    ...új állapot előállítása...
    UPDATE table SET ..., version= :version+1
    WHERE id=:id AND version=:version;
    if (sqlca.sqlcode<0) error= 1;
    else if (updated_rows==1) siker= 1;
}

Az 'optimizmus' ebben az esetben azt jelenti, hogy a rekordot nem zároltuk, hanem bízunk abban, hogy nem pont akkor módosítja valaki más, amikor mi is dolgozunk a tétellel.