Database/Redis

[실전 레디스] 3-2. 고급 기능: 루아

noahkim_ 2025. 3. 21. 19:42

하야시 쇼고 님의 "실전 레디스" 책을 정리한 포스팅 입니다.


1. 파이프라인

  • 이전 응답을 기다리지 않고 여러 요청을 한꺼번에 서버에 보내는 방식
항목 설명
사용 목적
성능 최적화
대체 가능 기능 MGET, MSET과 유사 (단, 파이프라인이 더 범용적)
장점 - 실행 순서 보장
- RTT(Round Trip Time) 절약
단점
- 조건 분기 불가
- 복잡한 로직 작성 어려움
- 원자성 보장 안 됨

 

예시

더보기
import redis

# Redis 클라이언트 연결
client = redis.StrictRedis(host='localhost', port=6379, db=0)

# 파이프라인 시작
pipe = client.pipeline()

# 여러 명령어를 파이프라인에 추가
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.set('key3', 'value3')
pipe.get('key1')
pipe.get('key2')

# 명령어를 한 번에 실행하고 응답 받기
responses = pipe.execute()

# 응답 출력
print(responses)  # 응답: [True, True, True, 'value1', 'value2']

 

2. 루아

  • 레디스에 내장된 스크립트 언어

 

기능

기능/특성 설명
원자성 보장
모든 명령은 하나의 명령처럼 실행됨 (중간에 실패 시 전체 롤백 가능)
복잡한 로직
if, for 사용 가능
읽기 작업 비동기 가능
읽기 작업은 트랜잭션 외로 비동기 처리가 가능 (보통은 call로 동기 실행)
샌드박스 환경 호스트 시스템 접근 제한 (전역 변수 사용 제한됨)
표준 라이브러리
base, table, string, math, struct, cjson, bitop 등
복제 및 영속화
스크립트 실행 결과는 마스터 → 레플리카 복제, RDB/AOF에 포함됨

 

이페머럴 스크립트

  • 한 번 실행하고 사라지는 임시 Lua 스크립트
명령어 설명
EVAL
Lua 스크립트 직접 실행 (eval "..." numkeys key1 ... argv1 ...)
EVALSHA
SHA1 해시값을 통해 이미 로드된 스크립트 실행
SCRIPT LOAD
Lua 스크립트를 Redis에 로드하고 SHA1 해시 반환
SCRIPT FLUSH
Redis 인스턴스에 저장된 모든 스크립트 삭제
- 메모리 사용량을 줄이기 위해 삭제
- eval이나 evalsha로 실행한 스크립트는 레디스 인스턴스에 남아있음
SCRIPT KILL
실행 중인 스크립트 강제 종료
- busy-replay-threshold: 타임아웃 설정

 

예제) eval

더보기
eval "local val = 0; for i = 1, ARGV[1] do val = val + i; end; return val" 0 10
  • 1~ARGV[1]까지 루프 반복
  • 각 반복마다 i값 누적

 

예제) evalsha

더보기
evalsha "5b6234158...." 2 key1 key2 value1 value2 value3
  • 실행할 스크립트 파일의 해시값을 인자로 전달하여 실행함

 

예제) script load

더보기
script load "파일명"
  • 특정 스크립트 파일을 sha1로 해시화하고 저장함
  • 해시값 출력됨

 

예제) 반복문

더보기
require 'redis'

script = <<EOF
  local counts = {}
  
  for i, key in ipairs(KEYS) do
    counts[i] = redis.call('SCARD', key)
  end
  
  return counts  
EOF

redis = Redis.new
hashed_script=redis.script(:load, script)
redis.evalsha(hashed_script, keys: ['user:1:votes', 'user:2:votes', 'user:3:votes'])

puts result

 

예제) TTL 설정

더보기
require 'redis'
script = <<EOF
  redis.call('SET', KEYS[1], ARGV[1])
  redis.call('EXPIRE', KEYS[1], ARGV[2])  
EOF

redis = Redis.new
hashed_script=redis.script(:load, script)
redis.evalsha(hashed_script, keys: ['user:1'], argv: ['hi', 30])
  • 기본적으로 TTL 설정을 옵션으로 쓰려면, SET 명령어와만 함께 쓸 수 있음
  • 데이터를 저장한 후, TTL 명령어로 설정해야 함
  • 이러할 경우 RTT 오버헤드 발생

 

플래그
  • 스크랩트 내에서 루아가 어떻게 동작할지 미리 알 수 없음
  • 플래그를 통해 사전에 어떻게 동작할 지 전달하여 레디스를 제어함
플래그명 의미
no-writes
해당 함수는 읽기 전용임을 선언 (쓰기 금지)
allow-oom
OOM 상황에서도 실행 가능
allow-state
오래된 상태에서도 실행 가능
no-cluster
클러스터 환경에서 실행 금지
allow-cross-slot-keys
클러스터 환경에서 다중 슬롯 키 접근 허용

 

레디스 함수

  • 이페머럴 스크립트를 모듈화

 

이페머럴 스크립트의 문제점
문제점 설명
SHA1 기반
해시값 기반 실행으로 디버깅 어려움
재사용 불가
다른 스크립트에서 호출 불가 (모듈화 어려움)
버전 제한
Lua 5.1에 고정, 최신 Lua 기능 사용 불가

 

특징
  • 재사용성
  • 영속성: 복제, 스냅숏, AOF 등

 

예제) 함수 등록

더보기
function load "#!lua name=mylib
redis.register_function('calculator', 
function(keys, args)
  local val = 0
  
  for i = 1, args[1] do
    val = val + i
  end

  return val
end
"
  • mylib 이라는 라이브러리에 함수를 등록

 

fcall calculator 0 10
function delete mylib

 

예시) 함수 실행 정지

더보기
function kill
shutdown nosave

 

예제) 플래그

더보기
#!lua name=mylib

local function sum(keys, args) 
...

redis.register_function(
  function_name='calculator',
  callback=sum,
  flags={'no-writes'}
)

 

루아 프로그래밍

  • 루아 스크립트 기반
  • 구조화, 관리, 제어가 발전함

 

예외 처리
방식 설명
redis.call(...)
오류 발생 시 스크립트 즉시 중단
redis.pcall(...)
오류 발생 시 중단하지 않고 오류 객체 반환

 

예제) redis.call()

더보기
redis.call("SET", "key", "value")
redis.breakpoint()  -- 여기서 실행이 중단됨
local value = redis.call("GET", "key")
return value
redis.debug("This is a debug message!")
local val = redis.call("GET", "key")
redis.debug("Fetched value:", val)
return val

 

예제) redis.pcall()

더보기
local result = redis.pcall("INCR", "key")  -- key가 숫자가 아니면 오류 발생
if type(result) == "table" and result.err then
    return "Error: " .. result.err
end
return result
  • 오류 발생 시 스크립트가 중단되지 않고, 오류 객체를 반환함

 

예제) redis.sha1hex()

더보기
local hash = redis.sha1hex("Hello, Redis!")
return hash  -- "09f7e02f1290be211da707a266f153b3b10e6932"