Fórum Root.cz

Hlavní témata => Server => Téma založeno: hknmtt 01. 05. 2025, 13:51:51

Název: MySQL: read once
Přispěvatel: hknmtt 01. 05. 2025, 13:51:51
Mam MySQL/MariaDB tabulku, ktora ma povedzme tri stlpce:
- prvy stlpec je integer, ktory je auto-increment
- druhy stlpec je unikatne a raditelne(sortovatelne) id odvodene od nejakej casovej znacky, ktore je vytvorene v aplikacii, takze je mozne chronologicky podla nej zoradit zaznamy
- treti stlpec su nejake data vo forme blobu

Potrebujem zabezpecit, ze kazdy riadok sa precita nejakym konzumentom iba jeden krat.

Problem: konzument ma read-only transakciu a precita si vsetky zaznamy, ked dobehne na koniec, ulozi si posledne id, nasledne opakuje proces s novou read-only transakciou, s tym ze pouzije posledne spracovane id ako podmienku pre vratenie novsich vysledkov. Ak nie su nove zaznamy, tak spi nejaky cas a proces znovu opakuje.

Teraz mam dve write transakcie, jedna zacne a vytvori nejake zaznamy. Druha write transakcia zacne po nej a tiez vytvori nejake zaznamy. Avsak druha transakcia skonci skor a commitne zmeny. Nasledne skonci prva transakcia a commitne zmeny.

Problem, ktory tu vznika je ten, ze prva transakcia bude mat mensi auto-increment integer(prvy stlpec), pripadne aj id ktore sa generuje v aplikacii(druhy stlpec), nez druha transakcia, takze chronologicky je to v poriadku. Lenze tym ze druha transakcia commitla skor, tak sa do DB zapisu riadky v "nespravnom" poradi. Co je bezne v poriadku, nic sa nedeje, mame stale unikatne idcka pre prvy aj druhy stlpec a indexy na radenie.

Lenze nas konzument ma teraz problem, ze preskocil zaznamy prvej transakcie, pretoze cital zaznamy, dobehol na koniec, spal, druha transakcia commitla zmeny, konzument spracoval nove zaznamy a ulozil si posledne id(ci uz auto-increment prveho stlpca, alebo aplikacne id z druheho stlpca), nasledne commitla prva transakcia, lenze mala mensie auto-increment id ako aj id z aplikacie, takze nas konzument tieto zaznamy nevidi.

Takze by ma zaujimalo, ako je mozne takyto problem riesit bez toho, aby sa kazdy riadok oznacil ako uz precitany a teda konzument tak nepreskocil zaznamy kvoli poradiu v ktorom su transakcie commitnute?
Název: Re:MySQL: read once
Přispěvatel: Kit 01. 05. 2025, 14:03:49
Mam MySQL/MariaDB tabulku, ktora ma povedzme tri stlpce:
Potrebujem zabezpecit, ze kazdy riadok sa precita nejakym konzumentom iba jeden krat.

V podstatě potřebuješ implementovat frontu. MySQL na to není zrovna vhodná. Pokud bys použil třeba Redis, tak ten to umí už v základu.

V MySQL budeš potřebovat ještě čtvrtý sloupec se stavem, např.
Kód: [Vybrat]
ENUM('čeká', 'zpracovává se', 'hotovo') DEFAULT 'čeká', který budeš průběžně modifikovat a výběr budeš dělat podle něho.
Název: Re:MySQL: read once
Přispěvatel: hknmtt 01. 05. 2025, 14:08:16
Mam MySQL/MariaDB tabulku, ktora ma povedzme tri stlpce:
Potrebujem zabezpecit, ze kazdy riadok sa precita nejakym konzumentom iba jeden krat.

V podstatě potřebuješ implementovat frontu. MySQL na to není zrovna vhodná. Pokud bys použil třeba Redis, tak ten to umí už v základu.

V MySQL budeš potřebovat ještě čtvrtý sloupec se stavem, např.
Kód: [Vybrat]
ENUM('čeká', 'zpracovává se', 'hotovo') DEFAULT 'čeká', který budeš průběžně modifikovat a výběr budeš dělat podle něho.

