본문 바로가기

프로젝트

Redis I/O에서 생기는 동시성 문제

이슈

💬 회사 프로젝트에서 캐싱을 위하여 Redis를 사용을 하고 있다. 일반 로직에서 캐싱을 읽고 업데이트 해주는 작업이 같이 수행이 되었다. 트래픽이 발생하기 이전에는 응답 시간이 짧아 Redis I/O에서 발생하는 문제는 발생하지 않았는데, 트래픽이 몰리면서 중간 로직의 응답시간이 길어지면서 Redis I/O 과정에서 업데이트를 하기 이전에 데이터를 읽어가는 문제가 발생하게 되었다.

대응 방안 고민

💬 하나의 자원에 하나의 스레드만 접근을 하여 처리를 할 수 있어야 데이터의 무결성이 깨지지 않을 수 있다. 무결성을 지켜주기 위하여 하나의 자원에 접근을 제한하는 Synchronized, Lock 등을 고려해볼 수 있었다. 회사에서는 서버를 Scale-out을 하여 여러대를 운영을 하고 있었기 때문에 하나의 서버에서만 자원 접근 제한을 걸 수 있는 Synchronized는 적합하지 않다고 판단을 하였다. 모두 서버에서 접근을 하여 사용할 수 있도록 Lock을 사용하기로 했다.

구체적 대응 방안

💬 Lock을 걸기 위해서 사용할 수 있는 건 Redis, Zookeeper, DB를 사용할 수 있었다. Redis 캐시에 Lock을 걸어야 했기 때문에 속도적인 문제로 DB는 사용할 수 없었다. 그리고 현재 회사에서  Zookeeper를 사용하고 있지 않아 Redis를 사용하여 구현을 하기로 했다.

 

✔ Redis 해결 방법

✅ Redis 공식 홈페이지에서는 Lock을 위하여 Key를 만드는데, SET의 NX 옵션을 사용하였다. 거기에 추가적으로 Redis Node가 여러 개의 경우 사용할 수 있는 RedLock이라는 알고리즘을 제공을 하고 있다. 현재 회사에서는 Redis Node를 하나만 사용하고 있기 때문에 일반적인 SET NX 옵션만 사용하여 코드를 구성하기로 했다.

 

✔ Redis 해결 코드

✅ 회사에서 사용하고 있는 Redis Client는 Lettuce였고, RedisTemplate에서 제공하는 setIfAbsent를 사용하여 SET NX를 대체할 수 있었다. 해당 메서드는 key 값에 해당하는 값이 존재하지 않으면 저장을 하고 True 값을 반환한다. 그것이 아니라면 False 값을 반환을 한다.
redisTemplate.opsForValue().setIfAbsent(key, "", Duration.ofMillis(10));​

생각

💬 무심코 문제가 되지 않아 지나가는 경우가 많았지만, I/O가 일어나는 자원에 대해서는 동시성이 발생할 수 있다는 것을 이번에 여실히 깨달았다. 앞으로는 I/O가 일어나는 자원에 대해서는 항상 동시성을 잘 고려하여 작성할 수 있도록 해야겠다. 그리고 시간이 된다면 프로젝트에서 어노테이션에서 사용하여 편하게 사용할 수 있도록 코드를 작성해봐야겠다. (현재 회사에서 따로 해보는 중... 적용해볼 수 있으면 좋겠다)