스프링에서 캐시(caffeine) 적용하기

이진우·2024년 1월 31일
0

스프링 학습

목록 보기
20/41

도입계기

메인페이지에서 tmdb의 open API를 통해서 트렌디한 영화 데이터(poster, 제목,장르 이름 등) 을 보여주는 부분이 있다.

이 부분은 Week Trending 영화 데이터와 Day Trending 영화 데이터를 함께 보여준다.

예를 들면 아래의 JSON 형태를 반환한다.

 "data": {
   "programTrendingDayInfos": [
     {
       "programId": 21,
       "title": "The Beekeeper",
       "genreName": "액션",
       "backDropPath": "/4MCKNAc6AbWjEsM2h9Xc29owo4z.jpg",
       "createdYear": "2024",
       "rating": "0.0"
     },
     ...
   ],
   "programTrendingWeekInfos": [
     {
       "programId": 32,
       "title": "아쿠아맨과 로스트 킹덤",
       "genreName": "액션",
       "posterPath": "/eDps1ZhI8IOlbEC7nFg6eTk4jnb.jpg",
       "createdYear": "2023",
       "rating": "0.0"
     },
     ...
   ]
 }
}

API 의 한계(관련 링크:)로 인해서 DB에 한번에 영화 데이터를 저장하여 우리 DB에 저장된 데이터를 보여주는 방식이 아니라 메인 페이지 또는 검색 페이지에서 open api를 통해 데이터를 받아오고 우리 DB에 저장된 데이터의 경우는 내비두고 DB에 저장되지 않은 프로그램의 경우는 우리 비즈니스 로직 상 편리하게 하기 위해서 DB에 저장하였다.

그러다 보니 메인 페이지를 호출할때는 항상 open api 를 통해 데이터를 넘겨 받는 작업이 필수 적이였다.

또한
https://developer.themoviedb.org/docs/popularity-and-trending

관련 api 의 설명을 보면

day trending 프로그램과 week trending 프로그램이 갱신되는 시간 간격은 실시간으로 반영되는 것이 아니라 day,week 의 시간간격으로 반영되는 것이므로 하루동안은 무조건 같은 값이 반환되나는 이야기가 된다.

도입계기 정리

1) open api 를 통해 데이터를 불러오기 때문에 기존 DB에서 불러오는 것보다 속도가 안좋음

2) open api 의 트렌딩이 실시간으로 반영되는 트렌딩이 아니라 최소 하루 간격으로 바뀌므로 하루 동안은 매번 같은 데이터 값이 반환된다.

=> 많은 사용자가 이용할 수 있는 메인페이지가 이렇게 되면 같은 데이터의 읽기 동작이 매번 수행, 매번 쿼리 수행(db에 저장되어 있는지 확인) 및 open api 요청이 발생하므로 비효율적이다라는 생각을 하였다. 또한 이 과정을 적절히 수행할 수 있는 것은 캐시라고 생각을 하여서 캐시를 적용하였다.

어떤 캐시를 사용할까?

로컬 캐시 vs 글로벌 캐시

캐시는 크게 로컬 캐시 와 글로벌 캐시로 나눈다.

1)글로벌 캐시

글로벌 캐시는 로컬 캐시 자체와 비교하면 성능이 느리지만 로컬 캐시는 분산 시스템에서는 데이터 일관성, 정합성이 깨질수도 있는 문제점이 있기 때문에 분산해서 시스템을 구축하였다면 외부에 레디스 같은 원격 캐시 아키텍쳐를 두는 것이 바람직하다.

2) 로컬 캐시

로컬 캐시는 글로벌 캐시와 다르게 네트워크를 타지 않기 때문에 더욱 속도가 빠르지만 반대로 분산형 시스템에서 안좋은 점이 많다고 생각한다.

참고 blog: https://dalsacoo-log.tistory.com/entry/Local-Cache%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-Spring-Cache-CaffeineEhcache-Redis-Client-side-caching

현재 한 EC2 내부에서 프로젝트를 돌리고 있기 때문에(분산형 시스템이 아님) 로컬 캐시를 이용하는 것이 바람직하다고 생각했다.

로컬 캐시 중 어떤 캐시를 사용할까?

