• 프로젝트의 중요기능 중 검색 부분이 Community-Server 프로젝트의 핵심 기능이라고 생각했습니다.
  • 검색 성능을 높이기 위해 Redis(NO-SQL)을 적용하였습니다.
    (RDB와 NO-SQL 차이가 궁금하신 분은 RDB와 No-SQL의 차이  참고 부탁드립니다.)
  • Spring-boot에서 제공해 주는 Spring boot redis 라이브러리를 사용했습니다.

설치 과정은 Redis 설치 및 GUI 적용 글을 보고 설치 후 적용하시면 됩니다.

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
  • application.properties에 적용
//build.gradle
spring.redis.host=localhost
spring.redis.port=6379

expire.defaultTime=60

 

redis를 연결하기 위한 Host IP, Port를 설정

expire.deaultTime 은 redis 쿠키를 유지하는 시간(TTL, Time To Live)으로 값이 1당 1초입니다.

만료시간은 후에 대용량 트래픽에 대한 성능테스트를 진행 후 적절한 시간을 설정할 예정입니다.


 Spring Date Redis를 통해 Lettuce, Jedis라는 두 가지 오픈소스 Java 라이브러리를 사용합니다.

 

출처 : https://jojoldu.tistory.com/418

Jedis

  • 장점
    • Jedis는 사용하기 쉬운 API를 제공하며, 러닝커브가 낮습니다.
  • 단점
    • Jedis는 동기식 API만을 제공하기 때문에, 비동기 처리가 필요한 환경에서는 부적합할 수 있습니다.
    • Jedis는 connection pool 관리에 대한 기능이 상대적으로 떨어질 수 있습니다.

Lettuce

  • 장점
    • Lettuce는 비동기 API를 지원하여 높은 성능과 확장성을 제공합니다.
    • Lettuce는 connection pool을 효과적으로 관리하여 성능을 최적화합니다.
    • Lettuce는 Reactive 프로그래밍을 지원하므로, Reactor나 RxJava와 같은 라이브러리와 통합이 용이합니다.
  • 단점
    • Jedis에 비해 러닝커브가 높습니다.

 

두 개의 비교를 찾으며 성능적으로 차이가 있다고 조사하여 Lettuce가 Jedis보다 성능적으로 TPS및 응답속도 등이 높으며, 비동기를 지원하여 lettuce을 채택하여 사용하였습니다.


또한, Spring Data Redis는 Redis에 두 가지 접근 방식을 제공합니다.

Redis Template

  • Redis Template을 사용하면 특정 Entity 뿐만 아니라 여러 가지 원하는 타입을 넣을 수 있습니다.(List, Map, Hash 등)

Redis Repository

  • Spring Data Redis의 Redis Repository를 이용하면 간단하게 Domain Entity를 Redis Hash로 만들 수 있습니다.

 

확장성을 위해 RedisTemplate을 사용하였습니다.

이는 레디스에서 제공해 주는 자료구조(List, Hash, Map, Object 등)를 구체적으로 명시하여 사용하기 때문에 채택하였습니다.

 

 

  • RedisTemlate Key-Value 자료구조가 <String, Object> 인 이유
    • 검색 API 의 성능을 위해 Redis의 캐시를 사용하고 있습니다.
    • 이를 동일한 검색시 동일한 결과값을 주기 위해 Key는 검색조건들을 String 형태고 넣었으며, 결과가 항상 동일한 객체로 반환되지 않아 이를 고려하여 Object 형태로 처리하였습니다.
  • cacheManger가 필요한 이유

 

 

 

 

@Cacheable

  • 캐시 기능 수행
  • 캐시가 존재하면 캐시의 정보를 가져오고 존재하지 않을 시 캐시 등록
  • 스프링은 AOP 기반으로 캐시가 작동
  • 어노테이션으로 AOP설정을 할 수 있어 간편하게 사용

@CacheEvict

  • 캐시를 적절한 시점에 제거
  • 키에 해당하는 캐시만 제거
  • 캐시에 저장된 값을 모두 제거 시 allEntires = true 설정

동일한 검색시 Cacheable을 통해 캐시을 등록하였으며 검색 결과가 변하는 게시글의 CUD가 발생할 경우 CaheEvict을 통해 삭제하도록 구성하였습니다.


  • Redis를 사용하기 위해서는 Key-Value 형식의 설계를 잘 구성해야합니다.
  • 검색시 검색단어를 통해 검색 내용을 알 수 있듯이 Key에 검색 단어를 String 문자열으로 넣어 구성하였습니다.
  • 구체적인 내용은 검색 API에 저장되는 유니크한 Key를 만들기 위해 검색 한 카테고리 번호, 게시글 제목, 게시글 내용, 게시글 생성날짜, 추천수 등의 값을 조합하여 만들었습니다.
//PostDTO.java
@Override
public String toString() {
    return "PostDTO" + getCategoryNumber() + getPostName() + getContents() + getCreateTime() + getSuggestionCount();
}

실제 로컬 redis에 저장된 key-value 구조


  • Mybatis의 검색 기능을 위해서 쿼리문을 동적 쿼리로 구성하기 위한 방법으로 <if> 절을 이용하여 쿼리문을 구성하였더니 쿼리의 and 조건으로 인해 여러 개의 Mapper가 만들어지는 문제가 발생했습니다.
  • 이는 가독성이 떨어지며 하드코딩이라 느껴 다른 방안을 찾아봤습니다.
  • MyBatis 공식문서를 참조하여 해결 방안을 찾았습니다. 
  • <where> ~ <if > ~ 문법들을 이용하여 하나의 mapper로 리팩토링 시켰습니다.
  • 문법을 사용하게 되면 첫 Where 절의 "and"를 제외하기 때문에 문제없이 사용이 가능하였습니다.

 

 

전체 코드가 궁금하신 분은 community-server 프로젝트를 참고해 주세요. 

 

Issue/10 ADMIN Controller 개발 by gamsayeon · Pull Request #13 · j-lab-edu/community-server

 

github.com


결과

  • 데이터가 디스크에 저장하지 않고 메모리에 저장하여 읽기 성능이 향상되었습니다.
  • 실제 동일한 검색시 Postman기준 응답시간이 3.58s -> 51ms 으로 향상됨을 확인하였습니다.
  • Redis를 적용하기 전 No-Sql개념을 탐구함으로써 가변적인 구조로 데이터 저장, 데이터 설계 비용 감소 등의 개념을 알게 되었습니다.

느낀점

  • No-SQL 중 Redis를 사용해보았습니다. 다른 NoSQL 데이터베이스가 무엇인지에 대한 궁금증이 생겼습니다. 향후 다른 No-SQL을 활용한 프로젝트를 진행할 계획입니다.
  • 또한, RDB와 No-SQL의 정확한 차이점에 대해 자세히 알아보고 정리할 예정입니다.
  • Redis를 사용할 때 Lettuce라는 오픈 라이브러리를 선택하여 활용했습니다. Jedis를 사용하는 경우와의 성능의 차이를 직접 확인하고자 합니다.
  • 향후 서버를 증설시 일관성을 유지하는 과정으로써 master-slave 환경에 대해 자세히 조사하여 정리할 예정입니다.

참고

 

+ Recent posts