[학습] Lua 스크립트
프로젝트에 선차감 방식을 도입하려고 합니다. 이 방식은 요청이 들어왔을 때 미리 자원을 차감하고, 대기 큐로 메시지를 전달할 수 있기 때문에 효율적인 흐름 제어가 가능합니다.
하지만 선차감은 동기 방식으로 작동한다는 점이 고민입니다. 동기 방식 자체가 문제라기보다는, 이를 레디슨(Redisson) 분산 락과 함께 사용할 때 병목이 생길 수 있다는 점이 우려됩니다.
레디슨은 분산 환경에서 락을 걸기 위한 네트워크 통신이 포함되어 있고, 여기에 동기 코드가 포함되면, 락 획득까지의 대기 시간이 누적될 수 있습니다.
즉, 락을 기다리는 동안 동기 코드가 블로킹되면 전체 처리량이 감소할 수 있는 것이죠.
그렇다고 선차감 로직을 비동기로 전환하면, 락 대기 시간은 줄어들 수 있지만, 정합성 문제가 발생하게 됩니다.
실제로 테스트 결과, 10,000건의 요청 중 약 6,000~8,000건만 성공적으로 처리되었고, 이는 비동기 처리로 인해 경합이나 순서 보장 실패가 원인일 수 있습니다.
따라서 현재 상황은 다음과 같은 딜레마로 요약할 수 있습니다:
- 동기 방식: 정합성은 유지되지만 성능 저하
- 비동기 방식: 성능은 나쁘지 않지만 정합성 문제 발생
루아 스크립트를 활용한 선차감 처리
이러한 문제들을 해결하기 위해 Lua 스크립트를 Redis에 적용하는 방식이 유력한 대안으로 떠올랐습니다.
Lua 스크립트를 사용하면 다음과 같은 장점이 있습니다:
- 선차감 방식을 Redis 내부에서 직접 구현할 수 있어, 요청이 들어오자마자 자원을 빠르게 차감하고 메시지를 전달할 수 있습니다.
- Redis는 Lua 스크립트를 단일 명령처럼 실행하기 때문에, 원자성(atomicity)이 자동으로 보장됩니다. 이로 인해 RDB 기반 동기 처리에서 얻을 수 있는 정합성도 확보할 수 있습니다.
- 처리 속도 또한 매우 빠릅니다. Lua 스크립트는 Redis 엔진 내에서 실행되기 때문에, 네트워크 지연이나 분산락 대기 없이 고속 처리가 가능합니다.
즉, 정합성, 성능, 응답속도라는 세 가지 요구사항을 모두 충족시킬 수 있는 해결책으로 Lua 스크립트가 적합하다고 판단됩니다.
간단하게 루아스크립트의 템플릿을 학습해봅시다.
-- KEYS[1] = 재고 키
-- ARGV[1] = 차감할 수량
local stock = tonumber(redis.call("GET", KEYS[1]))
if stock == nil then
return -1 -- 재고 없음
end
if stock < tonumber(ARGV[1]) then
return 0 -- 재고 부족
end
redis.call("DECRBY", KEYS[1], ARGV[1])
return 1 -- 차감 성공
뭔지는 잘모르겠지만 1씩 감소시키라는거 같습니다.
gpt는 어떤식으로 답변할까? 다음과 같다. 위는 내글을 gpt가 수정한 글이다.
Redis에서 Lua 스크립트를 사용하는 이유
Redis는 기본적으로 단일 명령에 대해 빠르고 간단한 처리를 제공하지만, 복잡한 조건이나 여러 명령이 함께 실행되어야 하는 경우에는 원자성 보장이 어렵다. 이때 Redis의 Lua 스크립트 기능을 활용하면, 다음과 같은 이유로 보다 정교하고 안정적인 처리가 가능하다.
1. 원자성 보장 (Atomicity)
Lua 스크립트는 Redis 내부에서 단일 트랜잭션처럼 실행되며, 중간에 다른 명령이 끼어들 수 없다. 이는 데이터 정합성이 중요한 상황에서 경쟁 조건을 방지하고, 동시성 문제 없이 안전하게 데이터를 조작할 수 있게 해준다.
Redis 공식 문서에 따르면, Lua 스크립트는 하나의 명령처럼 실행되어 원자성을 보장한다고 명시되어 있다.
2. 네트워크 왕복(Round Trip) 최소화
여러 Redis 명령을 클라이언트에서 순차적으로 보내게 되면, 명령마다 네트워크 왕복 시간이 발생한다. 반면 Lua 스크립트를 사용하면 여러 명령을 하나의 EVAL 호출로 처리할 수 있어 네트워크 지연을 크게 줄일 수 있다.
예를 들어, 5개의 Redis 명령을 개별적으로 실행하면 5번의 왕복(RTT)이 발생하지만, Lua 스크립트로 묶으면 1회만 통신하면 된다.
3. 서버 내부 실행으로 인한 고속 처리
Lua 스크립트는 Redis 서버 내부에서 실행되므로, 별도의 외부 시스템과의 통신 없이 처리된다. 이로 인해 메모리 기반의 빠른 응답 속도를 그대로 유지하면서 복잡한 로직을 처리할 수 있다.
특히, 분산락이나 RDB 접근을 수반하는 방식보다 훨씬 짧은 지연 시간 내에 응답이 가능하다.
4. 복잡한 로직의 단일 스크립트 처리
Lua는 조건문, 반복문, 연산 등을 포함할 수 있어, 단순한 키-값 연산 이상의 로직도 Redis 내부에서 처리할 수 있다. 이를 통해 다음과 같은 처리가 가능하다.
- 특정 조건을 만족할 때만 데이터 갱신
- 재고 선차감과 대기 큐 메시지 전송을 동시에 수행
- TTL 설정 및 특정 시간 조건 검증 로직 포함
5. 정합성과 성능의 균형 확보
전통적으로 RDB 기반의 선차감은 정합성은 확보되지만 처리 속도가 느리고, 반대로 Redis 단일 명령어를 사용하는 방식은 빠르지만 정합성 보장이 어렵다.
Lua 스크립트는 이 둘 사이의 균형을 맞춰준다. Redis의 빠른 처리 특성을 유지하면서도, 여러 검증 로직을 포함한 정합성 있는 처리를 가능하게 한다.
결론
Redis의 Lua 스크립트는 정합성, 성능, 처리 효율이라는 세 가지 핵심 요구사항을 모두 만족할 수 있는 방법이다. 복잡한 조건을 포함한 고속 처리가 필요한 상황에서, Lua 스크립트는 단순한 Redis 명령어 조합 이상의 유용한 도구가 된다.