콘서트, 극장, 이벤트 예매 시스템에서 흔히 발생하는 이슈는 다음과 같다:
이러한 상황에서는 단순히 DB 트랜잭션으로는 부족하다. 예약 시도 자체를 사전에 차단할 수 있어야 한다. 이를 위해 우리는 Redis + Lua를 사용하여 같은 유저가 같은 스케줄에 대해 10초에 한 번만 예약 시도할 수 있도록 비즈니스 제약을 걸어보았다.
ratelimit:user:{userId}:schedule:{scheduleId}
키를 구성한다.이 방식의 핵심은 예약 전에 이미 제약 조건을 Redis에서 빠르게 판단할 수 있다는 점이다. 또한 key 존재 여부 확인 → SET + TTL 설정까지를 한 번의 명령으로 묶어 race condition도 제거할 수 있다.
-- KEYS[1] = 예약 제약 키
-- ARGV[1] = 현재 시간 (timestamp)
-- ARGV[2] = 제한 시간 TTL (ms)
local key = KEYS[1]
local exists = redis.call('EXISTS', key)
if exists == 1 then
return 0
else
redis.call('SET', key, '1', 'PX', ARGV[2])
return 1
end
EXISTS
는 키 존재 여부를 체크SET
+ PX
를 이용해 제한 키를 저장 (PX = milliseconds)import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
const payload = JSON.stringify({ scheduleId: 1001, seatNumber: 'A3' });
const headers = { 'Content-Type': 'application/json', 'x-user-id': '1' };
const res = http.post('http://localhost:8080/api/v4/reservations', payload, { headers });
console.log(`응답 코드: ${res.status}`);
sleep(5);
}
200 OK
429 Too Many Requests
127.0.0.1:6379> KEYS ratelimit:user:*:schedule:*
"ratelimit:user:1:schedule:1001"
TTL 확인:
127.0.0.1:6379> TTL ratelimit:user:1:schedule:1001
(integer) 7
이렇게 Redis에 저장된 키가 10초 후 자동 삭제되기 때문에, 이후에는 재예약 시도가 가능해진다.