Ono pouzit stlpec na oznacenie precitania, ako som pisal hore, je ako-tak v poriadku...ak mam len jedneho konzumenta. Chcel by som to poriesit tak, aby to mohlo fungovat pre viacerych.

Nemozem v podstate ani urobit nejaku dodatocnu tabulku, do ktorej by som kopiroval idcka novych zaznamov, pretoze mi vznika uplne totozny problem kvoli tomu, ze zaznamy nie su v absolutnom poradi. Drzat zoznam spracovanych zaznamov v pameti pre kazdeho konzumenta je dost nerealne. Cize ani to nie je cesta. Mozem pripadne urobit opak a teda pre kazdeho konzumenta kopirovat do vedlajsej tabulky id spracovanych zaznamov a proste filtrovat podla toho.
Název: Re:MySQL: read once
Přispěvatel: hknmtt 01. 05. 2025, 14:15:51
Vo svojej podstate, jedine co realne potrebujem je, aby databaza mala nejaky auto-increment, ktory sa zapise az pocas commitu, takze nemoze vzniknut out-of-order insert zaznamov a to by vyriesilo cely moj problem.
Název: Re:MySQL: read once
Přispěvatel: Filip Jirsák 01. 05. 2025, 14:18:50
Když budete mít víc konzumentů, stejně nebudete chtít, aby se o záznamy prali. Takže ty záznamy rozdělíte do oddílů a každý konzument bude zpracovávat svůj oddíl. Třeba na základě ID záznamu modulo počet konzumentů.

Vo svojej podstate, jedine co realne potrebujem je, aby databaza mala nejaky auto-increment, ktory sa zapise az pocas commitu, takze nemoze vzniknut out-of-order insert zaznamov a to by vyriesilo cely moj problem.
To je přesně něco, co nejde, pokud nepoužijete úroveň izolace transakcí SERIALIZABLE, tedy že se bude provádět jedna transakce za druhou, nepoběží paralelně vedle sebe.
Název: Re:MySQL: read once
Přispěvatel: Michal Šmucr 01. 05. 2025, 14:44:45
Asi bych se snažil vylepšit tu logiku tak, aby z toho byla normální fronta s joby, kdy budu z consumera aktualizovat stav a nebudu spoléhat na nějaké sekvenční číslování. Tzn. měl bych tam sloupec se stavem, nevím teď např. smallint (0 - pending, 1 - processing, 2 - done, 3 - failed), co bych aktualizoval podle potřeby. Plus případně nějaká další metadata (který worker/consumer to udělal atp.).
MySQL 8, případně teď jsem se díval, už že i MariaDB 10.6 by měla umět SELECT s použitím FOR UPDATE SKIP LOCKED.
FOR UPDATE zařídí, že když nějaký consumer přečte tabulku, a vrátí mu to jeden řádek (LIMIT 1) co je pending, tak ten řádek také zároveň zamkne než skončí jeho transakce (změní sloupec stav).
To SKIP LOCKED zas zajistí, že pokud už bude řádek zamčený transakcí z jiného consumeru, tak se přeskočí.


