Race conditions a concurrency patří mezi nejzákeřnější chyby moderních webových aplikací. Často se projeví až ve chvíli, kdy systém obsluhuje více uživatelů současně – například při objednávkách, platbách nebo rezervacích. Jak fungují, proč tyto problémy vznikají a jak jim předcházet pomocí transakcí, locků nebo idempotence?
Co je concurrency
Concurrency označuje schopnost aplikace zpracovávat více úloh během stejného časového období. Nejde nutně o skutečný paralelní běh na více procesorech, ale o efektivní střídání a koordinaci více operací.
V moderním webu se s concurrency setkáváme neustále. Webový server obsluhuje více uživatelů současně, databáze zpracovává několik dotazů najednou a frontend komunikuje s více API endpointy. Díky tomu mohou aplikace reagovat rychleji a lépe využívat dostupné prostředky.
Práce s více souběžnými operacemi však přináší i rizika. Pokud několik požadavků současně pracuje se stejnými daty, mohou vznikat nekonzistentní stavy nebo takzvané race conditions. Proto je správné řešení concurrency důležitou součástí návrhu moderních webových aplikací.
Co je race condition
Race condition je situace, kdy výsledek operace závisí na pořadí nebo načasování více souběžných požadavků. Pokud několik procesů pracuje současně se stejnými daty, může dojít k nekonzistentním výsledkům.
Typickým příkladem je e-shop s posledním kusem zboží skladem. Dva zákazníci odešlou objednávku ve stejný okamžik, oba vidí produkt jako dostupný a systém ho může prodat dvakrát.
Často se objevují u plateb, rezervací, slevových kódů nebo editace dat. Proto je důležité počítat se souběžným zpracováním požadavků a chránit kritické operace pomocí transakcí, locků nebo atomických databázových operací.
Řešení začíná v databázi
Při řešení race conditions nestačí spoléhat pouze na aplikační logiku. I když aplikace správně ověří stav dat, mezi kontrolou a samotným zápisem může jiný souběžný požadavek data změnit. Právě proto se většina ochranných mechanismů implementuje přímo v databázi.
Základem jsou databázové transakce umožňující provést více operací jako jeden celek. Pokud během zpracování nastane problém nebo konflikt, databáze změny neuloží a vrátí systém do původního stavu. Díky tomu nedochází k nekonzistentním datům.
Důležitou roli hrají také atomické operace, při kterých databáze provede kontrolu i změnu v jediném kroku. Místo samostatného načtení hodnoty a následného zápisu je tak menší prostor pro vznik race condition.
Databáze navíc nabízejí různé mechanismy zamykání dat (locking), jenž mohou dočasně zabránit jiným transakcím ve změně stejného záznamu. Právě kombinace transakcí, atomických operací a vhodně nastavených omezení bývá nejspolehlivější obranou proti problémům způsobeným souběžným přístupem k datům.
Optimistic locking: Jak zabránit přepsání cizích změn
Optimistic locking je technika používaná pro ochranu dat při souběžných úpravách. Vychází z předpokladu, že konflikty mezi uživateli nejsou příliš časté, a proto není potřeba data předem zamykat.
Každý záznam obvykle obsahuje číslo verze nebo čas poslední změny. Když uživatel data načte, aplikace si tuto hodnotu zapamatuje. Při ukládání změn následně ověří, zda se záznam mezitím nezměnil. Pokud ano, aktualizace se neprovede a uživatel je upozorněn na konflikt.
Typickým příkladem je editace článku, produktu nebo uživatelského profilu. Dva administrátoři otevřou stejný záznam, jeden změny uloží a druhý se o několik minut později pokusí uložit svou verzi. Díky optimistic lockingu nedojde k přepsání novějších dat starší verzí.
Výhodou tohoto přístupu je vysoký výkon a minimální dopad na databázi, protože nedochází k blokování záznamů. Proto se optimistic locking často používá v redakčních systémech, CRM aplikacích nebo administracích, kde jsou konflikty spíše výjimečné.
Pessimistic locking: Když je konflikt pravděpodobný
Pessimistic locking funguje opačně než optimistic locking. Vychází z předpokladu, že ke konfliktům může docházet často, a proto data preventivně uzamkne ještě před jejich úpravou. Dokud je záznam zamčený, ostatní požadavky musí počkat nebo jsou odmítnuty.
Tento přístup se používá hlavně u operací, kde by konflikt mohl způsobit finanční škodu nebo nekonzistentní data. Typickým příkladem jsou platby, rezervace letenek, prodej posledních kusů zboží nebo práce s bankovními účty.
Představte si rezervační systém hotelu. Ve chvíli, kdy si uživatel začne rezervovat poslední volný pokoj, systém záznam dočasně uzamkne. Jiný zákazník tak nemůže stejný pokoj rezervovat ve stejném okamžiku. Teprve po dokončení nebo zrušení rezervace se zámek uvolní.
Výhodou pessimistic lockingu je vysoká jistota, že nedojde ke konfliktu nebo přepsání dat. Nevýhodou může být nižší výkon a horší škálovatelnost, protože zamčené záznamy mohou blokovat další požadavky. Proto se používá především u kritických operací, kde je správnost dat důležitější než maximální rychlost.
Idempotence: Bezpečnost pro opakované požadavky
Moderní webové aplikace musí počítat s tím, že stejný požadavek může dorazit vícekrát. Uživatel může dvakrát kliknout na tlačítko „Zaplatit“, mobilní aplikace může po výpadku připojení zopakovat request nebo externí služba odešle webhook znovu, protože nedostala potvrzení o přijetí.
Bez ochrany by taková situace mohla vést například k vytvoření dvou objednávek, dvojímu stržení platby nebo opakovanému odeslání e-mailu. Právě zde pomáhá idempotence.
Princip spočívá v tom, že každá operace dostane unikátní identifikátor, takzvaný idempotency key. Server si zapamatuje, že daný požadavek už zpracoval. Pokud později přijde stejný požadavek se stejným klíčem, neprovede akci znovu, ale vrátí původní výsledek.
Idempotence je dnes běžnou součástí platebních systémů, API služeb i e-shopů. Pomáhá zajistit, že důležité operace proběhnou pouze jednou bez ohledu na to, kolikrát je klient omylem nebo z technických důvodů odešle. Díky tomu zvyšuje spolehlivost aplikace a chrání data před duplicitními akcemi.
Frontend problém nevyřeší, ale může pomoci
Mnoho vývojářů se snaží race conditions řešit už na frontendu. Běžným postupem je například deaktivace tlačítka po kliknutí, zobrazení načítacího stavu nebo zabránění opakovanému odeslání formuláře. Taková opatření mohou zlepšit uživatelský zážitek a snížit počet nechtěných duplicitních akcí.
Je však důležité si uvědomit, že frontend není bezpečnostní vrstva. Uživatel může odeslat více požadavků ručně, pomocí vývojářských nástrojů v prohlížeči nebo přímo přes API. Útočník navíc může posílat desítky paralelních requestů bez jakékoliv interakce s uživatelským rozhraním.
Frontend by proto měl být pouze první linií obrany. Skutečná ochrana proti race conditions musí být implementována na backendu a v databázi pomocí transakcí, locků, atomických operací nebo idempotence. Teprve kombinace obou přístupů zajistí dobrou uživatelskou zkušenost i správné fungování aplikace při souběžném zatížení.
Výkon nestačí, důležitá je i správnost
Concurrency a race conditions patří mezi problémy, které se často projeví až v reálném provozu pod zátěží. Zatímco concurrency je přirozenou součástí moderních aplikací, race conditions vznikají ve chvíli, kdy systém není připraven na souběžné operace nad stejnými daty.
Správné řešení proto nespočívá pouze v úpravách frontendu, ale především v použití databázových transakcí, lockingu, atomických operací a idempotence.
S rostoucí návštěvností a složitostí aplikací navíc roste i význam kvalitní infrastruktury. Vývojáři dnes stále častěji provozují webové aplikace, API služby nebo mikroservisy v cloudovém prostředí, kde mohou snadno škálovat výkon podle aktuální zátěže.
Pro provoz moderních aplikací může být zajímavou volbou například cloudová infrastruktura od ZonerCloudu, která nabízí virtuální servery, databázové služby i výkonnou infrastrukturu pro náročnější projekty. Díky tomu lze snadněji zajistit nejen výkon, ale také stabilitu aplikací při souběžném zpracování velkého množství požadavků.












