Database/Redis
Lua Script in redis
최-코드
2024. 11. 5. 10:48
중요성
- 동시성을 해결할 수 있는 해결책으로, lua script에 적힌 명령어는 redis에서 원자적으로 실행된다. 즉, 각 명령어 사이에 다른 명령어가 실행되지 않는다.
- 여러 번 redis 서버에 요청을 보내지 않고 한 번만 요청을 보내서 여러 명령어를 수행할 수 있다.
Lua 문법 :
- https://velog.io/@goyou123/LUA 이 블로그를 참고하자.
- lua script에서 return이 없으면 서버에 아무 값을 보내지 않는다.
redis에서 사용 방법
- script load "<lua script 내용>"을 실행함으로써 lua script로서 실행할 명령어들을 메모리에 로드할 수 있다.
- 이 때 반환되는 값은 sha1로 해당 lua script를 지칭하는 값이다.
- evalsha <sha1> <num> 명령어를 입력하면 해당 sha1 값에 해당하는 lua script가 실행된다. 여기서 <num>은 키의 개수를 뜻하며 0이 아닌 숫자일 시에 바로 뒤에 키값을 써주고, 이후에 일반적인 인수를 넣어준다.
- lua script에서 일반적인 인수를 받는 방법은 ARGV[index]을 이용하면 된다. ARGV는 전역변수로 인수에 따라 자동으로 선언된다.
- redis에서 ARGV에 값을 줄 때는 무조건 string 값으로 넘겨줘야 하며, lua script 내부에서도 string 타입이 된다.
script load 'return 1 + ARGV[1] + tonumber(ARGV[2])'
evalsha <sha1> 0 '1' '2' ...
- redis에 존재하는 명령어를 사용하려면 redis.call('명령어', KEYS[index], 인자1, ...)을 이용하면 된다.
- 이 때 KEYS 또한 전역변수로, redis에서 전송한 값을 string으로 받는다.
script load 'return redis.call("GET", keys[1])'
evalsha <sha1> 1 color // KEYS = { 'color' }
- return redis.call("GET", "color")로 접근해도 실행은 가능하지만, 이면에서 버그가 생기기 시작하고 문제를 일으키게 되므로 사용을 지양해야 한다.
spring에서 사용 방법
- DefaultRedisScript를 싱글 인스턴스로 관리하여 동일 script에 대해 동일한 sha1 값을 사용하도록 설정한다.
@Bean
public DefaultRedisScript<Void> hsetxxScript() {
String luaScript =
"local key = KEYS[1] " +
"local fieldKey = ARGV[1] " +
"local fieldValue = ARGV[2] " +
"if redis.call('EXISTS', key) == 1 then " +
" redis.call('HSET', key, fieldKey, fieldValue) " +
"end";
return new DefaultRedisScript<>(luaScript, Void.class);
}
- 로드해 놓은 script를 사용한다.
...
private final DefaultRedisScript<Void> hsetxxScript;
...
public void putFieldIfPresence(String key, String fieldKey, String fieldValue) {
stringRedisTemplate.execute(hsetxxScript, singletonList(key), fieldKey, fieldValue);
}
- 내부적으로 전달하는 값을 byte 배열로 변경한다.
- RedisTemplate<String, Long>과 같이 설정하고 값을 넣으면 해당 객체의 타입도 저장되는데, Lua Script에서 저장될 때는 Long 값 자체에 대한 byte값이 저장되므로 일치하지 않게 된다.
- 따라서 Lua Script를 사용할 때에는 무조건 RedisTemplate<String, String>만을 사용하자.