Systémový návrh validace QR vstupenek: od offline režimu k idempotentnosti
Kontrolor naskenuje QR kód vstupenky, systém ji ověří v databázi a označí jako použitou. Hlavní požadavek: jedna vstupenka – jeden vstup. Na první pohled jednoduchý úkol se komplikuje síťovými výpadky, frontami a více kontrolory. Prozkoumáme vývoj řešení od offline validace k spolehlivému serverovému zpracování.
Offline režim využívá digitální podpis (ECC nebo RSA). QR obsahuje data vstupenky a podpis, kontrolor ověří veřejným klíčem bez sítě. Problém: chybí synchronizace mezi kontrolory. Snímek obrazovky s vstupenkou projde každou bránou nezávisle.
Serverový stav a atomické aktualizace
Jednotné úložiště řeší problém synchronizace. Každé skenování je požadavek na server s atomickou aktualizací stavu.
Příklad v PostgreSQL:
UPDATE tickets
SET status = 'wasted', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
Pokud je vrácen řádek – vstupenka je zneplatněna, vstup povolen. Žádný řádek – vstupenka již byla použita nebo neexistuje.
Klasický problém: ztráta odpovědi od serveru. Kontrolor v oblasti slabého signálu odešle požadavek, server aktualizuje databázi, ale odpověď nedorazí. Časový limit, opětovné skenování – server odmítne, klient neprojde navzdory zaplacené vstupence.
Idempotentnost na úrovni API
Řešení – idempotentní požadavky s unikátním klíčem (UUID), generovaným při otevření sezení skenování. Klíč zaznamenává pokus, umožňuje opakování bez duplikace.
Logika v transakci (read committed):
begin ;
-- 1. Zaznamenáme pokus
INSERT INTO processed_requests (ticket_id, idempotency_key)
VALUES (:ticket_id, :idempotency_key)
ON CONFLICT (ticket_id, idempotency_key)
DO NOTHING RETURNING *;
-- 2. Pokud rows_affected == 0: rollback, vrátit SUCCESS
-- 3. Zneplatníme vstupenku
UPDATE tickets
SET status = 'wasted', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
-- 4. Pokud rows_affected == 0: rollback, vrátit ERROR
commit;
-- 5. Vrátit SUCCESS
Transakce zajišťuje atomičnost: buď je klíč + stav aktualizován, nebo nic. Unikátní index na (ticket_id, idempotency_key) zabraňuje závodům – druhý požadavek čeká nebo jde do DO NOTHING.
Opakovaný pokus ve stejném sezení vrátí úspěch, i když první odpověď byla ztracena.
Head-of-Line Blocking a ztráta kontextu
Skutečný problém: kontrolor pod tlakem fronty uzavře sezení bez odpovědi. Nové skenování vygeneruje nový idempotency_key. Server vidí použitou vstupenku s cizím klíčem – odmítne.
Kontrolor nerozliší legitimního klienta od podvodníka se snímkem obrazovky. HoL-blokování vyžaduje konečnou odpověď před další vstupenkou.
Kompromis: vracet časové razítko posledního použití. Kontrolor vyhodnotí: „před 2 minutami – stejná osoba“. Riziko: skupina se společným QR projde postupně.
Klíčové výzvy:
- Síťové časové limity bez ztráty stavu
- Synchronizace více kontrolorů
- Lidský faktor ve frontách
- Rovnováha spolehlivosti a UX
Co je důležité
- Atomický UPDATE s podmínkou status='active' zabraňuje dvojímu zneplatnění
- Idempotentnost přes UUID + unikátní index zaručuje opakování bez duplikátů
- Transakce (read committed) zajišťují sekvenční viditelnost změn
- HoL-blokování je nevyhnutelné pro 100% spolehlivost bez biometrie
- Kompromis s časovým razítkem přenáší rozhodnutí na operátora
Perspektivy vylepšení
Statické QR zastarávají. Dynamické tokeny nebo challenge-response s jednorázovými OTP řeší problém. Kontrolor požádá o nonce od serveru, klient podepíše – server ověří a atomicky označí.
Biometrie (face ID) nebo NFC eliminují snímky obrazovky, ale vyžadují hardware. QUIC minimalizuje ztrátu paketů, ale neřeší ztrátu kontextu.
Škálování: Redis pro mezipaměť idempotency_keys, sharding podle ticket_id. Monitorování: metriky failed_scans podle časového razítka, A/B testy kompromisů.
— Editorial Team
Zatím žádné komentáře.