[Error/Redis] RedisSystemException .. Write commands not allowed after non deterministic commands

2025. 2. 27. 21:26DB/NoSQL

 

📝 Redis를 다루며 만났던 에러들이 있었기에, 대응 방법을 까먹지 않기 위해 글을 작성하고자 합니다.


😭Error  : RedisSystemException .. Write commands not allowed after non deterministic commands

SpringBoot 프로젝트에서 대량으로 저장된 데이터를 Lua script를 이용하여 삭제하는 과정이 있었습니다. 해당 과정에서 발생하는 에러는 다음과 같습니다.

 

 

org.springframework.data.redis.c: Error in execution;

nested exception is io.lettuce.core.RedisCommandExecutionException:

ERR Error running script  ...

Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to sinngle commands replication mode

 

😂원인

사실 놀랍게도 에러에 해결책이 숨어 있습니다. 다만, 왜 에러가 발생했는지가 궁금했기에, 내용을 좀 더 찾아보게 되었습니다. 

 

우선 해당 에러는 비결정적(non deterministic) 명령어 이후에, 결정적 명령어가 왔기 때문에 발생하는 에러입니다. 예를 들어 SCAN과 같은 명령어는 검색할 때마다 결과 값이 바뀔 수 있습니다. 그런 상황에서 DEL 과 같이 확정적으로 삭제 명령어가 오게 되면, 삭제되는 데이터가 다를 수 있습니다

 

여기서 삭제되는 데이터가 다를 수 있다라는 건, 삭제되는 장소가 다를 경우에 발생할 수 있는데요! 네, 그렇습니다. Redis 는 Master-Slave 구조를 가지고 있습니다! 따라서 같은 데이터를 유지하기 위해 Master에서 실행된 SCAN 결과가 Slave에서 실행된 SCAN 결과와 동일해야 했고, 삭제되는 데이터 또한 같아야 합니다. 

 

그런데 Redis 버전마다 Lua Script를 처리하는 방식이 달랐고, 제가 사용한 Redis 버전에서는 SCAN 이후에 DEL 을 처리할 수가 없었기에 해당 에러가 발생했던 겁니다.

 

제가 로컬에서 사용했던 Redis는 3.x 버전이었습니다. 

 

gpt를 활용하여 찾아본 Redis 버전의 Lua script 실행 방식은 달랐는데요! (출처 : chatGPT)

Redis 버전 Lua script 실행 방식 SCAN -> DEL 실행 결과
Redis 3.x Verbatim Replication (스크립트 원본을 그대로 복제) SCAN이 실행될 때마다 결과가
달라질 수 있어 복제 실패 발생 가능
Redis 5.x Effects Replication (실행된 명령어만 복제) SCAN 실행 결과는 복제되지 않고,
DEL만 복제되므로 문제 없음
Redis 7.x Effects Replication (Verbatim Replication 완전 제거) Redis 5.x와 동일하게 동작하며,
DEL만 복제되므로 문제 없음

 

📝해결

1. Redis 3.x

Redis 3.x 버전을 사용할 거라면, Lua script에 redis.replicate_commands() 를 추가해주면 됩니다. 위의 에러로 찍힌 로그를 보면 해결책이 눈에 보이시죠?!

 

Lua script는 변수를 local로 지정해주는 게 특징인데요!! 코드에 redis.replicate_commands()가 추가됨을 확인할 수 있습니다.

redis.replicate_commands() // 해당 코드 추가

local cursor = "0"
repeat
    local result = redis.call("SCAN", cursor, "MATCH", "sample*", "COUNT", 100)
    cursor = result[1]
    local keys = result[2]
    
    if #keys > 0 then
        redis.call("DEL", unpack(keys))
    end
until cursor == "0"

 

2. Redis 5.x 상위 버전 사용

이 경우는 1.에서 사용한 redis.replicate_commands()를 추가할 필요가 없습니다. 사실 제일 좋은 방법은 최신 버전을 사용하는 것이겠지요!

 

마지막으로 Redis 문서 중 Lua script 페이지에서 다루고 있는 redis.replicate_commands() 입니다.

 

Verbatim script replication was the only mode supported until Redis 3.2, in which effects replication was added.

The lua-replicate-commands configuration directive and redis.replicated_commands() Lua API can be used to enable it.

In Redis 5.0, effects replication became the default mode. As of Redis 7.0, verbatim replication is no longer supported.

 

 

References

1. Scripting With Lua (Redis official) 

2. Redis interact with Data (데이터 접근)