Redis 리뷰

영태·2022년 8월 8일
0

[REVIEW]

목록 보기
4/7
post-thumbnail

Redis

레디스를 도입한건 팀프로젝트 때였는데, 사실상 이때는 밈캐시 같이 쓴 것 같습니다 레디스에서 지원하는 데이터 타입을 제대로 활용해보지 못했고 redis에 대해 잘 알지도 못했지만, 그래도 원티드 프리온보딩 때 boss-raid 프로젝트를 하면서 모르는 부분을 보충하고 sorted-set도 활용해보면서 왜 필요한지, 어떨 때 써야할지를 조금 알게 된 것 같습니다
아무튼...지금까지 사용해온 레디스의 기본 개념부터 한번 짚고 넘어가겠습니다

Redis란?

  • redis는 In-Memory DB이면서 NoSQL에 속하는 데이터베이스입니다.
    • 여기서 Memory는 전원이 꺼질 경우 데이터가 모조리 사라지는 휘발성을 가지고 있는 것이 특징입니다
    • 메모리, 즉 램은 CPU와 물리적으로 가깝습니다
    • 따라서 데이터를 처리하는 것이 매우 빠릅니다
    • 한편 HDD와 SDD의 경우 전원이 꺼져도 데이터가 날아가지 않는 비휘발성의 속성을 가지고 있지만 CPU와의 물리적인 거리가 멀기 때문에 데이터가 사라지지 않는 비휘발성의 속성을 가지고 있습니다
    • 이 점이 redis와 In-Memory가 아닌 NoSQL,SQL와의 차이점입니다
    • 레디스는 메모리라는 특징 때문에 상당히 빠른 속도를 가지고 있습니다
  • IoT(사물 인터넷)의 출현과 함께 클라우드 기반 솔루션들이 성장하면서 '실시간'으로 데이터를 처리해야 할 필요성이 많아졌습니다
  • 기존의 DB로는 급격하게 늘어난 데이터를 감당할 수 없었고, 빠른 속도가 보장되는 즉, 실시간에 가까운 데이터 처리를 하기 위해 redis의 사용이 급증했습니다

밈캐시 vs 레디스

  • 밈캐시

    • 밈캐시는 데이터 타입을 String만 지원합니다
    • 또한 데이터를 메모리에만 저장하기 떄문에 메모리가 부족할 경우 일부 데이터를 삭제해야 하고,복제가 불가능하여 캐시 그 이상으로 활용할 수 없습니다
    • 캐시 용량도 1메가 바이트로 매우 작습니다
  • redis

    • 한편 redis는 String 외에도 set, list, 제가 사용해본 sorted-set, hash, 비트 배열과 스트림까지 지원합니다
    • 메모리를 포함하여 디스크에도 저장할 수 있는데, 이를 통해서 비휘발성의 특징도 가지고 있습니다
    • 혹은 특정 지점을 설정하고 디스크에 백업할 수 있는 snapshot 기능이 있는데 아직 활용해보진 않았지만 이 snapshot기능을 통해 데이터를 디스크에 저장시켜 메모리의 여유 공간을 만들수도 있습니다
    • 캐싱이 가능해 실시간 채팅에도 적합하며
    • 캐시 용량도 512메가바이트로 큰 용량을 지원합니다
    • 또 이것도 활용해보려고 노력하다 결국 성공하진 못했지만...ACID를 유사하게 지원해서 트랜잭션을 걸 수 있다는 장점이 있습니다. 다만 이건 해보려다가 실패해서...한번 더 도전해볼 생각입니다
    • 이런 이유로 범용성이 매우 높고, 캐시로만 끝나는 것이 아니라 확장성도 좋기 떄문에 사용자가 가장 많은 In-memory DB라고 생각하며 그런 이유에서 redis를 사용했습니다
  • 추가

    • 밈캐시는 멀티스레드를 사용하기 때문에 다중처리가 빠르다는 장점이 있긴 합니다 redis는 싱글 쓰레드이구요
    • 하지만 이외에는 많은 단점이 존재한다고 생각합니다
    • 저장할 공간이 없다고 해서 일부러 데이터를 지운다는 것은 필요한 상황이 있을 수도 있겠지만, 좋지 못한 방향이라고 생각합니다

