본문 바로가기
기획&구현

[BE] 당근플래너 Redis 도입기

by JeongO 2023. 2. 8.

안녕하세요! 당근플래너 팀 엔드를 맡은 진!입니다 😊

이번에는 저희 당근플래너 서비스에 Redis를 도입하게 된 이야기를 쓰려고 합니다.

 

 

🙄 갑자기 웬 Redis야?

레디스 도입을 이야기 하려면 먼저 리프레시토큰 이야기를 해야할 것 같아요.

이번 프로젝트를 진행하면서 난생 처음으로 Refresh Token을 구현해 보았는데요. 이 Refresh Token을 간단히 설명드릴게요.

 

저희가 어떤 서비스를 이용할 때 '로그인'이라는 과정을 통해 해당 서비스를 이용할 권한을 얻게 됩니다.

로그인 방식은 다양하지만 당근플래너는 'Access Token'을 유저에게 발급하는 방식으로 로그인을 진행하고 있어요.

유저는 이 Access Token만 있으면 당근플래너의 서비스를 이용할 수 있게 됩니다. 반대로 이 토큰이 없다면 서비스를 사용할 수 없게 되고요!

 

문제는 이 Access Token을 누군가가 악의적으로 탈취할 수 있는데요. 이렇게 되면 악당(?)이 '나'인척 하고 서비스를 이용할 수 있습니다. 그래서 보통 이 Access Token의 유효기간을 짧게 설정해놓고 유효기간이 만료되면 Access Token이 쓸모 없어지도록 만들어 버립니다.

 

하지만 이 경우, Access Token이 만료될 때 마다 유저들은 새로운 Access Token을 받기 위해 새로 로그인을 해야하는 상황이 발생하는데 이는 생각만해도 번거로운 과정이죠?! 그래서 사용하는 것이 바로 Refresh Token입니다. 이 아이의 용도는 Access Token을 새로 발급받기 위한 허가증이라고 생각하시면 될 것 같아요!

 

 

😵‍💫Redis란 단어를 처음 듣다

 

당근플래너를 처음 기획할 때 저희는 유저의 로그인 상태를 유지시키기 위해 리프레시토큰(Refresh Token)을 적용하기로 결정했습니다! 액세스토큰(Access Token)의 만료시간을 짧게 하고, 리프레시토큰을 이용해 계속해서 액세스토큰을 새로 발급하기로 했죠.

 

유저가 로그인에 성공하면 Access Token과 Refresh Token을 함께 발급해주고, 동시에 Refresh Token만 따로 DB에 저장하도록 구현했습니다. 그리고 Access Token이 만료되면 유저가 보낸 Refresh Token과 DB에 저장된 Refresh Token이 일치하는 지 확인 후, 새로운 Access Token을 발급하기로 헀습니다.

 

그리고 리프레시토큰을 적용하기로 한 후, 듣게 된 단어가 바로 '레디스(Redis)'였습니다. 리프레시토큰을 저장하기 위한 저장소로 계속 언급되었는데요. 

 

처음에는 Refresh Token도 어떻게 구현하는지 모르겠는데, 레디스는 또 뭔가....싶어 머리가 아파왔습니다. 

처음 해보는 두 가지를 동시에 하려면 아직 부족한 실력으로 감당이 안될 것 같아, 우선은 리프레시토큰기능을 먼저 구현하기로 했습니다. 

 

 

🫠Redis를 도입하기 전...

 

Redis를 도입하기 전, 저희는 유저에게 발급해준 Refresh Token을 저장하기 위해 기존에 구축되어 있는 데이터베이스인 MySQL을 저장소로 사용하였습니다. Refresh Token 저장 기능은 간단히 아래의 방식으로 구현되었습니다.

 

 

1. DB의 Member 테이블에 리프레시토큰 컬럼 추가하기

 

2. Refresh Token 발급과 동시에 DB의 회원테이블에 저장하기

 

3. AccessToken재발급 요청이 들어왔을 때 재발급 로직 수행

     = 유저가 보낸 Refresh Token과 DB에 저장된 Refresh Token 일치 확인 + Refresh Token 검증 + 새로운 토큰 발급

 

 

🤔'Redis' 꼭 써야해??

 

이렇게 리프레시토큰의 기능을 구현하고 난 후, 본격적으로 '레디스'를 생각하기 시작했는데요!

우선은 현재도 레프레시토큰의 기능이 작동되는데 저장소를 '레디스'로 옮겨야 하나? 하는 의문이 들었습니다.

그리고 조사를 시작했죠.

그리고 내린 결론은, '레디스'를 적용하는 것이 좋겠다는 것이었습니다.

레디스에 대해 아직 깊게 공부하지는 못했습니다만, 레디스를 도입하기로 결정한 가장 큰 이유는 두 가지가 있었습니다.

 

 

첫 번째로, 접근성과 효율성이 좋기 때문인데요!

우선 저희 서비스에서 Refresh Token 관련 로직을 생각해보면 다음 세 가지 경우에 DB에 접근하게 됩니다.

●  토큰 발급 시 DB에 저장
●  토큰 재발급 요청시 DB 조회
●  로그아웃 or 토큰만료시 DB에서 삭제

이렇게 세 경우마다 DB에 접근을 해야했습니다. 따라서 접근성이 중요했고 아래 그림에서 보듯, DB 보다는 더 가볍고 접근성이 좋은 레디스가 적절하다고 판단했습니다. 아, 그리고 접근성도 접근성이지만 기존 DB에 Refresh Token을 저장, 수정, 삭제하는 경우 회원들의 중요한 정보가 저장되어 있는 Member Entity를 계속해서 건드려야하는 점이 위험하다고 생각했어요.

 

 

