QR 티켓 검증 시스템 설계: 오프라인에서 멱등성까지
티켓 검사원이 QR 코드를 스캔하면 시스템이 데이터베이스에서 확인하고 사용 완료로 표시합니다. 핵심 요구사항: 한 장의 티켓, 한 번의 입장. 겉보기엔 간단한 작업이 네트워크 장애, 대기열, 여러 검사원으로 인해 복잡해집니다. 오프라인 검증부터 안정적인 서버 측 처리까지 솔루션의 진화를 살펴보겠습니다.
오프라인 모드는 디지털 서명(ECC 또는 RSA)을 사용합니다. QR 코드에는 티켓 데이터와 서명이 포함되어 있으며, 검사원은 네트워크 연결 없이 공개 키로 이를 검증합니다. 문제점: 검사원 간 동기화 부재. 티켓의 스크린샷을 각 게이트에서 독립적으로 사용할 수 있습니다.
서버 측 상태와 원자적 업데이트
중앙 집중식 저장 시스템이 동기화 문제를 해결합니다. 각 스캔은 원자적 상태 업데이트를 위한 서버 요청입니다.
PostgreSQL 예시:
UPDATE tickets
SET status = 'used', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
행이 반환되면 티켓이 사용 완료되고 입장이 허용됩니다. 행이 반환되지 않으면 티켓이 이미 사용되었거나 존재하지 않습니다.
고전적인 문제: 서버 응답 손실. 신호가 약한 지역의 검사원이 요청을 보내면, 서버가 데이터베이스를 업데이트하지만 응답이 도착하지 않습니다. 타임아웃이 발생하고 재스캔을 시도하면—서버가 거부하고, 클라이언트는 유료 티켓을 가지고도 입장할 수 없습니다.
API 수준의 멱등성
해결책은 스캔 세션을 열 때 생성된 고유 키(UUID)를 사용한 멱등 요청입니다. 키는 시도를 기록하여 중복 없이 재시도를 허용합니다.
트랜잭션 내 논리(읽기 커밋됨):
begin ;
-- 1. 시도 기록
INSERT INTO processed_requests (ticket_id, idempotency_key)
VALUES (:ticket_id, :idempotency_key)
ON CONFLICT (ticket_id, idempotency_key)
DO NOTHING RETURNING *;
-- 2. rows_affected == 0이면: 롤백, SUCCESS 반환
-- 3. 티켓 사용 완료
UPDATE tickets
SET status = 'used', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;
-- 4. rows_affected == 0이면: 롤백, ERROR 반환
commit;
-- 5. SUCCESS 반환
트랜잭션은 원자성을 보장합니다: 키와 상태가 업데이트되거나 아무 일도 일어나지 않습니다. (ticket_id, idempotency_key)에 대한 고유 인덱스는 경쟁 조건을 방지합니다—두 번째 요청은 대기하거나 DO NOTHING으로 이동합니다.
동일 세션 내 재시도는 첫 번째 응답이 손실되었더라도 성공을 반환합니다.
선두 차단과 컨텍스트 손실
실제 문제: 대기열 압박을 받는 검사원이 응답 없이 세션을 닫습니다. 새로운 스캔은 새로운 idempotency_key를 생성합니다. 서버는 사용된 티켓과 다른 키를 보고—거부합니다.
검사원은 합법적인 고객과 스크린샷을 가진 사기꾼을 구별할 수 없습니다. 선두 차단은 다음 티켓 전에 최종 응답이 필요합니다.
타협점: 마지막 사용 시간을 반환합니다. 검사원이 평가합니다: "2분 전—아마 같은 사람일 것입니다." 위험: 공유 QR 코드를 가진 그룹이 순차적으로 통과합니다.
주요 과제:
- 상태 손실 없는 네트워크 타임아웃
- 여러 검사원의 동기화
- 대기열에서의 인간적 요소
- 신뢰성과 UX의 균형
핵심 요약
- status='active' 조건을 가진 원자적 UPDATE는 이중 사용을 방지합니다
- UUID + 고유 인덱스를 통한 멱등성은 중복 없이 재시도를 보장합니다
- 트랜잭션(읽기 커밋됨)은 변경 사항의 순차적 가시성을 보장합니다
- 생체 인증 없이 100% 신뢰성을 위해서는 선두 차단이 불가피합니다
- 시간 기록 타협은 결정을 운영자에게 이전합니다
향후 개선 사항
정적 QR 코드는 구식이 되어가고 있습니다. 동적 토큰 또는 일회용 OTP를 사용한 챌린지-응답이 문제를 해결합니다. 검사원이 서버에서 논스를 요청하면, 클라이언트가 서명하고—서버가 검증하고 원자적으로 표시합니다.
생체 인증(얼굴 ID) 또는 NFC는 스크린샷을 제거하지만 하드웨어가 필요합니다. QUIC는 패킷 손실을 최소화하지만 컨텍스트 손실을 해결하지는 않습니다.
확장: idempotency_keys 캐싱을 위한 Redis, ticket_id별 샤딩. 모니터링: 시간 기록별 실패 스캔 메트릭, 타협점의 A/B 테스트.
— Editorial Team
아직 댓글이 없습니다.