Název: Re:MySQL: read once
Přispěvatel: hknmtt 01. 05. 2025, 14:50:16
Ja neriesim ziadnu frontu ani vzajomne exkluzivnych konzumentov. Zase to tu ide to offtopicu, ako vzdy, takze tomu chcem zabranit co najskor. Jedine, co potrebujem, je zabezpecit citanie zaznamov v konzistentnom poradi bez preskocenia v pripade, ze transakcie comitnu zmeny v odlisnom poradi nez v ktorom boli zacate.
Název: Re:MySQL: read once
Přispěvatel: hknmtt 01. 05. 2025, 16:28:29
Uz som to vyriesil. Ak by niekto riesil nieco podobne, tak je nutne pouzit id generovane v aplikacii, ktore su unikatne ale zoraditelne podla casu, respektive monotonicky. Vzdy ked sa vytvori nova write transakcia, treba pre nu vygenerovat nove id a ulozit si zoznam aktivnych transakcii. Akekolvek data budu v ramci tejto transakcie vyprodukovane, budu mat vzdy vecsie id nez je id samotnej transakcie, kedze cas tyka kupredu a id sa budu posuvat dopredu taktiez. Konzument jedine co musi urobit je robit range select kde pouzije svoje posledne spracovane id ako spodnu hranicu a id najmensej aktualne aktivnej transakcie ako horny limit. Ak transakcia comitne, pouzije sa id nasledovnej najnizsej transakcie ako horny limit. Ak sa transakcia revertne, rovnaky princip - na dalsiu najmensiu alebo az po koniec zaznamov. Transakcie mozu byt comitnute v akomkolvek poradi, hlavne je vzdy pouzit horny limit najnizsej transakcie, bez ohladu na jej ne/uspech, a takto rad za radom postupne citat viac a viac novych zaznamov.
Název: Re:MySQL: read once
Přispěvatel: Filip Jirsák 01. 05. 2025, 20:05:36
Zase to tu ide to offtopicu, ako vzdy
Je dobře, že jste si toho konečně všimnul. Teď se ještě zamyslete nad tím, zda to náhodou není tím, jak kladete dotazy. Když se to jiným neděje, skoro to vypadá, jako by to bylo vámi a vašimi nesrozumitelnými dotazy.

To vaše řešení je divné (tím, že generování ID přesunete z databáze do aplikace se těžko může něco změnit – pokud ta aplikace nesplňuje nějaké podmínky, které pro databázi neplatí), vychází z předpokladů, které jste v zadání nenapsal (takže těžko může někdo takové řešení navrhnout – ale to je stále dokola váš problém, že neumíte popsat, jaký problém řešíte). A věty jako „Transakcie mozu byt comitnute v akomkolvek poradi, hlavne je vzdy pouzit horny limit najnizsej transakcie, bez ohladu na jej ne/uspech“ ve čtenářích vzbuzují otázky, zda víte, co je to commit transakce, zda víte zejména co je to rollback transakce a kde přijde čtenář k ID neúspěšné, tedy rollbacknuté transakce.
Název: Re:MySQL: read once
Přispěvatel: Sox37 02. 05. 2025, 08:15:03
Kromě transakcí si nastudujte také zámky. InnoDB podporuje i tzv. gap locks, kterými jedna transakce může zabránit druhé vkládat řádky s určitými hodnotami indexu. Ve vašem případě by tak transakce s menšími hodnotami mohla zamknout rozsah za jejím posledním timestampem a transakce s vyšsími timestampy by tak musela čekat.

https://dev.mysql.com/doc/refman/8.4/en/innodb-locking.html
Název: Re:MySQL: read once
Přispěvatel: MalyTomi 02. 05. 2025, 08:50:06
a co takto to urobit, ako sa bezne robi synchro? pridas stlpec s datumom a casom vlozenia zaznamu a namiesto pamatania si idcka, si len zapamatas najvyssi datum. A pojdes od neho vyssie. Takto mozu byt viaceri consumeri a kazdy si moze ist od ineho zaznamu, a neriesis kto kedy ten zaznam vlozil. Nemusis ani riesit letny/zimny cas, ak ten datum bude GMT, a ani rozdielne casy na roznych strojoch, kedze sa kontrolovat bude stale voci tomu, kde je db.
Název: Re:MySQL: read once
Přispěvatel: Sox37 02. 05. 2025, 10:11:23
MalyTomi: To je to, co má OP aktuálně. Problém je v tom, že má více producentů a do DB mohou být vloženy starší záznamy až po tom, co tam jsou už nějaké novější.

