본문 바로가기

백엔드 개발/Spring

[spring] redis로 caching해서 dbms의 부하 줄이기 - 1

반응형

평소에 Nosql에 관심이 많았는데. 이번에 퇴사하고 개인프로젝트를 하면서 redis로  caching을 해보기로 결심했다.

본래 목적은 캐싱이었는데 사용자의 이벤트 조회 로그를 저장해서 서비스의 메인에 내가 조회한 상품을 노출하는 기능을 먼저 구현하게 되었다. (이것도 차후 글을 올려야겠다.)

로그를 기록하고 메인에 노출하는 기능을 구현하면서 레디스가 제공하는 여러 자료형에 대해 이해하고 성능도 테스트해볼 수 있었는데

역시 In-memory db라서 I/O 성능이 꽤 좋다고 느꼈었다.

그래서 전격적으로 db의 데이터를 redis로 옮겨서 캐싱을 해보기로 결정했다.


1. 어떤 자료형으로 구현할 것인가?

레디스가 제공하는 자료형은 set, sorted set,hash, list, string으로 네가지가 있다.

모두 이름 그대로다.

list는 queue나 stack의 자료구조를 구현하는데 유용하다.

string은 key - value의 단순한 구조이다.

set이나 sorted set은 수학의 집합의 개념을 구현한 것이고 sorted set은 score이라는 값을 넣어서 set을 정렬할 수 있다.

sorted set은 score의 크기 순서대로 정렬되고 count() 함수나 reverse range 같은 기능을 제공하므로 게임의 랭킹 기능을 구현하기 적합해 보인다.

hash는 key - field(복수) - value 구조로 object 형태를 구현할 수 있다.

hash를 이용하면 최적화를 통해서 많은 데이터도 적은 용량으로 저장할 수있다.

하나의 key에 대해서 1000개의 필드가 가장 최적화되는 필드의 숫자라고 하니 주의가 필요하다.


2. list vs hash

최근에 사용자가 조회한 상품을 기록해 메인에 노출하는 기능(자꾸 언급하니깐 조만간 이것에 관해서 꼭 적어야할 듯...)을 구현하면서 sorted set을 사용해서 이번에는 hash나 list를 이용해보기로 했다.

우선 spring data redis로 hash를 jpa처럼 entity로 등록해서 repository로 간편하게 관리할 수 있어서 한번 테스트를 해보기로 했다.


spring data redis 설정은 생략함

저는 아래 두개글을 참고해서 설정했었으니 참고

http://kingbbode.tistory.com/25

https://jojoldu.tistory.com/297


entity와 repository 설정하는 것은 아래 spring data redis document를 참고

https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis.repositories.usage 


3. repository와 entity 설정

@RedisHash가 jpa에서 entity에 해당하는 어노테이션이고 어노테이션에 들어가는 value인 "EventId"는 @Id값과 결합해서 key를 생성할 때 사용하는 값이다.

그리고 @Id는 key를 식별할 떄 사용하는 고유한 값으로 @RedisHash와 결합해서 key를 생성하는 한다.

이 예에서는 EventId::[@id값]이 들어간다.


위는 jpa와 구조가 똑같은데. EventLogRepository가 crudRepository를 super class로 두고 확장(상속)하고 있다.

이로써 save(), findAll(), findById()같은 메소드를 사용해서 redis의 hash 자료형을 간편하게 관리할 수 있다.



4. 테스트 코드 작성과 성능 테스트

위 테스트 케이스는 등록한 entity 400개를 생성하고 모두 조회하는 간단한 테스트 코드이다

모든 데이터를 조회하는 성능을 확인해보자.



400개를 조회하는데 약 4초정도 걸린다.

RDB와 한번 비교해보자.

현재 내 mariaDB에는 공연관련 데이터 튜플이 400건정도 있다.

한번 조회해서 성능을 테스트해보겠다.


엥? 399건에 대해서 0.715초다..뭔가 이상하다.

내가 알기로는 in-memory db의 성능이 더 좋아야하는데..

여기서 우리는 hash에 대해서 생각해볼 필요가 있다.

