Redis 카운터 경쟁 조건 해결: Lua 스크립팅으로 원자성 확보

Redis 카운터의 경쟁 조건 해결: Lua 스크립팅으로 원자성과 신뢰성 확보

카테고리

프로그래밍/소프트웨어 개발

서브카테고리

DevOps

대상자

  • Redis를 활용한 레이트 제한, 로그인 제한 등의 시스템 개발자
  • 중간 난이도: Redis 기본 명령어(INCR, EXPIRE)와 Lua 스크립팅 이해 필요

핵심 요약

  • 경쟁 조건(Race Condition)은 병렬 요청 시 INCREXPIRE 명령을 분리하여 처리할 경우 발생
  • Redis 트랜잭션(MULTI/EXEC)은 명령 순서 보장은 하지만 원자성(Atomicity) 확보 불가
  • Lua 스크립팅을 사용하면 Redis 서버 내에서 단일 원자적 연산(Single Atomic Operation) 수행 가능

섹션별 세부 요약

1. 경쟁 조건 발생 원인

  • INCREXPIRE 명령 분리 시, 병렬 요청으로 인한 TTL 초기화 오류 발생
  • 예: 두 요청이 동시에 INCR 실행 후, EXPIRE 명령이 마지막 요청에만 적용되어 타임아웃 지연
  • TTL 초기화카운터 증가가 동시에 실행되는 경우, 불확실한 상태 유발

2. Redis 트랜잭션의 한계

  • MULTI/EXEC 명령은 명령 그룹화는 가능하지만, 조건부 실행(if count == 1) 지원 불가
  • INCREXPIRE 사이에 다른 클라이언트의 요청이 삽입될 수 있음
  • 원자성(Atomicity)은 보장하지 않음

3. Lua 스크립팅으로 경쟁 조건 해결

  • Lua 스크립트는 Redis 서버 내에서 단일 원자적 연산(Single Atomic Operation) 수행
  • INCREXPIRE 명령을 Lua 스크립트 내부에서 조건부 실행 가능
  • KEYS[1]: 대상 키, ARGV[1]: 윈도우 시간 전달 가능
  • 예:
  • local count = redis.call("INCR", KEYS[1])
    if count == 1 then
      redis.call("EXPIRE", KEYS[1], ARGV[1])
    end
    return count

4. ioredis를 통한 Lua 스크립팅 구현

  • eval 명령으로 Lua 스크립트 실행
  • KEYS[1]: 대상 키, ARGV[1]: 윈도우 시간 전달
  • 예:
  • const count = unwrap(
      await redis.fire("eval", luaScript, 1, failureKey, WINDOW_SEC),
    ) as number;

결론

  • Lua 스크립팅을 사용하면 Redis 카운터의 원자성(Atomicity)과 신뢰성(Reliability) 확보 가능
  • ioredis와 같은 라이브러리를 통해 Lua 스크립트 실행 가능
  • 경쟁 조건(Race Condition)을 피하고 네트워크 요청 횟수 줄이기로직 복잡성 이전에 효과적
  • 참고 툴: express-admin-honeypot (Redis 기반 보안 툴)