레디스를 도입한건 팀프로젝트 때였는데, 사실상 이때는 밈캐시 같이 쓴 것 같습니다 레디스에서 지원하는 데이터 타입을 제대로 활용해보지 못했고 redis에 대해 잘 알지도 못했지만, 그래도 원티드 프리온보딩 때 boss-raid 프로젝트를 하면서 모르는 부분을 보충하고 sorted-set도 활용해보면서 왜 필요한지, 어떨 때 써야할지를 조금 알게 된 것 같습니다
아무튼...지금까지 사용해온 레디스의 기본 개념부터 한번 짚고 넘어가겠습니다
밈캐시
redis
추가
const getRedis: IRaidStatus = await this.cacheManager.get('raidStatus');
const result: IRaidStatus = getRedis ? getRedis : { canEnter: true, enteredUserId: null, raidRecordId: null };
return result;
/**
* @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에서 해당 토큰 정보가 조회된다면 비인증 에러를 던지도록 로직을 구성했습니다.