가장 성능이 좋다는 cafeine Cache를 사용하고자 했다.

https://github.com/ben-manes/caffeine?tab=readme-ov-file

에서 성능이 좋다는 것을 확인할 수 있으니 확인하고

나중에 기회가 된다면 다른 캐시를 직접 비교해가며 공부해가고 싶다.

다른 캐시에 대한 설명이 있는 부분은 다른 블로그에 많이 있어서 참고하면 좋을 것 같다.

우리 상황에서 적용이 가능할까?

여기서부터는 확실하지 않기 때문에 이 부분도 따로 공부해야 한다. ㅎ

캐시는 기본적으로 데이터를 메모리에 올려놔서 빠른 반환이 가능하다.

그러면 우리 EC2 에서 돌릴 수 있는지 궁금했다.

따라서 ununtu 내에서 free -h 명령어를 이용해서 가용 메모리 를 확인했다.

흐음~~

available 만 보면 뭔가 더 추가할 수 있을 것만 같고, free를 보면 뭔가 좀 빠듯해보인다.

관련 블로그를 찾아봤지만 완벽히 이해하지는 못했다. 하지만

https://jaynamm.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%AC-Buffer-%EC%99%80-Cache-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0#2.%20Cache
:available :swapping 없이 사용가능한 메모리 남은 영역

https://mozi.tistory.com/424
:남는 메모리는 버퍼/캐시 영역으로 활용

1) 캐시 용도의 메모리를 반환할 만큼 하고도 메모리가 없다:라면
SWAP 영역 사용(Disk의 공간을 메모리처럼 사용, DIsk IO 발생=>성능저하

2) 메모리가 충분하다:
캐시 메모리를 반환 후 요청한 프로세스 메모리로 할당

을 토대로 보았을 때 available을 남은 메모리 양으로 생각하였다.
따라서 메모리가 이 정도면 괜찮다는 결론을 내리고 이 값을 캐싱하려고 했다.

적용 방법

build.gradle에 설정파일 추가

 //캐싱 관련 추가
    implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1'

여러 종류 캐싱할수 있도록 Enum Type 생성

@Getter
public enum CacheType {

   PROGRAMS("programTrending", 2 * 60, 100);

   CacheType(String cacheName, int expiredAfterWrite, int maximumSize) {
       this.cacheName = cacheName;
       this.expiredAfterWrite = expiredAfterWrite;
       this.maximumSize = maximumSize;
   }