레디스를 사용한 이유

  • 빠른 데이터 조회가 필요할때, 혹은 관계형 DB를 사용할 필요가 없는 로그아웃 시의 블랙리스트에서 NoSQL로서 사용할때 주로 사용했습니다.
  • 빠른 데이터 조회에 있어서는
    • boss-raid
      - boss-raid 프로젝트 당시 보스레이드의 시간 제한이 3분이라는 점을 고려해 ttl을 3분으로 두고 빠른 조회를 위해 redis를 사용했습니다
      const getRedis: IRaidStatus = await this.cacheManager.get('raidStatus');
      const result: IRaidStatus = getRedis ? getRedis : { canEnter: true, enteredUserId: null, raidRecordId: null };
      return result;
  • 두번째로는 랭킹조회를 redis로 하도록 했는데 redis의 sorted-set 기능을 활용해 유저가 가진 점수가 가장 높은 순으로 redis에서 데이터를 조회할 수 있도록 했습니다
   /**
   * @description 랭크 탑 10 리스트 불러오기
   *  레이드를 진행한 전체 유저의 점수(totalScore)를 내림차 순으로 가져옵니다.(zscore)
   *  해당 점수의 랭킹 리스트를 가져옵니다. (zrevrangebyscore)
   *  위의 랭킹 리스트[0]에 해당하는 랭킹을 가져와서 동점을 처리합니다.  (zrevrank)
   */
  async getTopRankerList(): Promise<IRankingInfo[]> {
    const allUsers = await this.redis.zrevrange('Raid-Rank', 0, -1);
    const resultTotal: IRankingInfo[] = await Promise.all(
      allUsers.map(async el => {
        const score = await this.redis.zscore('Raid-Rank', el);
        const sameScoreList = await this.redis.zrevrangebyscore('Raid-Rank', score, score);
        const firstKey = sameScoreList[0];
        const rank = await this.redis.zrevrank('Raid-Rank', firstKey);
        const result: IRankingInfo = { ranking: rank + 1, userId: Number(el), totalScore: Number(score) };
        return result;
      }),
    );
    return resultTotal;
  }
  • 점수를 기록하는 것에 관계가 필요하지 않기 때문에 sorted-set의 key 값을 ranking으로 두고 하위 값들을 유저이메일-점수로 기록해 이 set을 sort하여 순서대로 조회하도록 한 것입니다

    • 이렇게 랭킹기록, 조회 과정에 레디스를 활용한 경험이 있습니다

    • 온도의 팀 프로젝트

      • 프로젝트의 가장 중심이 되는 피드 데이터에 redis를 도입했습니다. 이유는 조회가 되는 횟수가 굉장히 많고, 그 점에 있어서 늘 rdb에 요청을 해야 하는 상황이 부적절하다고 판단했고, 속도 자체가 느려진다는 점이 주요한 단점이라고 생각했습니다
        - 따라서 첫번째 조회 시, 검색어를 기반으로 RDB에서 최초로 조회된 데이터를 검색어-조회된 데이터의 key-value 값으로 두어 레디스에 저장되도록 했습니다
        - 따라서 최초 이후의 조회시에는 redis의 검색어 key값을 통해 RDB에서의 조회 없이 빠른 속도로 조회 데이터를 받아올수 있게 했습니다
        - 불필요한 RDB 접근 없이 빠른 속도로 조회할 수 있도록 한 것이 피드 데이터 조회에 redis를 활용한 이유입니다

        const redisInput = JSON.stringify({ region, feedTags, page });
        const redis = await this.cacheManager.get(redisInput);
        if (redis) {
         return redis;
        } else {
         const result: FetchFeedOutput = await this.feedService.findWithTags({
           feedTags,
           region,
           page,
         });
         await this.cacheManager.set(redisInput, result, { ttl: 5 });
         return result;
        }
        } catch ({ error, region }) {
        const result: FetchFeedOutput = await this.feedService.findWithTags({
         feedTags,
         region,
         page,
        });
        return result;
      • redis를 활용해 다음과 로그아웃된 jwt-토큰을 무효화시키도록 로직을 짠 기억이 있습니다.

        //전략 패턴 내의 validate() 함수 로직
        async validate(req, payload) {
          const result = await req.headers.authorization.split(' ')[1];
          const redisKey = `accessToken:${result}`;
          const exist = await this.cacheManager.get(redisKey);
          if (exist) {
            throw new UnauthorizedException();
          } else {
            return {
              id: payload.sub,
              email: payload.email,
            };
          }
        }
         - 로그아웃된 유저가 생성했던 토큰의 블랙리스트를 redis에 기록했는데, 로그아웃을 한 유저가 다시 api에 접근하는 것을 방지하기 위함이었습니다.
         - 로그아웃을 하더라도 브라우저 상에서 프론트엔드에서 쿠키와 헤더 삭제가 제대로 되지 않았을 경우를 가정해 안전 장치를 적용한 것입니다
         - 브라우저 상에서 jwt-token이 삭제되지 않는다면 인증을 재차 통과할 수 있기 때문에, 로그아웃된 유저가 보유한 jwt-token을 만료 기한만큼 ttl을 설정해 redis에 블랙리스트를 만든다음, 전략패턴을 거치는 과정에서 redis에서 해당 토큰 정보가 조회된다면 비인증 에러를 던지도록 로직을 구성했습니다.
         
         

정리

Redis란?

  • NoSQL 범주에 속하는 인메모리 DB
  • 전원이 꺼질 경우 데이터가 사라지는 휘발성이 있다
  • CPU와 물리적으로 가까워 데이터 처리속도가 빠르다
  • IoT 출현과 함께 실시간 데이터 처리의 필요성이 높아지면서 사용이 급증

밈캐시

  • string 데이터 타입만 지원
  • 멀티스레드를 지원해 다중처리가 빠르지만 이외에는 단점

레디스가 더 나은 점

  • sorted-set, hash 등 다양한 데이터 타입을 지원
  • snap-shot을 통해 디스크에 저장시켜 메모리 여유 공간을 만들어줌
  • 큰 용량을 지원
  • 트랜잭션 지원

레디스를 사용한 이유

  • 조회에 있어서 빠른 속도를 보장하기 위해 캐싱해서 사용
  • 간단한 데이터를 캐싱할때 사용 (로그아웃한 유저의 jwt-토큰 블랙리스트로 활용)
  • sorted-set을 이용한 유저의 랭킹조회 구현을 위해 사용
profile
개발 공부중

0개의 댓글