
위 화면은 우리 서비스에서 볼 수 있는 검색 기록 화면이다. 여태까지는 기능이 구현 되지 않아서 그냥 더미데이터가 들어가 있었다. redis 를 도입하면서 구현을 완성 했다.
redis에 대해서는 다른 포스트에서 더 자세히 작성할 예정이다.
검색 기록을 MySQL DB에 저장하고 관리하려 했지만 검색을 진행할 때 마다 사용자와 연관된 검색기록을 DB로 부터 조회하는 로직 자체가 오버헤드가 클 것 이라고 판단하고 redis도입을 생각해 보았다.
redis는 in-memory DB로, SSD와 같은 저장장치에 존재하는 것이 아닌 RAM에서 데이터를 관리 하기 때문에 속도가 매우 빠르다.
또한 key-value 조합으로 데이터를 관리하는 NoSQL이다.
가장 먼저 로컬 환경에서 redis를 실행시키고 검색기능을 구현해보았다.
build.gradle에 다음의 의존성을 추가해준다.
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
application.yml 파일은 다음과 같다.
spring:
data:
redis:
port: 6379
host: localhost
물론 로컬 환경에서 redis-server를 다운로드 하고 실행해야 한다.
configuration 파일은 다음과 같다.
@Configuration
@EnableCaching
public class RedisConfig {
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.host}")
private String host;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration();
redisConfiguration.setHostName(host);
redisConfiguration.setPort(port);
return new LettuceConnectionFactory(redisConfiguration);
}
// 검색 기록 템플릿
@Bean
public RedisTemplate<String, SearchLog> redisTemplate() {
RedisTemplate<String, SearchLog> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(SearchLog.class));
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(SearchLog.class));
return redisTemplate;
}
}
나의 경우에는 SearchLog 라는 일종의 엔티티를 리턴값으로 사용할 예정이었기에 위와 같이 설정했다.
SearchLog 부분에는 Object, String 등의 자료형이 들어가도 된다.
실제 검색 로그를 구현한 부분은 다음과 같다.
@Service
@RequiredArgsConstructor
public class SearchLogService {
private final MemberRepository memberRepository;
private final RedisTemplate<String, SearchLog> redisTemplate;
public void saveRecentSearchLog(Long memberId, SearchLogDto request) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BusinessExceptionHandler("member not found", ErrorCode.NOT_FOUND_ERROR));
String now = request.getCreatedAt();
String key = "SearchLog:" + member.getUniqueId();
SearchLog value = SearchLog.builder()
.name(request.getName())
.createdAt(now)
.build();
// 기존에 같은 이름의 검색 기록이 있는지 확인하고 제거
List<SearchLog> logs = redisTemplate.opsForList().range(key, 0, -1);
for (SearchLog log : logs) {
if (log.getName().equals(request.getName())) {
redisTemplate.opsForList().remove(key, 1, log);
}
}
Long size = redisTemplate.opsForList().size(key);
if (size == 10) {
// rightPop을 통해 가장 오래된 데이터 삭제
redisTemplate.opsForList().rightPop(key);
}
redisTemplate.opsForList().leftPush(key, value);
}
public List<SearchLogDto> findRecentSearchLogs(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BusinessExceptionHandler("member not found",ErrorCode.NOT_FOUND_ERROR));
String key = "SearchLog:" + member.getUniqueId();
List<SearchLog> logs = redisTemplate.opsForList().
range(key, 0, 10);
List<SearchLogDto> searchLogDtoList = new ArrayList<>();
for (SearchLog log : logs) {
SearchLogDto searchLogDto = SearchLogDto.fromLog(log.getName(), log.getCreatedAt());
searchLogDtoList.add(searchLogDto);
}
return searchLogDtoList;
}
}
두가지의 로직이 존재한다. redis에 검색 기록을 저장하는 로직과 redis로 부터 조회하는 로직.
redis에 저장할때에는 list 형식으로 저장하여 최대 개수를 10개로 정한 다음 leftPush()와 rightPop() 으로 구현했다.
추가적으로 이전에 검색했던 내역이라면 삭제 후 맨위에 다시 추가해주는 로직을 사용했다
검색을 진행하는 로직에서 위의 메서드를 사용하여 검색 기록에 추가해주었다!

검색한 기록들이 잘 뜨는것을 볼 수 있다.