Conception d'un système de validation de billets QR : du mode hors ligne à l'idempotence
Un contrôleur scanne un code QR, le système vérifie sa validité dans la base de données et le marque comme utilisé. L'exigence fondamentale : un billet, une entrée. Une tâche apparemment simple devient complexe en raison des pannes réseau, des files d'attente et des multiples contrôleurs. Explorons l'évolution des solutions, de la validation hors ligne au traitement fiable côté serveur.
Le mode hors ligne utilise une signature numérique (ECC ou RSA). Le code QR contient les données du billet et la signature ; le contrôleur la vérifie avec une clé publique sans connexion réseau. Le problème : l'absence de synchronisation entre contrôleurs. Une capture d'écran du billet peut être utilisée à chaque portillon indépendamment.
État côté serveur et mises à jour atomiques
Un système de stockage centralisé résout le problème de synchronisation. Chaque scan est une requête au serveur avec une mise à jour atomique de l'état.
Exemple en PostgreSQL :
UPDATE tickets
SET status = 'used', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
Si une ligne est retournée, le billet est validé et l'entrée est accordée. Si aucune ligne n'est retournée, le billet est déjà utilisé ou n'existe pas.
Un problème classique : la perte de la réponse du serveur. Un contrôleur dans une zone à faible signal envoie une requête, le serveur met à jour la base de données, mais la réponse n'arrive pas. Un délai d'attente se produit, une nouvelle tentative de scan est effectuée—le serveur la refuse, et le client ne peut pas entrer malgré un billet payé.
Idempotence au niveau de l'API
La solution est des requêtes idempotentes avec une clé unique (UUID), générée lors de l'ouverture d'une session de scan. La clé enregistre la tentative, permettant des nouvelles tentatives sans duplication.
Logique dans une transaction (read committed) :
begin ;
-- 1. Enregistrer la tentative
INSERT INTO processed_requests (ticket_id, idempotency_key)
VALUES (:ticket_id, :idempotency_key)
ON CONFLICT (ticket_id, idempotency_key)
DO NOTHING RETURNING *;
-- 2. Si rows_affected == 0 : rollback, retourner SUCCÈS
-- 3. Valider le billet
UPDATE tickets
SET status = 'used', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
-- 4. Si rows_affected == 0 : rollback, retourner ERREUR
commit;
-- 5. Retourner SUCCÈS
La transaction assure l'atomicité : soit la clé et l'état sont mis à jour, soit rien ne l'est. Un index unique sur (ticket_id, idempotency_key) empêche les conditions de concurrence—la deuxième requête attend ou passe à DO NOTHING.
Une nouvelle tentative dans la même session retourne le succès, même si la première réponse a été perdue.
Blocage en tête de file et perte de contexte
Un problème réel : un contrôleur sous pression d'une file d'attente ferme la session sans réponse. Un nouveau scan génère une nouvelle idempotency_key. Le serveur voit un billet utilisé avec une clé différente—refus.
Le contrôleur ne peut pas distinguer un client légitime d'un fraudeur avec une capture d'écran. Le blocage en tête de file nécessite une réponse finale avant le billet suivant.
Un compromis : retourner l'horodatage de la dernière utilisation. Le contrôleur évalue : "il y a 2 minutes—probablement la même personne." Risque : un groupe avec un code QR partagé passe séquentiellement.
Défis clés :
- Délais d'attente réseau sans perte d'état
- Synchronisation de multiples contrôleurs
- Facteurs humains dans les files d'attente
- Équilibre entre fiabilité et expérience utilisateur
Points clés à retenir
- La mise à jour atomique UPDATE avec une condition status='active' empêche la double validation
- L'idempotence via UUID + index unique garantit des nouvelles tentatives sans duplication
- Les transactions (read committed) assurent une visibilité séquentielle des changements
- Le blocage en tête de file est inévitable pour une fiabilité à 100 % sans biométrie
- Le compromis de l'horodatage déplace la décision vers l'opérateur
Améliorations futures
Les codes QR statiques deviennent obsolètes. Les jetons dynamiques ou le challenge-response avec OTP à usage unique résolvent le problème. Le contrôleur demande un nonce au serveur, le client le signe—le serveur vérifie et le marque atomiquement.
La biométrie (reconnaissance faciale) ou NFC élimine les captures d'écran mais nécessite du matériel. QUIC minimise la perte de paquets mais ne résout pas la perte de contexte.
Mise à l'échelle : Redis pour mettre en cache les idempotency_keys, partitionnement par ticket_id. Surveillance : métriques pour les scans échoués par horodatage, tests A/B des compromis.
— Editorial Team
Aucun commentaire pour le moment.