hash 구조는 key를 hash function을 이용해서 index를 찾는 과정을 거친다.

그렇다면 redis에 저 entity는 어떤 형태로 저장되고 있을까?





























redis-cli에 접속해서 keys *로 방금 저장된 hash들을 조회해보니 key가 400개 저장되어있다.

그렇다면 해쉬데이터 400개를 조회할 때 주어지는 key를 hash 함수로 index를 구하는 변환과정을 400번 거친다는 것을 예측할 수 있다.

([2018.11.22 추가]

redis에서 위와 같은 모든 hash table을 구할 때는 먼저 key를 구하고 field와 value를 구하는 과정을 거친다.  keys * -> hget key  이런식으로 두번의 과정을 거치는 것을 400번 진행한다. 그에 반해 list는 최초 key 하나를 입력하면 list의 값들을 한번에 가져온다)

RDB에서는 index를 타므로 그리고 ([2018.11.22수정] 이 경우 index range scan이 아니므로 index를 탄다는 의미는 잘못됐다. full-table-scan이다.)조회하는 값이 400개로 비교적 적기때문에 성능이 좋은 것 같다.

굳이 hash로 object를 저장할 필요는 없어보인다.

캐싱하는 키의 유효기간을 일주일로 설정해서 크롤링하는 날인 일주일 이후에는 없애버릴 것이기 때문이다.

그리고 검색조건에 따른 이벤트의 결과를 캐싱할 것인데 이런 형태로 이벤트를 저장하면 조건에 대한 검색을 한번 더 수행해야한다.

5. 그렇다면 list를 사용해보자!

위의 테스트 코드를 실행해보면 아래와 같다.

조회 성능이 많이 좋아졌다.

여기서 2초나 걸리는 것은 json을 자바 빈으로 변환하는 과정을 거치기 때문인 것으로 추정된다.

(redis-cli로 같은 명령어를 치면 순식간에 조회되는 것을 확인할 수 있음. String data를 조회하기 때문)

실제로 레디스로 컨텐츠를 조회할 때 400개를 한번에 조회할 일은 없고 5개씩 조회할 예정이므로 5개를 한번 조회해보자.



별차이 없음...OTL...(두번쨰 실행해보면 1.7초대로 시간이 줄어들기는한다...ㅠ)

여기서 우리는 redis를 왜 사용하는지 어떤 환경에서 도입하는지 생각해볼 필요가 있다.

실제 애플리케이션에서는 복잡하고 많은 데이터를 조회하는 쿼리들을 많이사용한다.

예를 들어서 join이나 subquery를 함께 이용하는 쿼리를 사용해서 40000개를 조회한다고 생각해보자 테이블의 column수가 10개가 넘는다.

이 경우에 db에 걸리는 부하가 상당할 것이다.

그러나 레디스에 dbms에 저장된 40000건의 데이터를 json 형태로 저장해서 요청이 올 때 마다 사용자에게 제공한다고 생각해보자 이런 경우 DB에 부하는 확실히 줄어들고 db에 저장된 데이터를 java bean으로 맵핑하는 과정도 줄일 수 있다. 


아래 글에 매우 잘 정리되어 있으니 읽어보면 도움이 될듯하다.

http://www.kosta.or.kr/mail/2014/download/Track2-8_2014Architect.pdf

실제로 80000건의 데이터를 저장하고 한번 조회해보니 위와같이 나왔다.

(5건을 조회했을 때보다 더 시간이 단축되었는데. 5건, 400건, 80000건의 조회 속도가 별차이 없다는 걸로 이해하면 될 듯하다.)


이 글의 결론은 간단한 프로젝트에서는 RDB가 더 빠를 수 있다는 것...

서버의 아키텍처를 구성할 떄 애플리케이션의 성격을 고려해야하는 이유이다...

내가 redis에 대해서 아직 잘 몰라서 틀린 부분이 있을지 몰라서

내가 공부하면서 참고한 글들을 첨부했으니 혹시나 이 글을 읽는 분들은 꼭 함께 읽어보셨으면 좋겠다.


반응형