배경 및 목표
공채 시즌에는 수백 명이 동시에 지원합니다. 수험번호는 공고 단위로 유니크해야 하는데, “현재 최대값 조회 → +1 → 저장” 과정의 시간 간격에 다른 요청이 끼어들면 같은 번호가 배정됩니다.
sequenceDiagram participant A as 지원자 A participant B as 지원자 B participant DB as Database A->>DB: 최대 수험번호 조회 (100) B->>DB: 최대 수험번호 조회 (100) A->>A: 다음 번호 = 101 B->>B: 다음 번호 = 101 A->>DB: 101 배정 ✅ B->>DB: 101 배정 ❌ 중복!
목표
- 공채 시즌 동시 지원에서도 공고 단위 수험번호를 100% 유니크하게 배정한다.
- DB 커넥션 장기 점유·데드락 없이 낮은 지연으로 처리한다.
해결 방법과 해결 후보군
1. DB 락 대신 Redis를 선택한 이유
| 방식 | 문제점 |
|---|---|
| 비관적 락 (SELECT FOR UPDATE) | DB 커넥션 오래 점유, 데드락 위험 |
| 낙관적 락 (Version) | 공채 시즌 동시 요청 많으면 재시도 빈번 |
| Redis 분산락 + INCR | ms 단위 락, 원자적 증가, TTL 데드락 방지 |
Redis는 인메모리 기반으로 ms 단위 락 획득/해제가 가능하고, INCR이 원자적이므로 별도 재시도 없이 유니크를 보장합니다.
2. SET NX + INCR 원자적 연산
SET lock:target:123 "holder" NX EX 10 # 분산락 획득 (TTL 10초)
INCR sequence:target:123 # 원자적 증가“조회 → 계산 → 저장” 3단계가 INCR 1단계로 줄어들어 경합 자체가 사라집니다. TTL을 설정하여 네트워크 장애 시에도 락이 자동 해제되어 데드락을 방지합니다.
sequenceDiagram participant E as 지원 엔드포인트 participant P as 수험번호 처리기 participant R as Redis participant DB as Database E->>P: 지원 내부 이벤트 P->>R: SET key value NX (락 획득) alt 락 성공 R-->>P: OK P->>R: INCR (원자적 증가) R-->>P: 101 P->>DB: 수험번호 101 배정 P->>R: 락 해제 else 락 실패 R-->>P: NULL P->>P: 대기 후 재시도 end
결과
| 지표 | 결과 |
|---|---|
| 유니크 보장 | 100% (100건/초) |
| 락 획득/해제 | 1~2ms |
| DB 락 대비 | 10배 빠름 |
| 데드락 위험 | TTL로 방지 |
모니터링
- 수험번호 중복 발생 건수(0 유지)를 관측한다.
- Redis 락 획득/해제 지연(p99)과 락 실패 재시도 횟수를 관측한다.