실전 프로젝트 4주 차에 돌입하며, Redis를 활용해 성능 개선을 진행했다. 이전에 동시성 문제를 비관적 락으로 해결했는데, 응답 속도를 개선할 수 있는 방법이 없을지 찾아보다가 빠른 I/O가 장점인 Redis를 활용한 개선을 해보면 어떨까 하는 생각에 적용하게 되었다.
Redis 도입 이유
우리 프로젝트는 '수강신청'이다. 과목마다 수강신청이 가능한 인원은 제한적이다. 100명의 제한 인원이 이미 모두 신청해서 잔여석이 0명 일 때 에는 '수강신청 가능 인원이 마감되었습니다.' 라는 응답을 보내준다.
이 응답을 굳이 비즈니스 로직으로 들어와서 MySQL에서 남은 잔여석을 확인하지 않고, Redis를 활용하여 남은 수강인원이 0명일 경우에 바로 응답을 보내 주도록 하면 응답 속도를 개선할 수 있을 것이다.
Redis 란?
Remote Dictionary Server의 약자로, 오픈 소스 기반의 인메모리 데이터 베이스.
장점
- 높은 성능 : 메모리 내 데이터 구조를 사용하여 빠른 읽기 및 쓰기 작업이 가능
- 다양한 자료구조 : String, Hash, List, Set, Sorted Set 등 다양함
단점
- 모든 데이터가 메모리에 저장되어 메모리 소모가 높을 수 있음
- Redis는 단일 쓰레드로 동작하므로 특정 작업이 블로킹되면 서버 전체가 영향을 받을 수 있음
Redis 적용
build.gradle 의존성 추가
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
application.yml 설정 추가
redis host와 port는 설정하지 않아도 default 값으로 설정된다. host: localhost, port: 6379 로 설정된다. password 설정을 해두어서, password 정보도 함께 작성하였다. 배포 시 host를 레디스가 설치되어있는 인스턴스의 ip로 바꿔주어야 한다.
비즈니스 로직 이전, 레디스 캐시 먼저 접근하여 남은 수강신청 인원을 확인
수강신청 시, 컨트롤러에서 사용하는 레디스 캐시 facade 클래스이다.
실제 비즈니스 로직 이전에 키값으로 레디스에 저장되어 있는지 확인한다. 만약 저장되어있는 값이 '0'이라면, 수강 신청 인원이 마감된 것이다. 바로 예외를 발생시켜 신청 인원이 마감되었다는 메시지를 클라이언트에게 전달한다. 비즈니스 로직을 접근하지 않도록 하였다.
만약 레디스에 값이 존재하지 않으면 수강신청을 완료한 후, 레디스에 남은 수강신청 인원을 저장해 준다.
Repository 역할을 하는 클래스. Redis 데이터에 직접 접근하는 메서드들이다. 비즈니스 로직에 직접 들어가기 전, 이 메서드들을 활용하여 레디스에서 수강신청 잔여 석의 데이터를 먼저 확인한다.
또한 데이터가 없는 경우, 레디스에 값을 저장하는 메서드도 포함되어 있다.
Redis 도입 전 기존 수강신청 flow
Redis를 도입하기 이전, 기존 수강신청 flow이다. 강의 여석을 확인하기 위해 무조건 비즈니스 로직, DB접근이 필요하다.
Redis 도입 후 개선된 수강신청 flow
Redis를 도입하여 비즈니스 로직 이전에 여석을 확인하여 0명일 경우, 바로 클라이언트에게 응답해 주는 방식으로 개선되었다.
성능 테스트 결과 - Redis 적용 전 / 후
AWS EC2 t2.medium 에서 테스트를 진행하였다. 테스트는 Jmeter를 활용하여 진행하였다. 5천명 ~ 5만명 의 요청을 10초동안 보냈다. 그 중 5만명 요청의 결과이다.
오류율은 100명이 모두 수강신청이 되면, 그 이후 요청은 Http code가 400으로 응답되도록 하였다. 200 OK가 아니라서 오류로 잡히는 것.
Redis 적용 전
평균 응답시간 : 810ms / TPS : 234
Redis 적용 후
평균 응답시간 : 297ms / TPS : 598
평균 응답시간은 810ms -> 297ms로 약 63% 개선되었고, TPS는 234 -> 598로 약 155.56% 개선된 결과를 얻을 수 있었다.
❌ 트러블 슈팅
레디스 캐시 코드 서비스 배포 적용 후, 수강신청 요청 RedisCommandExecutionException 발생
io.lettuce.core.RedisCommandExecutionException: NOAUTH HELLO must be called with the client already authenticated, otherwise the HELLO AUTH <user> <pass> option can be used to authenticate the client and select the RESP protocol version at the same time
원인
처음 로컬로 개발할 때, password 설정을 하지 않고 개발했었음. 하지만 레디스가 설치된 EC2의 Redis 설정은 password가 설정되어있었다. 인증이 되지 않아 Redis에 요청을 보낼 수 없던 오류.
해결
RedisConfig 클래스에 password 정보를 추가하여 해결.