사용자가 아이템 정보를 클릭하여 확인을 할 때마다 히스토리를 저장하고, 이를 최신순으로 정렬하여 보여주는 기능을 Spring Boot와 Redis를 활용하여 구현하는 방법에 대해 알아보겠습니다.
Redis의 Sorted Set(ZSet) 자료구조는 중복 없이 유니크한 String Value를 저장하는 Set과 유사하지만, 각 데이터에 점수(Score)를 부여하여 Score를 통해 데이터를 정렬할 수 있는 기능을 제공합니다.
즉, Set 자료구조 특성에 Score라는 속성을 추가로 갖고 있어 데이터 순서를 정렬할 수 있습니다. 이는 사용자가 최근 본 아이템 목록을 구현하는 데 매우 유용한 구조입니다. Socre를 활용하여 시간 순 또는 다른 기준에 따라 요소를 정렬할 수도 있습니다.
아이템 ID를 Key로, 아이템 상세 조회 시간을 Score로 저장하면, 최근 본 아이템 순으로 정렬할 수 있습니다.
이제 Redis의 Sorted Set와 Spring Boot를 이용해서 구현하는 방법을 알아보겠습니다.
build.gradle 혹은 pom.xml파일에 redis에 대한 의존성 라이브러리를 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: localhost
port: 6379
@EnableRedisRepositories
: Spring Data Redis 리포지토리를 활성화redisTemplate()
: Redis 서버와의 통신을 위한 RedisTemplate
객체를 설정하며, 이 객체를 사용하여 Redis의 다양한 데이터 조작 기능을 쉽고 효율적으로 사용할 수 있도록 합니다.@Configuration
@EnableRedisRepositories
public class RedisConfig {
// application.yml 파일에서 설정한 값을 각 필드에 주입
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
// Redis 서버에 연결하기 위한 설정
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key 직렬화 방식 정의
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 저장되는 데이터값의 JSON 직렬화 방식을 정의
return redisTemplate;
}
}
이제 사용자가 아이템을 조회할 때마다 해당 아이템의 ID와 조회 시간을 Socre로 사용하여 Sorted Set에 저장하고, 이를 조회하는 코드를 살펴보겠습니다.
@Service
@RequiredArgsConstructor
public class RedisService {
private static final String USER_VIEW_ITEMS = "user:%s:view_items";
private static final long SECONDS_IN_A_WEEK = 7L * 24 * 60 * 60; // 최근 7일 동안 저장
private final RedisTemplate<String, String> redisTemplate;
// 사용자별로 아이템의 ID와 조회 시간을 저장
public void addItemRecentlyViewed(Long userId, Long itemId) {
String key = getUserViewedKey(userId);
double score = getCurrentTimeInSeconds();
redisTemplate.opsForZSet().add(key, String.valueOf(itemId), score);
}
// 사용자의 최근 본 아이템 목록 조회
// 최근 7일 동안 count 개수만큼 조회
public Set<Long> getViewData(Long userId, int count) {
String key = getUserViewedKey(userId);
double minScore = getCurrentTimeInSeconds() - SECONDS_IN_A_WEEK;
return convertSet(redisTemplate.opsForZSet().reverseRangeByScore(key, minScore, Double.MAX_VALUE, 0, count));
}
// Object Set -> Long Set 변환
private Set<Long> convertSet(Set<Object> itemIds) {
return itemIds.stream()
.map(itemId -> Long.parseLong((String) itemId))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
// userId를 사용하여 고유한 Redis 키를 생성
private String getUserViewedKey(Long userId) {
return String.format(USER_VIEW_ITEMS, userId);
}
// 현재 시간을 밀리초에서 초 단위로 변환
private double getCurrentTimeInSeconds() {
return System.currentTimeMillis() / 1000.0;
}
}
addItemRecentlyViewed()
: 사용자가 아이템을 조회할 때마다 해당 아이템의 ID와 조회 시간을 Socre로 사용하여 Sorted Set에 저장합니다. key는 사용자 별로 고유한 ID를 의미하며, Socre는 현재 시간을 초 단위로 변환한 값입니다. Redis는 데이터를 저장할 때 키(key)와 값(value) 모두 문자열 형태로 저장하는 것이 일반적이기 때문에 Long 타입의 itemId
를 문자열로 변환해주었습니다.getRecentlyViewedItems()
: 사용자의 최근 본 아이템 목록을 조회합니다. minScore
로 최소 점수(시간)와 조회할 아이템의 개수(count
)를 지정하여, 특정 기간 동안 조회한 아이템을 가져올 수 있습니다. 조회된 아이템 ID들은 Long
타입으로 변환 후 LinkedHashSet
에 저장하여 순서를 유지합니다.저는 아이템 목록 조회 반환 타입을 Set<Long>
으로 지정하여 itemId
목록을 반환하였습니다. 이때 주의할 점은 Redis에서 데이터를 조회할 때, 반환되는 집합(Set)의 원소들은 Object
타입입니다. 이때 Long
타입의 아이템 ID 목록을 사용하려면, 명시적으로 Object
에서 Long
으로 변환하는 과정이 필요합니다. 저는 이 과정을 convertSet
메서드에 작성했습니다.
@Service
@RequiredArgsConstructor
public class ItemsService {
private static final int MAX_COUNT = 30; // 최대 30개
private final RedisService redisService;
public void addViewItem(Long itemId, Long userId) {
redisService.addItemRecentlyViewed(userId, itemId);
}
public Set<Long> getViewItems(Long userId) {
return redisService.getViewData(userId, MAX_COUNT); // 30개 조회
}
}
addViewItem()
: 사용자가 조회한 아이템 Id와 사용자 Id를 통해 최근 본 목록에 추가합니다.getViewItems()
: 최대 30개를 지정하여 원하는 개수만큼의 데이터를 조회합니다.이제 실제로 아이템을 조회하여 최신순으로 목록이 정렬되는지 확인해보겠습니다.
Redis의 Sorted Set(ZSet)은 데이터에 점수(Score)를 부여하여 Score를 통해 데이터를 정렬할 수 있는 기능을 제공합니다.
Socre를 활용하여 시간 순 또는 다른 기준에 따라 요소를 정렬할 수도 있습니다.