   private String cacheName;
   private int expiredAfterWrite;
   private int maximumSize;
}
  • cacheName: 캐시를 적용하기 편하게 이름을 지정합니다.
  • expiredAfterWrite: 캐시 만료 시간을 설정합니다. 현재는 테스트를 위해 2분으로 설정하였습니다.
  • maximumSize: 캐시에 적용되는 엔티티의 수를 제한합니다.

    maximumSize 는 https://www.baeldung.com/java-caching-caffeine 을 참고하였습니다. 여기에서 정보를 얻으시는 것을 추천합니다.

    관련 테스트 코드 및 적용 방법을 알려줍니다.

    저같은 경우는 1로 설정해도 적용이 되는 것을 확인하였으나 쫄려서 100으로 일단 설정했습니다.

    캐싱 Config 설정

    
    @Configuration
    @EnableCaching
    public class CacheConfig {
    
        @Bean
        public CacheManager cacheManager() {
            SimpleCacheManager cacheManager = new SimpleCacheManager();
            List<CaffeineCache> caches = Arrays.stream(CacheType.values())
                .map(
                    cache -> new CaffeineCache(cache.getCacheName(),
                        Caffeine.newBuilder()
                            .expireAfterWrite(cache.getExpiredAfterWrite(), TimeUnit.SECONDS)
                            .maximumSize(cache.getMaximumSize())
                            .build()
                    )
                )
                .collect(Collectors.toList());
            cacheManager.setCaches(caches);
            return cacheManager;
        }
    }

    간단하게 우리가 설정한 카페인 캐시를 적용하는 CacheConfig 입니다.

    메서드 위에 적용

    이제는 진짜 다 끝났습니다.

    @Override
        @Cacheable(cacheNames = "programTrending")
        @Transactional
        public TrendingResponseDto showTrending() {

    우리의 캐시가 필요한 메서드 이름 위에 @Cacheable 을 붙여주고 적용하고 싶은 cacheNames 를 지정하면 끝입니다.

    테스트 적용

    간단하게 실제로 쿼리가 나가는지 안나가는지 실행해보면 캐시가 적용되는지 확인할 수 있습니다.

    처음 시도

    
            program0_.program_id as program_1_7_,
            program0_.average_rating as average_2_7_,
            program0_.created_year as created_3_7_,
            program0_.poster_path as poster_p4_7_,
            program0_.review_count as review_c5_7_,
            program0_.title as title6_7_,
            program0_.tm_db_program_id as tm_db_pr7_7_,
            program0_.type as type8_7_ 
        from
            program program0_ 

    쿼리가 발생함.

    이후 시도는 쿼리 발생안함!

    2024-02-01 03:56:23.171  INFO 15840 --- [nio-8080-exec-1] t.OTTify.oauth.jwt.JwtAuthFilter         : checkAccessTokenAndAuthentication() 호출
    2024-02-01 03:56:23.172  INFO 15840 --- [nio-8080-exec-1] tavebalak.OTTify.oauth.jwt.JwtService    : extractAccessToken() 호출

    언제까지??

    우리의 만료시간 설정동안!

    따라서 나같이 하루 동안의 캐시값을 바꾸지 않을 거면 60X60(1분)X60(1시간)X24를 만료시간으로 설정하는 것이 맞을 것이다.

    참조

    https://hnev.tistory.com/43 :Ec2 메모리 용량 확인
    https://jaynamm.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%AC-Buffer-%EC%99%80-Cache-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0#2.%20Cache
    :버퍼와 캐시 및 available 관련 설명

    https://mozi.tistory.com/424 :swap 영역 등 설명
    https://velog.io/@songs4805/Spring-Cache%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90
    :캐시 종류 및 장단점

    https://blog.yevgnenll.me/posts/spring-boot-with-caffeine-cache
    :caffeine 캐시 적용

    https://myborn.tistory.com/24 :caffeine 캐시 적용

    https://velog.io/@sixhustle/caffeine-cache : caffeine 캐시 적용

    문제점 정리 및 추가사항

    1) spring-boot-starter-cache 안해도 되던뎅?

    많은 블로그에서 build.gradle 에 spring-boot-starter-cache를 추가해야 캐시 관련 어노테이션을 쓸 수 있다고 적혀있었으나 기존의 프로젝트에서는 이미

    implementation 'org.springframework.boot:spring-boot-starter-web'

    이 존재하였기에 관련 starter-cache를 추가하지 않아도 되었습니다.

    :스프링 부트 버전에 따라 다를 수도 있으니 확인할 것!

    2)캐시 기본 설정이 왜 redis 로 될까 + 캐시 키값

    기존 프로젝트에 redis 가 build.gradle 에 추가되어서 추가적인 캐시 컨피그를 설정하지 않으면 redis 가 기본으로 세팅 되는 것 같습니다.
    @Override
      @Cacheable(value = "programCache", key = "#programId")
      public ProgramResponseDto showDetails(Long programId) {

    관련해서 실험해볼겸 이번에는 key 값에 따라서 다른 캐시를 저장해보도록 하겠습니다.

    java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [tavebalak.OTTify.program.dto.programDetails.Response.ProgramResponseDto]
    	at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:43) ~[spring-core-5.3.30.jar:5.3.30]
    	at org.springframework.core.serializer.Serializer.serializeToByteArray(Serializer.java:56) ~[spring-core-5.3.30.jar:5.3.30]
    	at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:60) ~[spring-core-5.3.30.jar:5.3.30]
    	at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:33) ~[spring-core-5.3.30.jar:5.3.30]
    	at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:94) ~[spring-data-redis-2.7.17.jar:2.7.17]
    	at org.springframework.data.redis.serializer.DefaultRedisElementWriter.write(DefaultRedisElementWriter.java:44) ~[spring-data-redis-2.7.17.jar:2.7.17]
    	at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.write(RedisSerializationContext.java:290) ~[spring-data-redis-2.7.17.jar:2.7.17]
    	at org.springframework.data.redis.cache.RedisCache.serializeCacheValue(RedisCache.java:285) ~[spring-data-redis-2.7.17.jar:2.7.17]
    	at org.springframework.data.redis.cache.RedisCache.put(RedisCache.java:171) ~[spring-data-redis-2.7.17.jar:2.7.17]
    	at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87) ~[spring-context-5.3.30.jar:5.3.30]
    	at org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:837) ~[spring-context-5.3.30.jar:5.3.30]

    redis 관련 오류가 뜨는 것을 볼 수 있고
    오류가 하라는 대로 DTO 에 Serializable을 추가하면 (위 과정에서 트렌딩을 캐시에 적용하였는데 다른 예제입니다)

    @Getter
    public class ProgramResponseDto implements Serializable {
    
      private ProgramDetailResponse programDetailResponse;
      private OAProgramCreditsDto oaProgramCreditsDto;
      private ProgramProviderListResponseDto programProviderListResponseDto;
      private String programNormalReviewRating;

    처음 호출시 되게 느렸다:

    후에 redis 를 사용하면

    programCache 란 이름으로 적용되는 것을 볼 수 있습니다.

    get 을 이용해서 어떤 값이 들어가 있는지 구경해보죠!

    "\xac\xed\x00\x05sr\x00Gtavebalak.OTTify.program.dto.programDetails.Response.
    ProgramResponseDto\xde^\xedr\xbf\x17W\x8c\x02\x00\x04L\x00\x13oaProgramCr
    editsDtot\x00^Ltavebalak/OTTify/program/dto/programDetails/openApiRequest/p
    ersonDetails/OAProgramCreditsDto;L\x00\x15programDetailResponset\x00LLtavebala
    k/OTTify/program/dto/programDetails/Response/ProgramDetailResponse;L\x00\x19pro
    gramNormalReviewRatingt\x00\x12Ljava/lang/String;L\x00\x1eprogramProviderListResp
    onseDtot\x00ULtavebalak/OTTify/program/dto/programDetails/Response/ProgramProvide
    rListResponseDto;xpsr\x00\\tavebalak.OTTify.program.dto.programDetails.openApiReque
    st.personDetails.OAProgramCreditsDto\x82\xd7\xa8c\xe97`y\x02\x00\x02L\x00\x04cast
    t\x00\x10Ljava/util/List;L\x00\x04crewq\x00~\x00\axpsr\x00\x13java.util.ArrayList
    x\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x18w\x04\x00\x
    00\x00\x18sr\x00Mtavebalak.OTTify.program.dto.programDetails.openApiRequest.pers
    onDetails.Cast\xef\x13\x84\x16\xc6F\x93}\x02\x00\x05L\x00\tcharacterq\x00~\x00\x0
    3L\x00\ndepartmentq\x00~\x00\x03L\x00\x12knownForDepartmentq\x00~\x00\x03L\x00\x0
    4nameq\x00~\x00\x03L\x00\x0bprofilePathq\x00~\x00\x03xpt\x00'T\xc3\xado Berto Ri
    vera / Don Hidalgo (voice)pt\x00\x06\xea\xb0\x90\xeb\x8f\x85t\x00\x0bLuis Valdezt\
    x00 /fOsJACqM3zcrhvYGrCSlErzbdBt.jpgsq\x00~\x00\x0bt\x00\x15Miguel Rivera (voice)pt\x00\x06\xeb\xb0\xb0\xec\x9a\xb0t\x00\x10Anthony Gonzalezt\x00 /3SfBmj35OfL8eVIQt9nTixWqVJH.jpgsq\x00~\x00\x0bt\x00\x0fH\xc3\xa9ctor (voice)pq\x00~\x00\x13t\x00\x13Gael Garc\xc3\xada Bernalt\x00 /7mq3EQN1oJfYNXkv9xKXKu6Ccw5.jpgsq\x00~\x00\x0bt\x00\x1aErnesto de la Cruz (voice)pq\x00~\x00\x13t\x00\x0eBenjamin Brattt\x00 /hBenHPT4iJEG2kt5z2TOGnkRZwh.jpgsq\x00~\x00\x0bt\x00\x1bMam\xc3\xa1 Imelda Rivera (voice)pq\x00~\x00\x13t\x00\x0cAlanna Ubacht\x00 /p2sIpgftEIkhPrrpgu8wW8XEpDg.jpgsq\x00~\x00\x0bt\x00\x14Elena Rivera (voice)pq\x00~\x00\x13t\x00\rRen\xc3\xa9e Victort\x00 /wAVDqwFhQsRQgO6VIYq6T9Wbbx8.jpgsq\x00~\x00\x0bt\x00#Mam\xc3\xa1 Socorro \"Coco\" Rivera (voice)pq\x00~\x00\x13t\x00\x13Ana Ofelia Murgu\xc3\xadat\x00 /rjlxNNR3t2BXDm1yjXLHx5489cV.jpgsq\x00~\x00\x0bt\x00\x13Chicharr\xc3\xb3n (voice)pq\x00~\x00\x13t\x00\x12Edward James Olmost\x00 /mXnilUrQBIMLHSQkPjQk99mX70x.jpgsq\x00~\x00\x0bt\x00\x1cPap\xc3\xa1 Enrique Rivera (voice)pq\x00~\x00\x13t\x00\x0bJaime Camilt\x00 /njvdXEGN3bSSXpsyVnyKQTXdf1V.jpgsq\x00~\x00\x0bt\x00\x1aPap\xc3\xa1 Julio Rivera (voice)pq\x00~\x00\x13t\x00\x0cAlfonso Araut\x00 /5ishPim5akWXSnMbwOWSc3KxDx8.jpgsq\x00~\x00\x0bt\x00.T\xc3\xado Oscar Rivera / T\xc3\xado Felipe Rivera (voice)pq\x00~\x00\x13t\x00\x10Herbert Siguenzat\x00 /aVLX3sRMZp1qIfqCRHgjjy2NfGI.jpgsq\x00~\x00\x0bt\x00\x1aMariachi / Gustavo (voice)pq\x00~\x00\x13t\x00\x0eLombardo Boyart\x00 /wwWs0u2QDz1XOakR05C0BYJCfM1.jpgsq\x00~\x00\x0bt\x00\x1aMam\xc3\xa1 Luisa Rivera (voice)pq\x00~\x00\x13t\x00\x0fSof\xc3\xada Espinosat\x00 /iOwPSogxUHNWh3B0aIR1DuEW921.jpgsq\x00~\x00\x0bt\x00\x1cT\xc3\xada Victoria Rivera (voice)pq\x00~\x00\x13t\x00\rDyana Ortellit\x00 /5ucJJBjifV9tEiVWnTLFy8OUrzF.jpgsq\x00~\x00\x0bt\x00\x12Head Clerk (voice)pq\x00~\x00\x13t\x00\x10Gabriel Iglesiast\x00 /8Bjy3RZ5TfNs3oUOUpxTv84G01f.jpgsq\x00~\x00\x0bt\x00\x1aT\xc3\xada Rosita Rivera (voice)pq\x00~\x00\x13t\x00\x0bSelene Lunat\x00 /dBVo0kKrIQGk3aqFbKUsvJp4DcJ.jpgsq\x00~\x00\x0bt\x00\x13Frida Kahlo (voice)pq\x00~\x00\x13t\x00\x17Natalia Cordova-Buckleyt\x00 /sWqzbY7xgw9dPhFQJOcging3QMu.jpgsq\x00~\x00\x0bt\x00\x17Departure Agent (voice)pq\x00~\x00\x13t\x00\x0cCarla Medinapsq\x00~\x00\x0bt\x00\rEmcee (voice)pq\x00~\x00\x13t\x00\x0eBlanca Aracelit\x00 /kDm6OwatIx7WWGumIhPUmGu2J3v.jpgsq\x00~\x00\x0bt\x00\x13Abel Rivera (voice)pq\x00~\x00\x13t\x00\nPolo Rojaspsq\x00~\x00\x0bt\x00\x13Rosa Rivera (voice)pq\x00~\x00\x13t\x00\x10Montse Hernandezt\x00 /mQ8tY2ji2fSBnkrKJAsIAxHTZUG.jpgsq\x00~\x00\x0bt\x00\x1bCorrections Officer (voice)pq\x00~\x00\x13t\x00\x0cCheech Marint\x00 /eecHDNRn9K80ZcuSocsMhQb2G1i.jpgsq\x00~\x00\x0bt\x00\x16Security Guard (voice)pq\x00~\x00\x13t\x00\x0eSalvador Reyespsq\x00~\x00\x0bt\x00\x17Juan Ortodoncia (voice)pq\x00~\x00\x13t\x00\x11John Ratzenbergert\x00 /e5aNU09v1q6WYTx9pNzC7yjSpve.jpgxsq\x00~\x00\t\x00\x00\x00\bw\x04\x00\x00\x00\bsq\x00~\x00\x0bpt\x00\tDirectingq\x00~\x00\x0et\x00\x0bLee Unkricht\x00 /crb297utC6W4HSstOe5djDPTwEN.jpgsq\x00~\x00\x0bpt\x00\aEditingq\x00~\x00\x0et\x00\x0bLee Unkricht\x00 /crb297utC6W4HSstOe5djDPTwEN.jpgsq\x00~\x00\x0bpt\x00\aWritingq\x00~\x00\x0et\x00\x0bLee Unkricht\x00 /crb297utC6W4HSstOe5djDPTwEN.jpgsq\x00~\x00\x0bpt\x00\aWritingq\x00~\x00\x0et\x00\x0fEnrico Casarosat\x00 /3UzBWIqYw0T0aLkdp6oEyLzWOE0.jpgsq\x00~\x00\x0bpt\x00\aWritingq\x00~\x00\x0et\x00\x12Madeline Sharafiant\x00 /qsWEDSX02wr9E2HfL5v9hx0DF2Q.jpgsq\x00~\x00\x0bpt\x00\x0eVisual Effectsq\x00~\x00\x0et\x00\x10Michael Bidingerpsq\x00~\x00\x0bpt\x00\x06Cameraq\x00~\x00\x0et\x00\x0cMatt Aspburypsq\x00~\x00\x0bpt\x00\tDirectingq\x00~\x00\x0et\x00\x0fSeong-Young Kimpxsr\x00Jtavebalak.OTTify.program.dto.programDetails.Response.ProgramDetailResponseY\x1cX]\x9e\t\xceh\x02\x00\tL\x00\x0cbackDropPathq\x00~\x00\x03L\x00\acountryq\x00~\x00\x03L\x00\x0bcreatedDateq\x00~\x00\x03L\x00\tgenreNameq\x00~\x00\aL\x00\roriginalTitleq\x00~\x00\x03L\x00\boverviewq\x00~\x00\x03L\x00\nposterPathq\x00~\x00\x03L\x00\ataglineq\x00~\x00\x03L\x00\x05titleq\x00~\x00\x03xpt\x00 /askg3SMvhqEl4OL52YuvdtY40Yb.jpgt\x00\x18United States of Americat\x00\n2017-10-27sq\x00~\x00\t\x00\x00\x00\x04w\x04\x00\x00\x00\x04t\x00\x06\xea\xb0\x80\xec\xa1\xb1t\x00\x0f\xec\x95\xa0\xeb\x8b\x88\xeb\xa9\x94\xec\x9d\xb4\xec\x85\x98t\x00\x06\xec\x9d\x8c\xec\x95\x85t\x00\x06\xeb\xaa\xa8\xed\x97\x98xt\x00\x04Cocot\x03\x01\xeb\xaf\xb8\xea\xb5\xac\xec\x97\x98\xec\x9d\x80 \xeb\xa9\x95\xec\x8b\x9c\xec\xbd\x94\xec\x9d\x98 \xec\x9e\x90\xeb\x9e\x91\xec\x9d\xb8 \xec\x97\x90\xeb\xa5\xb4\xeb\x84\xa4\xec\x8a\xa4\xed\x86\xa0 \xeb\x8d\xb8\xeb\x9d\xbc \xed\x81\xac\xeb\xa3\xa8\xec\xa6\x88 \xea\xb0\x99\xec\x9d\x80 \xeb\xae\xa4\xec\xa7\x80\xec\x85\x98\xec\x9d\xb4 \xeb\x90\x98\xea\xb8\xb8 \xea\xbf\x88\xea\xbe\xb8\xec\xa7\x80\xeb\xa7\x8c \xeb\xaf\xb8\xea\xb5\xac\xec\x97\x98 \xec\xa7\x91\xec\x95\x88 \xec\x82\xac\xeb\x9e\x8c\xeb\x93\xa4\xec\x97\x90\xea\xb2\x8c \xec\x9d\x8c\xec\x95\x85\xec\x9d\x80 \xea\xb8\x88\xea\xb8\xb0\xeb\x8b\xa4. \xeb\xa8\xbc \xec\x98\x9b\xeb\x82\xa0 \xec\xa1\xb0\xec\x83\x81 \xec\xa4\x91\xec\x97\x90 \xec\x9d\x8c\xec\x95\x85 \xeb\x95\x8c\xeb\xac\xb8\xec\x97\x90 \xea\xb0\x80\xec\xa1\xb1\xec\x9d\x84 \xeb\xb2\x84\xeb\xa6\xb0 \xec\x9d\xb8\xeb\xac\xbc\xec\x9d\xb4 \xec\x9e\x88\xec\x97\x88\xea\xb8\xb0 \xeb\x95\x8c\xeb\xac\xb8. \xeb\xaf\xb8\xea\xb5\xac\xec\x97\x98\xec\x9d\x80 \xec\xa3\xbd\xec\x9d\x80 \xec\x9e\x90\xec\x9d\x98 \xeb\x82\xa0\xec\x9d\xb4 \xeb\x90\x98\xec\x9e\x90 \xec\x8b\xa4\xeb\xa0\xa5\xec\x9c\xbc\xeb\xa1\x9c \xec\x9d\xb8\xec\xa0\x95\xeb\xb0\x9b\xea\xb2\xa0\xeb\x8b\xa4\xeb\x8a\x94 \xea\xb2\xb0\xec\x8b\xac\xec\x9d\x84 \xed\x95\x98\xea\xb3\xa0 \xea\xb2\xbd\xec\x97\xb0 \xeb\xac\xb4\xeb\x8c\x80\xec\x97\x90 \xec\x98\xa4\xeb\xa5\xb4\xeb\xa0\xa4 \xed\x95\x98\xeb\x8a\x94\xeb\x8d\xb0, \xec\x9a\xb0\xec\x97\xb0\xed\x9e\x88 \xec\x97\x90\xeb\xa5\xb4\xeb\x84\xa4\xec\x8a\xa4\xed\x86\xa0\xec\x9d\x98 \xea\xb8\xb0\xed\x83\x80\xec\x97\x90 \xec\x86\x90\xec\x9d\x84 \xeb\x8c\x94\xeb\x8b\xa4\xea\xb0\x80 \xec\xa3\xbd\xec\x9d\x80 \xec\x9e\x90\xeb\x93\xa4\xec\x9d\x98 \xec\x84\xb8\xec\x83\x81\xec\x97\x90 \xeb\x93\xa4\xec\x96\xb4\xec\x84\x9c\xea\xb2\x8c \xeb\x90\x9c\xeb\x8b\xa4. \xec\x9d\xb4\xec\x8a\xb9\xea\xb3\xbc \xec\xa0\x80\xec\x8a\xb9\xec\x9d\x84 \xec\x9d\xb4\xec\x96\xb4\xec\xa3\xbc\xeb\x8a\x94 \xeb\xa7\x88\xeb\xa6\xac\xea\xb3\xa8\xeb\x93\x9c \xea\xbd\x83\xea\xb8\xb8\xec\x9d\x84 \xea\xb1\xb4\xeb\x84\x88 \xec\xa3\xbd\xec\x9d\x80 \xec\x9e\x90\xeb\x93\xa4\xec\x9d\x98 \xec\x84\xb8\xec\x83\x81\xec\x97\x90 \xeb\x8f\x84\xec\xb0\xa9\xed\x95\x9c \xeb\xaf\xb8\xea\xb5\xac\xec\x97\x98\xec\x9d\x80 \xea\xb1\xb0\xec\xa7\x93\xeb\xa7\x90\xea\xb3\xbc \xec\x9c\x84\xeb\xb3\x80\xec\xa1\xb0\xea\xb0\x80 \xec\x9e\xa5\xea\xb8\xb0\xec\x9d\xb8 \xed\x97\xa5\xed\x84\xb0\xeb\xa5\xbc \xeb\xa7\x8c\xeb\x82\x9c\xeb\x8b\xa4. \xea\xb7\xb8\xeb\xa6\xac\xea\xb3\xa0 \xea\xbf\x88\xec\x97\x90 \xea\xb7\xb8\xeb\xa6\xac\xeb\x8d\x98 \xec\x9a\xb0\xec\x83\x81 \xec\x97\x90\xeb\xa5\xb4\xeb\x84\xa4\xec\x8a\xa4\xed\x86\xa0\xeb\xa5\xbc \xeb\xa7\x8c\xeb\x82\x98\xeb\x9f\xac \xea\xb0\x80\xeb\x8a\x94 \xec\x97\xac\xec\xa0\x95\xec\x97\x90\xec\x84\x9c \xea\xb0\x80\xec\xa1\xb1\xec\x9d\x98 \xeb\xb9\x84\xeb\xb0\x80\xec\x9d\x84 \xec\x95\x8c\xea\xb2\x8c \xeb\x90\x9c\xeb\x8b\xa4.t\x00 /pQu93NuwR90AaCULzglVD5Ge4Ml.jpgt\x00.\xec\xa3\xbd\xec\x9d\x80 \xec\x9e\x90\xeb\x93\xa4\xec\x9d\x98 \xec\x84\xb8\xec\x83\x81\xec\x9d\x80 \xeb\x8d\x94\xec\x9a\xb1 \xed\x99\x94\xeb\xa0\xa4\xed\x95\x98\xeb\x8b\xa4t\x00\x06\xec\xbd\x94\xec\xbd\x94t\x00\x030.0sr\x00Stavebalak.OTTify.program.dto.programDetails.Response.ProgramProviderListResponseDto\xd7\xe5\x13\xe2\x0c\x8b\x9fO\x02

    그렇다고 하네요~~

    조금 더 공부


    캐시 시간을 줄이자!

    Trending 이 하루동안 안바뀌는 그런 개념이 아니었다. https://developer.themoviedb.org/docs/popularity-and-trending 에서 time window가 day라서 update 주기가 day로 생각하였지만 다시 찾아보니 https://www.themoviedb.org/talk/63870c811b157d007a4444fe update 주기는 항상이라고 한다. 단 자체적으로 6시간 캐시가 있을 뿐이라고.. 그러면 1시간에서 2시간 캐싱을 적용하는 것으로 수정하자!

    추가적 적용 :Redis 캐시

    @Configuration
    @EnableCaching
    public class RedisCacheConfig {
    
      @Bean
      public CacheManager cacheManager(RedisConnectionFactory cf) {
          RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
              .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
                  new StringRedisSerializer()))
              .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                  new GenericJackson2JsonRedisSerializer()))
              .entryTtl(Duration.ofMinutes(1L));
    
          Map<String, RedisCacheConfiguration> cacheConfiguration = new HashMap<>();
    
          cacheConfiguration.put(CacheType.PROGRAM_TRENDING.getCacheName(),
              redisCacheConfiguration.entryTtl(Duration.ofMinutes(2L)));
    
          return RedisCacheManager
              .RedisCacheManagerBuilder
              .fromConnectionFactory(cf)
              .cacheDefaults(redisCacheConfiguration)
              .withInitialCacheConfigurations(cacheConfiguration)
              .build();
      }
    }

    단 이 경우 반환하는 DTO 에 @NoArgsConstructor가 있어야 했다.
    메인 페이지 트렌딩 경우 key 를 따로 지정 하지 않으면 "programTrending::SimpleKey []"
    라는 값이 redis 에 키 값으로 들어가 있는 것을 볼 수 있고 get 으로 내용물을 조회가 가능하다.

    참고:https://prodo-developer.tistory.com/157
    https://hstory0208.tistory.com/entry/Spring-Redis-Redis-cache%EB%A5%BC-%EC%A0%81%EC%9A%A9%ED%95%B4-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-%EB%B0%A9%EB%B2%95

  • profile
    기록을 통해 실력을 쌓아가자

    0개의 댓글