Skip to content
Snippets Groups Projects

Bezpečné transakce při zakládání uživatelů/účastníků/účastí

Merged Martin Mareš requested to merge mj/transakce into devel
  1. Jan 14, 2023
    • Martin Mareš's avatar
      Účastnický submit ošetřuje kolize · fc1f361a
      Martin Mareš authored
      Closes #206
      fc1f361a
    • Martin Mareš's avatar
      Registrace ošetřuje kolize při změně e-mailu · 1195ddeb
      Martin Mareš authored
      Při implementaci registrace jsme na to úplně zapomněli.
      
      Closes #307 and #321.
      1195ddeb
    • Martin Mareš's avatar
      Registrace používá find_or_create_user · 66692424
      Martin Mareš authored
      Tím se zavírá race condition při zakládání řádků v tabulce user.
      66692424
    • Martin Mareš's avatar
      Bezpečné transakce při zakládání uživatelů/účastníků/účastí · 6aff4e91
      Martin Mareš authored
      Closes #314:
      
      Pokud organizátor odešle POST na přidání nového účastníka soutěže
      dvakrát rychle po sobě (třeba kvůli nějakému automatickému retry
      po rozpadu spojení), dvě různé DB transakce se snaží založit uživatele
      se stejným loginem. Jedna z nich selže na unikátnosti sloupce email.
      
      V defaultní úrovni izolace transakcí (READ COMMITTED) to nemá žádné
      hezké řešení. Nepomůže SELECT ... FOR UPDATE, jelikož ten zamyká pouze
      nalezené řádky, nikoliv neexistenci dalších řádků vyhovujících podmínce.
      
      Co by se dalo dělat:
      
      (1) Zvýšit úroveň izolace aspoň na READ REPEATABLE. To vyřeší problém,
          ale současně může začít víceméně jakákoliv zapisující transakce
          failovat. Vyžadovalo by dopsat retry do prakticky všech míst v OSMO,
          kde je nějaký commit.
      
      (2) Retryovat specificky transakce na zakládání užívatelů (a účastí apod.).
          Tohle nejde snadno, jelikoz jsou i součástí dlouho běžících transakcí
          v importech (zatím jsme se snažíli, aby byl celý import atomický
          a v případě selhání se celý rollbackoval). To by možná mohly vyřešit
          subtransakce.
      
      (3) Zamykat celou tabulku s uživateli, než na ní provedeme první SELECT.
          To by asi vyřešilo problém, ale byl by potřeba zápisový zámek, takže
          by paralelně nemohla běžet žádná čtení. A také by se to potenciálně
          mohlo deadlockovat (potřebujeme v jedné transakci postupně lock na
          uživatele, účastníky a účasti a locky platí až do konce transakce).
      
      (4) Používat INSERT ... ON CONFLICT <něco>. To vypadá bezpečně, jen to není
          moc pohodlné, zejména proto, že s tím nepočítá ORM, takže je potřeba
          dělat všechno ručně.
      
      Zatím jsem zvolil (4), protože mi přijde, že to změny udržuje lokální
      a funguje i s dlouhými transakcemi při importu. Výhledově bych se ale
      chtěl zamyslet nad tím, jak takové věci řešit co nejuniverzálněji
      a nejpohodlněji.
      6aff4e91
    • Martin Mareš's avatar
      37185c2f
    • Martin Mareš's avatar
      1a1f02fe
Loading