사용자가 아이템 정보를 클릭하여 확인을 할 때마다 히스토리를 저장하고, 이를 최신순으로 정렬하여 보여주는 기능을 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를 활용하여 시간 순 또는 다른 기준에 따라 요소를 정렬할 수도 있습니다.