백엔드 데이터 인프라 61편 — Redis Functions (Library · FCALL · 영속 스크립트)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 61편. Redis Functions (Redis 7+) — Lua scripting 의 후속. Library·FCALL·FCALL_RO·FUNCTION LOAD 흐름, 영속·이름 기반·replica 자동 복제 같은 개선점, 기존 EVAL 에서 Functions 로 마이그레이션 패턴까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 61편 — Redis Functions (Library · FCALL · 영속 스크립트)

이 글은 백엔드 데이터 인프라 시리즈 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=mylibshebang 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=mylib shebang + 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·관리 명령 사양을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!