Podle mě by ani to moje řešení se zámky nefungovalo, musel by se spíš použít nějaký timeout, aby byl dostatek času pro vložení starších záznamů. Nebo případně aplikaci upravit tak, aby nebylo potřeba záznamy zpracovávat chronologicky.
Název: Re:MySQL: read once
Přispěvatel: Tomas-T 02. 05. 2025, 10:56:52
Nevím, zda by to nekolidovalo s nějakými jinými neuvedenými požadavky, ale jednoduché by bylo nenačítat záznamy až do aktuálního času, ale třeba minimálně minutu staré - pokud lze učinit předpoklad, že mezi takto starými záznamy žádná ještě nedokončená transakce už neběží, takže tam nic nepřibyde.
Název: Re:MySQL: read once
Přispěvatel: Filip Jirsák 02. 05. 2025, 11:17:08
Nevím, zda by to nekolidovalo s nějakými jinými neuvedenými požadavky, ale jednoduché by bylo nenačítat záznamy až do aktuálního času, ale třeba minimálně minutu staré - pokud lze učinit předpoklad, že mezi takto starými záznamy žádná ještě nedokončená transakce už neběží, takže tam nic nepřibyde.
Přesně tak. Vzhledem k tomu, že podle zadání hknmtt nevadí, když se nějaké záznamy zpracují dvakrát, a hknmtt zjevně moc netuší, jak to v té aplikaci vlastně vypadá s transakcemi, je nejspolehlivější metoda udělat při zpracování dostatečně velký překryv – větší, než je maximální možná délka transakce.

Druhá věc je, že hknmtt psal, že už si teď označuje záznamy jako zpracované, a to, co řeší, je jen optimalizace. Přičemž vůbec nevíme, proč to chce optimalizovat, zda je tam nějaký výkonnostní problém, a pokud ano, tak jaký. Takže vzhledem k tomu, že hknmtt nedokáže popsat, jaké jsou tam předpoklady, ani jak to v aplikaci funguje s transakcemi, ani proč to che optimalizovat, je nejlepší doporučení, které můžeme dát – nesahat na to a nechat to tak, jak to je.
Název: Re:MySQL: read once
Přispěvatel: NCC1701E 02. 05. 2025, 12:48:47
Mam MySQL/MariaDB tabulku, ktora ma povedzme tri stlpce:
Potrebujem zabezpecit, ze kazdy riadok sa precita nejakym konzumentom iba jeden krat.

V podstatě potřebuješ implementovat frontu. MySQL na to není zrovna vhodná. Pokud bys použil třeba Redis, tak ten to umí už v základu.

V MySQL budeš potřebovat ještě čtvrtý sloupec se stavem, např.
Kód: [Vybrat]
ENUM('čeká', 'zpracovává se', 'hotovo') DEFAULT 'čeká', který budeš průběžně modifikovat a výběr budeš dělat podle něho.

Ono pouzit stlpec na oznacenie precitania, ako som pisal hore, je ako-tak v poriadku...ak mam len jedneho konzumenta. Chcel by som to poriesit tak, aby to mohlo fungovat pre viacerych.


Pro víc konzumentů to může fungovat tak, že každý konzument bude mít svoje číslo a vždy po zpracování zvedne příznak (zvláštní sloupec) o jedničku. Pak je nutné si pohlídat aby v ID konzumentů nebyla díra. Samozřejmě potom zpracování konzumenty bude sériové, ale dotaz bude naprosto jednoznačný. Paralelizace by se dala udělat nahrazením příznaku bitmapou (tam bych se bál paralelních updatů) nebo pomocí pole (podobný problém s updaty a režie navíc). Na druhou stranu, u pole které bude mít na začátku všechny konzumenty a po zpracování se bude ubírat by režie nebyla tak strašná a update je taky řešitelný.
Název: Re:MySQL: read once
Přispěvatel: MalyTomi 02. 05. 2025, 13:01:01
MalyTomi: To je to, co má OP aktuálně. Problém je v tom, že má více producentů a do DB mohou být vloženy starší záznamy až po tom, co tam jsou už nějaké novější.
Nejako tomu nerozumiem, ten datum a cas by sa generoval az v okamziku commitu, ako default hodnota. Takze aj keby tam niekto vkladal starsie zaznamy, tak ten datum a cas by bol aktualny. A ak by aktualizator bezal pocas transakcie, tak by aj tak tie necommitnute zaznamy nevidel a teda by mal ulozeny starsi cas.
Název: Re:MySQL: read once
Přispěvatel: Tomáš Crhonek 03. 05. 2025, 01:31:02
Já teda nevím, co umí a neumí MySQL, PostgreSQL umí serializované transakce, takže tohle je vyřešeno buď přímo standardní transakční izolací (a opravdu to bezpečně funguje) nebo ještě druhá varianta, která v některých případech je přímo vhodná pro stavbu atomické fronty a to je SELECT FOR UPDATE SKIP LOCKED.

