일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- redis
- 부꾸러미
- dto projection
- 동적 SQL
- 사이드 프로젝트
- 부꾸
- 모의면접
- jooq
- open contribution jam
- 눈송이
- 코드트리
- Database
- spring context
- 보따리
- 테오의 스프린트
- 체험
- SQL
- Spring
- bean
- 오블완
- 북극곰
- 후기
- 구슬
- 글또 #다짐
- 트러블슈팅
- 글또
- 프로그래머스
- 티스토리챌린지
- DI
- jscode
- Today
- Total
벤티의 개발 로그
[Redis] 첫 만남: Cache 기능을 지원하는 NoSQL!? 본문
이 글은 2024년 4월에 Notion에 정리한 글을 재구성한 것입니다 😆 🚩
"에러가 있는 것 같아요!" 👀
기존에 작성했던 코드를 리팩토링 하던 어느 날, 이메일을 이용한 본인 인증 API에 오류가 있는 것 같다는 연락을 받게 되었다. 정확히 어떤 오류인지는 전달받지 못했기에, 기존에 작성한 코드를 들여다보았다.

'뭐지?'
당시 중간고사를 공부하고 있던 나는 빠르게 에러를 해결하기 위해 코드도 들여다보고, Postman에 정리한 Request Example들을 전부 다시 테스트해 봤다. 딱히 잘못된 것은... 없어 보였다.
정확한 판단을 위해 새로운 계정으로 회원 가입을 하고 다시 테스트를 해봤다. 의도한 대로 결과가 나왔다... 라고 생각한 순간, '잘못된 인증 번호 입력' 예외 처리에서 의도한 대로 결과가 나오지 않았다. 난 이것이 '단순히 잘못된 이메일 계정을 Request에 넣어서' 발생한 오류라고 생각했다.
아니었다. 뭔가 이상했다.
이전까지 에러를 잘 뱉어내던 다른 상황들에서도 의도하지 않은 결과가 나오기 시작했다. 잠시 후, 나는 어떤 점이 잘못됐는지 알 수 있었다.
문제 상황 😨
가장 중요한 것을 놓치고 있었다. 이 API를 혼자 개발했기 때문일까? 나는 SMTP 서버 입장에서 사용자에게 보낸 인증 번호가, 사용자가 입력한 인증 번호가 일치하는지에만 집중하고 있었다.

즉, 어떤 사용자에게 어떤 인증 번호가 전송되는 지를 생각하지 않았다. (...)
데이터를 관리할 때 가장 중요하게 고려해야 할 동시성 오류와 데이터 원자성 오류를 동시에 발생시키고 있었다...
처음에는 다른 데이터처럼 MySQL에 저장하려 했다. 하지만 이런 생각이 들었다.
'어차피 한 번 쓰면 아무 의미가 없는 데이터인데, MySQL에 일일이 저장해서 장시간 동안 보관할 필요가 있을까?'
마침, 본인 인증 API에 '유효 시간'을 도입하자는 의견이 있어 이 '유효 시간'을 어떻게 측정하고 관리할 것인가에 대한 고민도 하고 있었다. 이 2가지 문제를 동시에 해결할 수 있는 방법이 없을까 고민하던 끝에, 새로운 방법 찾았다.
Redis! 👋
MySQL 같은 RDB에만 매몰되어 헤매고 있던 나를 도와주었던 것은 Key-Value NoSQL인 Redis 였다.
Key-Value형 데이터베이스는 위 그림처럼 다양한 형태의 데이터를 저장할 수 있었다. Java의 HashMap이나 Python의 Dictionary 처럼 Key에는 '식별 가능한 고유한 값'이 온다는 성질을 공유했다.
하지만, Redis를 사용하기로 한 결정적인 이유는 아래와 같다.
1. In-Memory 데이터베이스이다.
- 데이터 접근과 대기 시간이 매우 짧다.
- 즉, 조회 속도가 매우 빠르다.
- 데이터 복구가 가능하다.
2. Cache 기능을 지원한다.
- 즉, 유효 시간을 설정하여 데이터를 저장할 수 있다.
따라서, Redis를 이용하면
- 각 사용자 별로 전송한 인증 번호를 저장하면서
- 쓸모가 없어진 데이터는 바로 지워주고
- 유효 시간을 사용하여 관리
할 수 있을 것이라 생각했다.
문제 해결! 🎉
결론부터 말하면 위 문제를 해결할 수 있었다!
우선, 위 사진처럼 RedisTemplate을 이용하여 코드를 작성하기 위해 build.gradle에 Redis-Reactive를 import 했다.
그 후, Redis에 데이터를 저장할 때 데이터가 저장될 유효 시간을 같이 저장하는 setValuesWithTimeout 함수를 작성하였다. 함수의 인자로 들어갈 timeout 인자의 값만큼 유효 시간이 설정된다. 주의할 것은, TimeUnit의 단위를 여러 가지로 설정할 수 있다는 것이다.
우리 팀이 기획한 유효 시간은 3분이었기 때문에, 나는 3000 Milliseconds를 인자로 넣어주었다. 또, 어떤 이메일로 인증 번호를 전송했는지 편하게 확인할 수 있게 인증 번호(Verification Code)의 약자인 VC와 이메일을 Key로 설정했다. 그리고, 가장 중요한 인증 번호를 Value로 설정했다.
(테스트 영상을 녹화하지 못해 스크린샷으로 대체합니다... 😅)
된다!!! Naver 계정으로 보낸 인증 번호(A)를, Gmail 계정으로 인증 번호(B)를 보내고, Request로 인증 번호(A)를 입력해도 제대로 인증이 된다!!!

...에서 끝날 수 있었지만...
반성의 의미에서 여기서 끝나지 않고 RedisTemplate에서 제공하는 다른 내장 함수를 알아보다가 delete와 hasKey의 존재를 알게 되었다.
delete는 JPA 내장 함수와 비슷하게 특정 key 값을 가진 개체를 Redis에서 삭제하는 함수이고, hasKey의 경우 Redis에 특정 key 값을 가진 개체가 존재하는지 여부를 boolean으로 반환하여 flag처럼 사용할 수 있게 지원하는 함수였다.
앞서 발생한 동시성 오류와 데이터 원자성 오류 발생을 더 확실하게 방지하기 위해, '유효 시간 3분의 경과 여부와 상관없이, 인증이 완료된 이메일로 전송한 인증 번호는 Redis에서 즉각 삭제'하는 것이 낫다고 판단했다.
따라서, 사용자가 인증 번호를 Request로 보낸 후
1. 인증이 완료되면 hasKey를 호출해서
2. Redis에 그 Key를 가진 개체가 있는 지 확인한 후
3. deleteValues를 호출해서 해당 개체를 Redis에서 삭제
하도록 로직을 짜서 코드를 작성했다.
느낀 점 🚩
위와 같은 시행 착오를 겪고 느낀 점은 아래 2가지이다.
1. 테스트 할 때는 실행 조건도 제대로 확인하자...
2. Cache 기능이 필요한 로직에서는 Redis를 이용하자!