두 번째로, Redis는 캐시 저장소로 최적화되어있다는 점이었습니다!

Refresh Token은 그 특성상 토큰의 유효기간이 지나면 폐기처리 해줘야 하는 데이터입니다.

그러니 주기적으로 토큰의 만료여부를 확인해 DB에서 직접 삭제해줘야 하는 번거로움을 피할 수 없겠죠?!

 

하지만 인메모리(In-memory) 방식의 Redis는 캐싱 전략을 구상할 수 있는 데이터베이스로써, Refresh Token같은 휘발성 데이터를 저장하기에 최적화된 저장소였습니다.

 

Redis에 저장할 때 저장기간(Duration)을 설정해주기만 하면 해당 시간이 지난 후 자동으로 삭제되는 아주 고마운 기능이 있었습니다. 덤으로 로그아웃 한 유저의 토큰을 블랙리스트로 관리해 악용되지 않도록 막을 수도 있다는 것도 알게 되었습어요! (하지만 이 기능까지는 아직 구현하지 못했습니다 ^.^;)

 

 

😘 Redis,  너 우리 당근플래너의 동료가 돼라!!

Refresh Token의 특성과, Redis의 기능을 고려했을 때 둘의 조합은 찰떡이었고 그렇게 Redis를 설치하기 시작했습니다!

레디스를 설치하는 방법은 도커 이미지를 사용할 수도 있고, 도커를 사용하지 않고 바로 서버에 설치할 수도 있습니다.

저희 당근플래너 배포서버에는 아래와 같이 간단하게 설치를 진행할 수 있었습니다!

 

 

 우선 서버에 접속한 후 아래와 같이 명령어를 차례대로 입력해 줍니다! redis 서버를 설치하기 위한 작업입니다.

sudo apt-get update
sudo apt-get upgrade

 

 다음으로 redis 서버를 설치합니다

sudo apt-get install redis-server

그리고 위와 같이 추가적인 디스크 공간이 사용될텐데 계속 진행할 것인지를 물어보면 킵고잉을 해야겠죠?! 

'Y' 를 입력하고 나면, 쭈욱쭈욱 이것저것 설치가 진행되고 아래와 같은 창이 뜨는데 또 Enter를 입력해 계속 진행을 합니다.

여기까지 진행하면 redis 설치가 완료되었습니다!

 

 

 이제 제대로 설치되었는지 확인을 해봅니다.

redis-server --version

제대로 설치가 되었습니다!

 

 

 그리고 아래와 같이 redis가 제대로 구동되는지 확인을 해봅니다.

   아래와 같이 ' 6379> ' 가 제대로 뜬다면 성공입니다!  (Redis는 기본 포트가 6379 입니다)

redis-cli

 

 

 

 

그리고 레디스 명령어를 입력해 봅니다. 아래와 같이 " key * " 를 입력하면 현재 레디스 서버에 저장된 데이터들을 조회할 수 있습니다! 처음 설치하고 아직 아무 데이터도 저장되지 않았기 때문에 (empty array)가 나온 것을 볼 수 있습니다!

 

 

 

만약 레디스에 데이터를 저장하고 조회를 하면 아래와 같이 저장된 'key' 값들이  조회되고, 해당 'key'의 'value' 값을 조회하기 위해서는 get value 명령어를 사용하면 됩니다. 아래 예시 이미지에서 보시는 바와 같습니다!

 


 

 

이렇게 레디스 서버 설치를 마치고, 이번에는 스프링부트에 레디스 의존성추가와, 설정파일 추가에 대해 간단히 살펴보겠습니다!

 

  우선 build.gradle에 아래와 같이 의존성 추가를 진행합니다

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

 

  properties 파일에도 아래와 같이 속성값들을 추가해 줍니다

spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379

 

그리고 Redis Config 파일과 Service 로직을 아래와 같이 작성하였습니다!

저희 서비스에 필요한  저장(setValues), 조회(getValues), 삭제(delValues) 관련 로직들만 추가했습니다.

 

 

  Config 파일은 간단히 아래와 같이 작성하였습니다.

 

Redis Connection Factory로는 lettuce와 Jedis가 있는데 스프링부트2부터는 Lettuce가 기본으로 설정되어 있어 

LettuceConectionFactory를 사용하였습니다.

 

 

 Redis 서비스 로직은 아래와 같이 작성하였습니다.

 

 

  그리고 Redis를 도입하기 전 기존의 DB에 토큰을 저장하던 로직을, 저장소만 Redis로 변경하도록 코드를 일부 수정해 주었습니다! 

 

 

아! Redis는 RDB와 달리 기본적인 데이터 저장 방식이 Key-Value 형식입니다.

저희 당근플래너는 현재 Key 값으로는 user의 email을, 대응하는 Value 값으로는 발급된 Refresh Token 문자열을 저장하고 있는데요!

아래와 같이 key, value 그리고 저장기간을 의미하는 Duration 값을 함께 셋팅해주면 저장이 완료됩니다!

 

 

 

😙Redis 도입기를 마무리하며..

사실 정해진 시간 내에 급히 구현하느라, Redis에 대해 아주 얕게만 알아보았는데요!

이렇게 리프레시토큰 관리 용도로만 사용하기에는 너무 아까울 정도로 레디스는 활용도가 높은 기술이었습니다. 저희 당근플래너 팀도 서비스의 고도화나 확장을 위해 레디스에 대해 더 공부하고 활용하게 되면 좋겠다는 생각(다짐?)을 해보았습니다!

 

그럼 이만 당근 플래너 서비스의 Redis 도입기 이야기를 마치겠습니다! 읽어주셔서 감사드립니다:D

다음에 또 뵈어요🥕(당근 찡긋) !