홈으로 돌아가기

영화관을 위한 QR 티켓 검증 설계

이 글은 영화관을 위한 QR 티켓 검증 시스템 설계를 분석합니다. 오프라인 서명부터 멱등 API와 PostgreSQL 트랜잭션까지. 타임아웃 문제, HoL 블로킹, 그리고 타협점 논의.

영화관에서 안정적인 티켓 검증 설계 방법
Advertisement 728x90

QR 티켓 검증 시스템 설계: 오프라인에서 멱등성까지

티켓 검사원이 QR 코드를 스캔하면 시스템이 데이터베이스에서 확인하고 사용 완료로 표시합니다. 핵심 요구사항: 한 장의 티켓, 한 번의 입장. 겉보기엔 간단한 작업이 네트워크 장애, 대기열, 여러 검사원으로 인해 복잡해집니다. 오프라인 검증부터 안정적인 서버 측 처리까지 솔루션의 진화를 살펴보겠습니다.

오프라인 모드는 디지털 서명(ECC 또는 RSA)을 사용합니다. QR 코드에는 티켓 데이터와 서명이 포함되어 있으며, 검사원은 네트워크 연결 없이 공개 키로 이를 검증합니다. 문제점: 검사원 간 동기화 부재. 티켓의 스크린샷을 각 게이트에서 독립적으로 사용할 수 있습니다.

서버 측 상태와 원자적 업데이트

중앙 집중식 저장 시스템이 동기화 문제를 해결합니다. 각 스캔은 원자적 상태 업데이트를 위한 서버 요청입니다.

Google AdInline article slot

PostgreSQL 예시:

UPDATE tickets 
SET status = 'used', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;

행이 반환되면 티켓이 사용 완료되고 입장이 허용됩니다. 행이 반환되지 않으면 티켓이 이미 사용되었거나 존재하지 않습니다.

고전적인 문제: 서버 응답 손실. 신호가 약한 지역의 검사원이 요청을 보내면, 서버가 데이터베이스를 업데이트하지만 응답이 도착하지 않습니다. 타임아웃이 발생하고 재스캔을 시도하면—서버가 거부하고, 클라이언트는 유료 티켓을 가지고도 입장할 수 없습니다.

Google AdInline article slot

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으로 이동합니다.

Google AdInline article slot

동일 세션 내 재시도는 첫 번째 응답이 손실되었더라도 성공을 반환합니다.

선두 차단과 컨텍스트 손실

실제 문제: 대기열 압박을 받는 검사원이 응답 없이 세션을 닫습니다. 새로운 스캔은 새로운 idempotency_key를 생성합니다. 서버는 사용된 티켓과 다른 키를 보고—거부합니다.

검사원은 합법적인 고객과 스크린샷을 가진 사기꾼을 구별할 수 없습니다. 선두 차단은 다음 티켓 전에 최종 응답이 필요합니다.

타협점: 마지막 사용 시간을 반환합니다. 검사원이 평가합니다: "2분 전—아마 같은 사람일 것입니다." 위험: 공유 QR 코드를 가진 그룹이 순차적으로 통과합니다.

주요 과제:

  • 상태 손실 없는 네트워크 타임아웃
  • 여러 검사원의 동기화
  • 대기열에서의 인간적 요소
  • 신뢰성과 UX의 균형

핵심 요약

  • status='active' 조건을 가진 원자적 UPDATE는 이중 사용을 방지합니다
  • UUID + 고유 인덱스를 통한 멱등성은 중복 없이 재시도를 보장합니다
  • 트랜잭션(읽기 커밋됨)은 변경 사항의 순차적 가시성을 보장합니다
  • 생체 인증 없이 100% 신뢰성을 위해서는 선두 차단이 불가피합니다
  • 시간 기록 타협은 결정을 운영자에게 이전합니다

향후 개선 사항

정적 QR 코드는 구식이 되어가고 있습니다. 동적 토큰 또는 일회용 OTP를 사용한 챌린지-응답이 문제를 해결합니다. 검사원이 서버에서 논스를 요청하면, 클라이언트가 서명하고—서버가 검증하고 원자적으로 표시합니다.

생체 인증(얼굴 ID) 또는 NFC는 스크린샷을 제거하지만 하드웨어가 필요합니다. QUIC는 패킷 손실을 최소화하지만 컨텍스트 손실을 해결하지는 않습니다.

확장: idempotency_keys 캐싱을 위한 Redis, ticket_id별 샤딩. 모니터링: 시간 기록별 실패 스캔 메트릭, 타협점의 A/B 테스트.

— Editorial Team

Advertisement 728x90

다음 읽기