[우아한테크코스 #6] 데이터 캐싱하기

NinjaJuunzzi·2022년 3월 20일
1

우아한테크코스

목록 보기
8/21
post-thumbnail

캐싱이란?

컴퓨팅에서 캐시는 일반적으로 일시적인 특징이 있는 데이터 하위 집합을 저장하는 고속 데이터 스토리지 계층입니다. 따라서 이후에 해당 데이터에 대한 요청이 있을 경우 데이터의 기본 스토리지 위치에 액세스할 때보다 더 빠르게 요청을 처리할 수 있습니다. 캐싱을 사용하면 이전에 검색하거나 계산한 데이터를 효율적으로 재사용할 수 있습니다.

캐싱을 적용하게 된 계기가 뭐죠??

1. 같은 검색어에 대한 결과 값 요청이 짧은 시간내에 반복된다면, 굳이 다 요청을 보내야 하는지 의문이 듦

2. 유튜브 API는 일일 요청 횟수에 제한이 있어 API_KEY를 동냥하고 다님

3. 같은 검색어에 대해 엔터(버튼 클릭)을 여러번하면 계속 로딩되었고 사용자 경험을 떨어뜨릴 수 있다고 생각하게됨

어떻게 캐시 스토어를 구현해보았나요 ?

아이디어는 다음과 같습니다

  • 우선 가장 익숙한 문법으로 접근하고자 하였습니다. (그것은 바로 클로져!)

  • 검색어와 다음 비디오를 요청하는 토큰 값을 캐시 스토어의 키 값으로 활용

  • 키 값에 저장되는 캐시 데이터는 json 화가 되지 않은 response data (API 응답 데이터를 캐싱하고자 하였기에)

  • 데이터가 최신화(서버 데이터와 클라이언트 데이터는 동일해야한다고 생각)되어야 하므로 캐시 데이터는 set 행위를 기준으로 60초 후에 비워지도록 구현

  • 여러 개의 캐시 스토어가 필요하지 않을 것이라 판단하여 IIFE를 활용하여 싱글 스토어(여러 개의 스토어 인스턴스 생성 불가하게)로 구현

구현 코드도 보여주세요

캐시 스토어

// cacheStore.js
import { REVALIDATION_TIME } from '../constants/cacheStore';

export const { getCacheData, setCacheData } = (function () {
  // cacheStore를 은닉화 하기 위해 클로저 패턴 사용
  const cacheStore = {};
  return {
    getCacheData(cacheKey) {
      return cacheStore[cacheKey];
    },
    setCacheData(cacheKey, data) {
      /** 캐시된 데이터가 서버 데이터와 맞지 않으면 안되므로 */
      setTimeout(() => {
        cacheStore[cacheKey] = undefined;
      }, REVALIDATION_TIME);
      cacheStore[cacheKey] = data;
    },
  };
})();

사용처

  async requestSearchVideoList(keyword, pageToken) {
   	// ...
    const alreadyData = getCacheData(`${keyword}-${pageToken}`);

    if (alreadyData) {
      // cache hit
      setState(STATE_STORE_KEY.IS_SEARCH_VIDEO_WAITING, false);
      return alreadyData;
    }
    
    // cache miss
    const searchResult = await youtubeAPIFetcher({
      path: API_PATHS.SEARCH,
      params: {
        q: keyword,
        part: 'snippet',
        maxResults: 10,
        type: 'video',
        pageToken: pageToken ?? '',
      },
    });

    setCacheData(`${keyword}-${pageToken}`, searchResult);

	// ...

    return searchResult;
  }

어떤 문제가 있을 수 있을까요? + 리뷰어의 리뷰

  • setCacheData 내부에서 setTimeout으로 해당 키의 값을 비우는게 옳은가?

리뷰어의 리뷰

현 로직상 set이 일어난 시점에서 1분 뒤에 무조건 값을 지우는 방식입니다.
만약 최초 set을 하고 59초 뒤에 두번째 set이 일어난다면?
두번째 set은 1초 뒤에 바로 지워질 겁니다.

즉, set 지우는 로직이 다음 set 로직에 영향을 준다고 할 수 있겠습니다.

리뷰에 대한 내 생각

처음에 리뷰를 보고는 말하는 바가 이해되지 않았습니다. setCacheDatagetCacheData로 캐시 값을 가져온 이후 캐시 값이 없을때만 실행된다고 오해했었거든요.

하지만 생각해보니 사용처에서 setCacheData를 캐시 값이 비워져 있지않음에도 여러번 호출할 수 있더라구요! 이렇게 캐시 값이 비워져 있는 경우가 아님에도 호출되게 되면 사실 상 setTimeout 으로 태스크 큐에 박아두었던 콜백함수들이 무한정 쌓이게 되겠죠. (이미 캐시된 값이 없음에도 또 비우게 될지도..?)

따라서 다음과 같이 수정을 해본다면 어떨까 싶었습니다.

캐시 값이 비워져 있음에도 클리어할 수 있음을 해결

export const { getCacheData, setCacheData } = (function () {
  
  const cacheStore = {};
  const timeoutList = {};
  
  return {
    getCacheData(cacheKey) {
      return cacheStore[cacheKey];
    },
    setCacheData(cacheKey, data) {
      if (!timeoutList[cacheKey]) {
        /** 중복된 timeout 콜백함수는 생기지 않는다.*/
        timeoutList[cacheKey] = setTimeout(() => {
          cacheStore[cacheKey] = undefined;
          /** timeoutList에서 삭제한다 */
          timeoutList[cacheKey] = undefined;
        }, REVALIDATION_TIME);
      }
      cacheStore[cacheKey] = data;
    },
  };
})();

그 다음 set에 영향을 주지 않도록 구현

import { REVALIDATION_TIME } from '../constants/cacheStore';

export const { getCacheData, setCacheData } = (function () {
  const cacheStore = {};
  const timeoutList = {};
  return {
    getCacheData(cacheKey) {
      return cacheStore[cacheKey];
    },
    setCacheData(cacheKey, data) {
      /** set이 발생하면 이전 타임아웃을 종료하고 */
      if (timeoutList[cacheKey]) {
        clearTimeout(timeoutList[cacheKey]);
      }
      /* 새로운 타임아웃을 발생시킨다. */
      timeoutList[cacheKey] = setTimeout(() => {
        cacheStore[cacheKey] = undefined;
      }, REVALIDATION_TIME);
      cacheStore[cacheKey] = data;
    },
  };
})();

하지만 다음과 같은 문제는 해결할 수 없었어요.

클로저를 적절히 활용해서 clearTimeout을 써볼 수 있겠지만, 이 역시 브라우져가 아예 종료된 시점에서는 동작하지 않을겁니다.

브라우저, 권한 및 로그인 서비스의 토큰 관리 방법 등 다른 프로그램이나 서비스에서는 어떻게 캐시를 관리하고 있을까? 리처치해보시면 적절한 아이디어를 얻을 수 있을겁니다 :)

그렇기에 다른 아이디어들을 리서치해보려고 합니다 !!

다른 아이디어를 리서치

/* 조 사 중 */
  • 메모이제이션 함수

Ref

profile
Frontend Ninja

0개의 댓글