https://www.postgresql.org/docs/17/sql-select.html#SQL-FOR-UPDATE-SHARE

Funguje to tak, že příkaz SELECT FOR UPDATE automaticky dá zámek pouze na ty řádky, které vyhovují WHERE. Takže SKIP LOCKED tyto řádky automaticky přeskočí.

Já si takto implementoval atomickou frontu, kde nebylo nutné udržet pořadí (ale stále jde mít ORDER BY) a každý řádek bylo nutné zpracovat pouze jednou a skutečně jednou a mít o tom záznam.

Takže něco jako WHERE todo=True, a v UPDATE potom ve transakci zapsat data a nastavit na SET todo=False + v mém případě ještě SET when=now() což je čas počátku transakce. Takže pochopitelně si pohlídat fci, jestli je to čas transakce nebo počátku session. Já to měl vlastně stejné, každý klient se připojil, zpracoval jeden záznam (až minuty nebo desítky minut), nebylo možné blokovat ostatní klienty (miliony položek v todo), takže jsem to vyřešit takto a doma v home labu jsem takto pustil jednoho klienta per CPU Thread, takže na síti cca 128 workerů a každý jeden řešil jeden obrázek (v mém případě, render z POVRay, převod na png správných rozměrů a ořezů a nakonec vytvoření animace a export jako video). Na data má Postgresql datový typ BYTEA do 1GB, což se vešlo.

Takže ověřit si, jestli MySQL umí serializable, jestli umí atomický set a where (ano umí, ale je potřeba si to vyžádat správnou izolací) a lze to mít i bez transakční izolace serializable, která toho uzamyká no vlastně všechno.

Tomáš Vondra o tom napsal pěkný článek na 2quadrant, ale nemůžu to najít.
Název: Re:MySQL: read once
Přispěvatel: Sam 03. 05. 2025, 11:06:22
V podstate jak zminil nekdo prede mnou potrebujes frontu. Napriklad Oracle ma implementaci oracle advanced queuing jiz skoro pres 20let ( pouzivame to v nasem reseni ). Hledej DBMS_AQ a DBMS_AQ_ADM ( pisu z hlavy ) pro inspiraci. Nevim jestli nekdo pro mysql neudelal nejaky podobny plugin. Samozrejme si muzes naprogramovat neco podobneho ( for update & skip locekd & nowait & limit ) ovsem transakce nebude read only.
https://dba.stackexchange.com/questions/98311/best-way-to-implement-concurrent-table-based-queue (https://dba.stackexchange.com/questions/98311/best-way-to-implement-concurrent-table-based-queue)

Název: Re:MySQL: read once
Přispěvatel: Michal Šmucr 03. 05. 2025, 12:12:38
Tahle další diskuse potom, co si tazatel našel vohejbák a vyřešil problém se svým rovnákem, který není fronta, je úplně zbytečná.

A ano, pokud by v podobné situaci někdo dělal frontu (což většinou dává smysl protože se to pak dá případně škálovat na víc workerů, stav těch úloh je persistentně na jednom místě, nemusí se řešit nějaké lastID ve workeru, pokud se to z jakéhokoliv důvodu restartuje, spadne atd.), tak MySQL 8 (od 2017) resp. Maria (od 2021) má mimo zámků na řádky také SKIP LOCKED, jak jsem psal. Tzn. jde to udělat podobně jako třeba v Postgresu, nejsou potřeba další workaroundy. Ale jak už tazatel zmínil, je to off-topic.
Název: Re:MySQL: read once
Přispěvatel: hknmtt 05. 05. 2025, 16:58:49
Ak by niekoho tato problematika zaujimala(teda to co bolo v povodnej otazke a nie ta typicka rootovska off topic diskusia o frontach a podobne), tak tu mozete najst nejake informacie https://mattjames.dev/auto-increment-ids-are-not-strictly-monotonic/