본문 바로가기

개발 경험기

[JWT 로그인 구현기] (3) Redis 도입하기

 

전편에서 refresh token을 MySQL에 저장했다. 그런데 내가 개발한 애플리케이션에서는 사용자 로그인, 토큰 재발급 요청이 들어올 때마다 refresh token 값이 수정된다. 쉽게 말해 MySQL IO 연산이 너무 자주 일어난다. 이는 성능적으로 문제가 있음을 느꼈고 개선하고 싶었다. 그래서 이번 편에서는 전편에서 생긴 성능 문제를 해결하기 위해 고민하고 공부한 내용을 공유하려 한다.

 

 

Redis

성능에 대해 문제가 생길 것 같다고 생각한 이유는 MySQL은 데이터를 SSD, HDD에 저장하기 때문이다. 성능을 어떻게 올릴 수 있을까 고민하고 공부하면서 Redis를 알게 됐다.

 

Redis?

Redis는 Remote Dictionary Server로 key-value로 데이터를 저장하는 서버이다. Redis를 사용하면 얻을 수 있는 장점으로 첫 번째는 성능이다. Redis는 메모리 기반 DB기 때문에 MySQL과 비교했을 때 데이터를 수정하는 작업을 더 빠르게 진행할 수 있다. 두 번째 장점으로는 Redis는 다른 메모리 기반 DB와 달리 다양한 자료구조를 제공해준다. 덕분에 개발자는 쉽게 데이터를 관리할 수 있어 생산성이 올라간다. 하지만 역시 단점도 존재한다. 첫 번째는 Redis는 메모리 DB기 때문에 SSD, HDD 기반 DB인 MySQL보다 저장할 수 있는 데이터양이 적다. 두 번째는 Redis는 관계형 모델이 아니라 key-value로 데이터를 저장하기 때문에 데이터를 정규화에 어려움이 있다.

 

나는 refresh token만 redis를 이용하고, 나머지 데이터들은 MySQL을 이용하기 때문에 Redis의 단점보다 장점이 더 크다고 생각해 도입을 결정했다.

 

Redis 사용 시 주의할 점

도입을 선택하고 나서도 Redis를 사용하는 데 주의해야 할 점이 있다. 첫 번째는 Redis는 메모리 기반 DB기 때문에 메모리 관리에 신경 써야 한다. 메모리 파편화 혹은 단편화를 주의해야 한다. 또, 메모리가 부족해 swap이 발생하면 Redis 성능에 큰 영향을 준다. 두 번째로 주의할 점은 O(N) 명령어는 운영환경에서 지양해야 한다. 왜냐하면 Redis는 single-thread로 동작하기 때문에 O(N) 명령어가 있으면 뒤에 기다리고 있는 명령들에게 지연이 생간다. 즉, Redis 성능에 큰 영향을 준다.

 

 

MySQL을 Redis로 migration 하기

 

Redis Client

Redis를 사용하기 위해서 Redis Client를 선택해야 했다. Java 진영의 Redis Client에는 Jedice와 Lettuce가 존재한다. 두 모듈의 큰 차이는 Jedice는 thread-safe 하지 않고, Lettuce는 thread-safe 하다는 것이다.

 

Jedice는 thread-safe 하지 않기 때문에 멀티 스레드 환경에서 같은 instance를 공유할 수 없다. 이를 해결하기 위해 connection pool 개념을 사용한다. 따라서 thread마다 Jedice instance를 갖고 있다. 따라서 Redis connection 수가 많아진다. 반면, Lettuce는 thread-safe 하다. 따라서 멀티 스레드 환경에서 같은 instance를 공유할 수 있다. 덕분에 Redis connecntion 수는 하나면 충분하다. (참조)

 

이런 두 차이가 Lettuce와 Jedice의 성능차이를 가져온다. 나는 성능이 더 좋은 lettuce를 Redis Client로 선택했다. (참조)

 

Redis migration

Redis를 사용하기 위해 의존성과 설정 파일을 추가했다.

 

build.gradle 스크립트 일부

 

RedisConfig 코드 일부

 

Redis를 사용해 refresh token을 저장하기 때문에 RefreshToken 클래스도 Redis에 의존적으로 수정했다.

 

RefreshToken 코드 일부

 

Redis Repository를 만들었다.

 

RefreshTokenRedisRepository 코드 일부

 

Redis Service를 만들어 Redis 작업을 수행한다. 간단한 작업이기 때문에 트랜젝션 처리는 하지 않았다. (단, Redis의 트랜젝션 개념은 일반 RDB 트랜젝션 개념과 차이가 있으므로 사용시 주의해야 한다. (참조))

 

RefreshTokenService 코드 일부

 

마무리

개발한 기능의 성능을 높이기 위해 고민하고 공부하면서 Redis를 알게 됐다. 개발자는 기술에 의존하기보다는 해결하고자 하는 문제에 집중하고 거기에 적합한 기술을 사용하는 것의 중요성을 느꼈다.

 

사용자 인증, 인가 기능을 개발하면서 만족하지 않고 개선할 수 있는 부분을 고민했다. 그 결과 새로운 지식과 기술을 배울 수 있었다. 앞으로도 내가 개발한 기능에 만족하지 않고 개선점을 찾아 개선하는 개발자가 되도록 노력해야겠다.