백엔드 데이터 인프라 61편. Redis Functions (Redis 7+) — Lua scripting 의 후속. Library·FCALL·FCALL_RO·FUNCTION LOAD 흐름, 영속·이름 기반·replica 자동 복제 같은 개선점, 기존 EVAL 에서 Functions 로 마이그레이션 패턴까지 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 130편 중 61편이에요. 60편 에서 Lua scripting (Redis 내장 스크립트 언어) 으로 진짜 원자적 트랜잭션 을 푸는 방법을 봤다면, 그 Lua scripting 의 후속 이 Redis Functions (Redis 7+). Part 4-3 상호작용·프로그래밍 의 마지막 글이에요.
Redis Functions가 어렵게 느껴지는 이유
이름이 Functions 라 "기존 Lua script 와 뭐가 다른가" 가 처음 안 잡혀요.
첫 번째 걸림돌은 Library 라는 새 개념 이에요. Functions 는 하나씩 등록하는 게 아니라 Library 단위 로 묶어 등록해요. 한 Library 안에 여러 named function 이 들어 있는 구조라 처음 보면 왜 굳이? 가 의문이고요.
두 번째는 EVAL 과 함께 존재 한다는 점. Functions 가 Lua scripting 을 완전 대체하지 않아서 둘 다 살아 있어요. 어느 걸 써야 하나 가 헷갈리는데, 기존 코드는 EVAL 그대로 OK, 새 프로젝트만 Functions 권장 정도가 정답이에요.
이 글에서 Functions 의 4가지 핵심 개선·Library 구조·FCALL/FCALL_RO·EVAL 에서 마이그레이션 패턴·Cluster 환경까지 정리.
Functions vs EVAL — 4가지 핵심 차이
(1) 영속성 (Persistence)
| 항목 | EVAL Script | Functions |
|---|---|---|
| 디스크 저장 | X (메모리만) | ◯ (RDB·AOF) |
| 서버 재시작 시 | 사라짐 → NOSCRIPT 에러 | 유지 |
| 클라이언트의 NOSCRIPT 핸들링 | 필요 | 불필요 |
가장 큰 개선이에요. EVAL 은 Redis 재시작 = 모든 스크립트 캐시 손실 → 클라이언트가 NOSCRIPT 에러 보면 EVAL 다시 보내는 흐름이 필요했어요. 참고로 RDB (Redis Database 스냅샷) 와 AOF (Append-Only File 로그) 는 Redis 의 두 가지 영속화 방식인데, Functions 는 여기 함께 저장 되어 재시작 후에도 그대로 살아 있어요. NOSCRIPT 는 서버에 해당 스크립트가 없을 때 반환되는 에러 라 Functions 에서는 신경 쓸 일이 없고요.
(2) Replica 자동 복제
EVAL — master 에서 실행하면 결과만 replica 에 복제. 스크립트 자체 는 replica 에 안 옴 → replica 에서 직접 EVALSHA 호출 시 NOSCRIPT.
Functions — Library 자체가 replica 에 복제. replica 에서도 즉시 FCALL 가능. 읽기 분산 환경에 유리.
(3) 이름 기반 식별
EVAL — SHA1 해시 (스크립트 본문을 압축한 식별자) 로만 식별. 디버깅·운영 화면에서 보면 의미 불명.
Functions — 이름 (my_lib·unlock_lock) 으로 식별. 코드 가독성·운영 모니터링 모두 유리.
(4) Library 단위 atomic 로딩
EVAL — 한 스크립트씩 등록 → 복잡한 라이브러리는 부분 업데이트 가능.
Functions — Library 단위 로 통째 atomic 로딩 → 반쪽 업데이트 불가. 운영 안정성 ↑.
한 줄 정리 — Functions = EVAL 의 영속 + replica 자동 + 이름 + atomic library 개선판. 새 프로젝트 권장.
Library 구조
#!lua name=mylib
redis.register_function('hello', function(keys, args)
return 'Hello, ' .. args[1]
end)
redis.register_function('unlock', function(keys, args)
if redis.call('GET', keys[1]) == args[1] then
return redis.call('DEL', keys[1])
else
return 0
end
end)
가장 위 #!lua name=mylib 가 shebang line (스크립트 첫 줄 메타데이터 선언) 으로 library 이름을 지정해주는 필수 줄이에요. 그 아래 redis.register_function('name', function(keys, args) ... end) 로 함수를 등록하는데, 같은 library 안에 여러 함수 를 동시에 넣어도 돼요. 함수 인자는 keys (Redis 키 배열) 와 args (일반 인자 배열) 두 가지로, EVAL 의 KEYS[]·ARGV[] 와 동일한 역할이에요.
FUNCTION LOAD — Library 등록
> FUNCTION LOAD "#!lua name=mylib\nredis.register_function('hello', function(keys, args) return 'Hello' end)"
"mylib"
반환값 = library 이름.
기존 library 덮어쓰기 = REPLACE 옵션:
> FUNCTION LOAD REPLACE "#!lua name=mylib\n..."
FCALL — Function 실행
> FCALL <function-name> <numkeys> <keys...> <args...>
예제:
> FCALL hello 0 Alice
"Hello, Alice"
> SET lock:order:99 my-uuid EX 30
> FCALL unlock 1 lock:order:99 my-uuid
(integer) 1
> FCALL unlock 1 lock:order:99 wrong-uuid
(integer) 0
EVAL/EVALSHA 와 비교 — 훨씬 깔끔. SHA 외울 필요 X, 함수 이름 으로 직접 호출.
FCALL_RO — Read-Only 함수
> FCALL_RO <function-name> <numkeys> <keys...> <args...>
쓰기 명령을 호출하지 않는 함수는 FCALL_RO 로 명시해요. 장점이 두 가지인데, 먼저 replica 에서도 호출 가능 해서 읽기 함수를 replica 에 직접 라우팅할 수 있고, MULTI/EXEC 안의 transaction 안전성 도 확보돼요. 읽기 함수임이 명시되니까요.
redis.register_function{
function_name = 'get_user',
callback = function(keys, args)
return redis.call('HGETALL', keys[1])
end,
flags = {'no-writes'}
}
flags = {'no-writes'} 가 함수의 읽기 전용 표시. FCALL_RO 호환.
모니터링·관리 명령
FUNCTION LIST — Library 목록
> FUNCTION LIST
1) 1) "library_name"
2) "mylib"
3) "engine"
4) "LUA"
5) "functions"
6) 1) 1) "name"
2) "hello"
3) "description"
4) (nil)
5) "flags"
6) (empty array)
FUNCTION DUMP / RESTORE — 백업·이전
# 백업
> FUNCTION DUMP
"\x09\x00..."
# 다른 Redis 에 복원
> FUNCTION RESTORE "\x09\x00..."
운영 환경 이전·복구·다른 인스턴스 복제에 유용.
FUNCTION DELETE / FLUSH
> FUNCTION DELETE mylib # 특정 library 제거
> FUNCTION FLUSH # 모든 library 제거
FUNCTION STATS
> FUNCTION STATS
1) "running_script"
2) (nil) # 현재 실행 중인 스크립트 없음
3) "engines"
4) 1) "LUA"
2) 1) "libraries_count"
2) (integer) 1
3) "functions_count"
4) (integer) 2
운영 모니터링.
EVAL 에서 Functions 로 마이그레이션 패턴
단계 1: 기존 EVAL 스크립트
unlock_lua = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
"""
unlock_script = r.register_script(unlock_lua)
unlock_script(keys=['lock:order:99'], args=['my-uuid'])
단계 2: Functions Library 로 변환
-- mylib.lua
#!lua name=mylib
redis.register_function('unlock', function(keys, args)
if redis.call('GET', keys[1]) == args[1] then
return redis.call('DEL', keys[1])
else
return 0
end
end)
단계 3: 등록 + 호출
# 한 번만 (애플리케이션 시작 시 또는 deploy 시)
with open('mylib.lua') as f:
r.function_load(f.read(), replace=True)
# 이후 매번 단순 호출
r.fcall('unlock', 1, 'lock:order:99', 'my-uuid')
마이그레이션 핵심은 인자 명세가 소문자 table 인자로 바뀐다는 점이에요. KEYS[] 가 keys 로, ARGV[] 가 args 로 내려오고, redis.call 은 그대로 써요. 등록은 FUNCTION LOAD 로 한 번만 올려두고, 이후 호출은 FCALL <name> <numkeys> ... 로 매번 부르는 흐름이에요.
한계·주의사항
1. Redis 7.0+ 만
Redis 6.x 이하에서는 Functions 명령 자체가 없음. 7.0 이상 확인 후 도입.
2. Lua 5.1 그대로
내부 엔진은 여전히 Lua 5.1. 언어 기능 한계는 EVAL 과 동일. 순수 Lua 만, I/O·OS 모듈 차단.
3. 실행 시간 한계
EVAL 과 동일 = 기본 5초. busy-reply-threshold (장시간 실행 스크립트 경고 임계값) 설정.
4. Cluster — 같은 slot
모든 keys[] 가 같은 slot. EVAL 과 동일한 제약.
5. 마이그레이션 비용
기존 EVAL 코드를 전부 바꾸지 않아도 됨. 둘 다 함께 사용 가능. 새 코드만 Functions, 기존은 EVAL 그대로 유지가 현실적.
여기까지 따라오셨다면 한 가지 의문 — "클라이언트 라이브러리 지원은?". redis-py 4.2+, Jedis 5+, Lettuce 6+ 등 최신 클라이언트만 지원. 오래된 라이브러리 면 raw 명령으로 직접.
시험 직전 한 번 더 — Redis Functions 함정 압축 노트
- Redis Functions = Redis 7+ 신규, EVAL 의 후속
- 4가지 핵심 개선 = 영속·replica 자동·이름 식별·Library atomic 로딩
- 영속 = RDB·AOF 에 저장, 재시작 후에도 유지 → NOSCRIPT 에러 없음
- Replica 자동 복제 = replica 에서도 즉시 FCALL 가능
- 이름 기반 = SHA1 대신 함수 이름 (
unlock·hello) - Library 단위 atomic = 통째로 등록·갱신, 반쪽 업데이트 X
- Library 구조 =
#!lua name=mylibshebang +redis.register_function('name', fn) - 한 Library 안에 여러 함수 가능
- 함수 인자 =
keys(소문자, table) +args(소문자, table) FUNCTION LOAD= library 등록,REPLACE옵션으로 덮어쓰기FCALL= 실행 →FCALL <name> <numkeys> <keys...> <args...>FCALL_RO= 읽기 전용 함수 (replica 호출 가능, transaction 안전)- 읽기 전용 표시 =
flags = {'no-writes'} - 관리 명령 =
FUNCTION LIST·DUMP·RESTORE·DELETE·FLUSH·STATS - 마이그레이션 =
KEYS[]→keys,ARGV[]→args, 등록은 한 번만 - EVAL 과 함께 사용 가능 — 기존 코드 그대로 유지 가능
- 새 프로젝트 권장 = Functions
- 기존 코드 = EVAL 그대로 OK
- Redis 7.0+ 만 지원
- Lua 5.1 엔진 그대로 (언어 기능 동일)
- 실행 시간 한계 5초 (busy-reply-threshold)
- Cluster = 모든 keys 가 같은 slot — hash tag
- 클라이언트 라이브러리 = 최신 버전만 지원 (redis-py 4.2+·Jedis 5+·Lettuce 6+)
공식 문서: Redis Functions 에서 Library·FCALL·관리 명령 사양을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 56편 — Redis Keyspace Notifications + 세션 만료 패턴
- 57편 — Redis Pipelining + MULTI/EXEC와의 차이
- 58편 — Redis Pub/Sub + Sharded Pub/Sub
- 59편 — Redis Transactions + WATCH Optimistic Locking
- 60편 — Redis Lua Scripting (EVAL · EVALSHA)
다음 글: