[Redis] Spring Boot 프로젝트에 Redis 적용하기
🧱 프로젝트 소개
해당 프로젝트는 OAuth2 실전 프로젝트에 Redis 캐싱을 적용한 확장 버전이다. Redis를 사용해 성능을 향상시키고 서버 부하를 줄이는 방법을 구현해 보도록 하자.
전체 코드는 깃허브에서 확인 가능하다.
📦 의존성 추가
먼저, build.gradle 파일에 Redis 관련 의존성을 추가해야 한다.
1
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
이 의존성은 Spring Boot에서 Redis를 쉽게 사용할 수 있게 해주는 스타터 패키지이다. 자동 설정과 필요한 클래스들을 제공한다.
⚙️ Redis 설정
Redis를 사용하기 위한 설정 클래스를 만들어 준다. 이 설정은 캐시 매니저를 생성하고 캐시의 기본 동작을 정의한다.
RedisConfig파일에 cacheManger 빈을 등록해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30L))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager
.builder(connectionFactory)
.cacheDefaults(cacheConfig)
.transactionAware()
.build();
}
}
entryTtl(Duration.ofMinutes(3L)): TTL을 30분으로 설정한다.serializeKeysWith: 캐시 키를 문자열로 직렬화한다.serializeValuesWith: 값을 JSON 형식으로 직렬화한다.transactionAware(): 이 옵션은 트랜잭션이 성공적으로 완료된 후에만 캐시를 업데이트하도록 보장할 수 있다.
🔓 캐싱 활성화
애플리케이션에서 캐싱을 사용하려면 main 클래스에 @EnableCaching 어노테이션을 추가해야 한다.
1
2
3
4
5
6
7
8
9
@EnableCaching
@EnableJpaAuditing
@SpringBootApplication
public class LearnredisApplication {
public static void main(String[] args) {
SpringApplication.run(LearnredisApplication.class, args);
}
}
@EnableCaching은 Spring의 캐시 인프라를 활성화하며, 캐시 관련 어노테이션(@Cacheable, @CachePut, @CacheEvict 등)을 인식하고 처리할 수 있게 한다.
💼 캐싱 적용
캐싱을 적용할 부분은 다른 회원 프로필을 조회하는 로직이다. 이 서비스는 자주 조회되는 정보지만 자주 바뀌는 것은 아니기 때문에 여기에 캐싱을 하기로 했다. (참고로 OAuth2 프로젝트에는 없는 기능이다. Redis 학습을 위해 만든 API다.)
🔍 사용자 프로필 조회 (@Cacheable)
1
2
3
4
5
6
7
8
9
10
11
12
@Cacheable(cacheNames = "userCache", key = "#id + ':profile'", cacheManager = "cacheManager")
@Transactional(readOnly = true)
public UserResponse getUserProfileById(Long id) {
User findUser = userRepository.findById(id)
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
return UserResponse.builder()
.nickname(findUser.getNickname())
.email(findUser.getEmail())
.role(findUser.getRole().name())
.picture(findUser.getPicture())
.build();
}
@Cacheable 어노테이션의 의미:
cacheNames = "userCache": ‘userCache’라는 이름의 캐시를 사용한다.key = "#id + ':profile'": 캐시 키를 사용자 ID와 ‘profile’ 문자열의 조합으로 생성한다.cacheManager = "cacheManager": 앞서 정의한 캐시 매니저를 사용한다.
@Cacheable의 동작 방식:
- 메서드가 호출될 때, 먼저 캐시에서 해당 키로 데이터를 찾는다.
- 캐시에 데이터가 있으면, 메서드 실행 없이 캐시된 데이터를 즉시 반환한다.
- 캐시에 데이터가 없으면, 메서드를 실행하고 그 결과를 캐시에 저장한 후 반환한다.
✏️ 사용자 프로필 업데이트 (@CacheEvict)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@CacheEvict(cacheNames = "userCache", key = "#principal.getId() + ':profile'")
@Transactional
public UserResponse updateUserProfile(UserPrincipal principal, UserUpdateRequest request) {
User findUser = userRepository.findById(principal.getId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
User updateUser = findUser.updateProfile(request);
return UserResponse.builder()
.nickname(updateUser.getNickname())
.email(updateUser.getEmail())
.role(updateUser.getRole().name())
.picture(updateUser.getPicture())
.build();
}
@CacheEvict 어노테이션의 의미:
cacheNames = "userCache": ‘userCache’에서 데이터를 제거한다.key = "#principal.getId() + ':profile'": 제거할 캐시 항목의 키를 지정한다.
@CacheEvict의 동작 방식:
- 메서드가 실행되기 전에 지정된 캐시에서 해당 키의 데이터를 제거한다.
- 이후 메서드가 정상적으로 실행된다.
- 결과적으로 사용자 프로필이 업데이트되면 기존의 캐시된 데이터는 삭제된다.
이를 통해 사용자가 프로필을 업데이트 하면 캐시에 저장된 데이터가 삭제되어 다음 조회 시 새로운 데이터가 캐시에 저장되어 정합성 문제를 해결할 수 있다.
📊 성능 비교
Redis 캐싱 적용 전후의 성능을 비교해 보았다.
| 지표 | 캐싱 적용 전 | 캐싱 적용 후 | 변화율 |
|---|---|---|---|
| 평균 응답 시간 (ms) | 3610 | 1918 | -46.9% |
| 최소 응답 시간 (ms) | 2 | 1 | -50% |
| 최대 응답 시간 (ms) | 15263 | 7130 | -53.3% |
| 표준 편차 (ms) | 2264.91 | 1290.19 | -43% |
| 에러율 (%) | 0.000 | 0.000 | 변화 없음 |
| 처리량 (요청/초) | 229.71 | 355.42 | +54.7% |
| 수신 데이터량 (KB/초) | 100.94 | 153.41 | +52% |
| 송신 데이터량 (KB/초) | 52.72 | 81.57 | +54.7% |
| 평균 응답 크기 (Bytes) | 450.0 | 442.0 | -1.8% |
개선사항:
-
응답 시간 개선: 평균 응답 시간이 46.9% 감소했다.
-
처리량 증가: 초당 처리할 수 있는 요청 수가 54.7% 증가했다. 이는 서버의 전반적인 성능과 확장성이 향상되었음을 의미한다.
-
일관성 향상: 응답 시간의 표준 편차가 43% 감소했다. 이는 응답 시간의 일관성이 개선되어 사용자에게 더 안정적인 서비스를 제공할 수 있게 되었음을 나타낸다.
-
리소스 효율성: 평균 응답 크기가 약간 감소했지만, 처리량이 크게 증가했다. 이는 동일한 네트워크 리소스로 더 많은 요청을 처리할 수 있게 되었음을 의미한다.
Leave a comment