DataBase
25.04.16 Redis 에서 루아(Lua) 스크립트란?
ddong-kka
2025. 4. 16. 01:22
개요
리뷰의 평점 기능을 구현하는 도중 유저들이 동시에 리뷰를 작성하거나 평점을 수정할 경우,
평균 = 총합 / 리뷰 수 계산 과정에서 연산 충돌이 발생할 수 있다는 생각이 들었다.
- user A 와 userB가 동시에 평점을 등록함
- 둘 다 동일한 시점의 총점, 리뷰 수 등을 읽음
- 각각 계산된 평균을 저장하려고 시도한다.
- 마지막에 저장된 한쪽의 값만 반영되는 문제가 발생
- 실제 평균과 다른 값이 저장되어 데이터 정합성이 깨지게된다.
이런 경우 Lua 스크립트를 사용하면 문제를 해결할 수 있다.
Redis는 Lua를 통해 여러 명령을 원자적(atomic) 으로 실행할 수 있다.
즉, 총합 계산, 리뷰 수 증가, 평균 계산을 하나의 트랜잭션으로 묶어 실행하는 것이다.
Redis 루아(Lua) 스크립트란?
Redis 내부에서 Lua 언어로 작성된 코드를 실행할 수 있는 기능이다.
여러 Redis 명령어를 하나의 스크립트로 묶어 원자적으로 처리할 수 있도록 하는 기능이다.
특징
- Lua는 경량 스크립트 언어이다.
- Redis 내부 서버에서 실행된다. 즉, 서버 왕복이 없다
- 스크립트 전체가 트랜잭션처럼 모두 실행되거나, 아예 실행되지 않는다.
- 클라이언트와의 왕복 없이 Redis 내부에서 처리되므로 빠르다.
- 원자성이 보장된다. 실행 도중이 끼어들기가 불가능
그럼 왜 Lua 스크립트를 쓰지?
Redis는 기본적으로 단일 명령어만 원자적이다.
하지만 평점처럼 여러 명령어가 필요한 로직은 아래 문제가 발생할 수 있다
클라이언트가 명령어를 여러 번 보내면, 그 사이 다른 클라이언트가 끼어들어 값이 꼬이게된다.
결국 평균 값 계산이 잘못된다.
목적 | 설명 |
원자성 보장 | 여러 명령어를 하나로 묶어 안전하게 실행할 수 있다. |
조건문/반복문 처리 | 단일 명령어로는 안 되는 로직을 구현 가능 |
성능 최적화 | 네트워크 왕복 없이 Redis 서버 내에서 연산 |
동시성 제어 | 단일 스레드 특성과 결합해 안전한 병렬 처리 가능 |
Lua 스크립트 기본 문법
문법 예시
redis.call('SET','key','value') -- 명령어 실행
redis.call('INCRBY', KEYS[1], ARGV[1]) -- 키와 인자 전달
실행 예시 (CLI)
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
- "___" : Lua 스크립트 본문 내용을 따옴표로 감싸 작성한다.
- 1 : 키의 개수
- mykey : keys[1]에 전달
- myvalue : ARGV[1]에 전달
평점 등록 예시 코드
-- KEYS[1]: 총합 key
-- KEYS[2]: 개수 key
-- KEYS[3]: 평균 key
-- ARGV[1]: 새 점수
local total = redis.call('INCRBY', KEYS[1], ARGV[1])
local count = redis.call('INCR', KEYS[2])
local avg = total / count
redis.call('SET', KEYS[3], avg)
return avg
리뷰 평점 등록 시, 총점 / 개수 / 평균을 안전하게 갱신하는 스크립트이다.
- KEYS[1] : "rating:total"
- 총점이 저장된 키
- KEYS[2] : "rating:count
- 평점 준 사용자 수
- KEYS[3]: "rating:avg"
- 평균 점수를 저장한 키
- ARGV[1]
- 사용자가 이번에 입력한 새로운 점수
KEYS 는 Redis의 키 이름들이고 ARGB는 일반 인자 값을 의미한다.
요약
Redis 내부에서 실행되는 작은 코드 조각을 Lua 언어로 작성하는 것이 Lua 스크립트이다.
EVAL 명령으로 실행된다.
한 줄 한 줄씩 인터프리터로 로 실행되는 Redis 명령어를 하나의 트랜잭션처럼 묶어서 원자적으로 실행한다.
동시성 문제 해결, 성능 최적화, 복잡한 연산처리에 유용하다.