Redis의 Sorted set을 사용하여 랭킹 기능 구현하기

Cherry·2022년 9월 13일
0
post-thumbnail

Redis의 sorted set을 이용해서 랭킹 기능을 구현한 내용을 정리하고자 한다 💻

🧐 데이터베이스 쿼리가 아닌 Redis를 사용한 이유는?

속도 차이 !!!

랭킹 기능은 DB에 저장된 데이터를 count 하여 구현할 수도 있다. 하지만 그렇게 하면 데이터 양이 증가할수록 count를 계산하고 결과를 가져오는 시간이 오래 걸리기 때문에 성능면에서 좋지 않다고 판단하였다.

그래서 속도 개선을 위해 in-memory DBRedis를 사용하였다.

✓ Memcached가 아닌 Redis를 사용한 이유

in-memory DBRedisMemcached 두 가지를 많이 사용한다고 한다.

Memcached 대신 Redis를 선택한 이유는 Redis가 더 다양한 자료구조를 지원하기 때문이다.

Memcached는 String 타입 하나만을 지원하는데에 비해 Redis는 String과 지금 사용하려는 sorted-set 이외에도 다양한 자료구조를 지원하고 있기 때문에 개발에 용이하다고 판단하였다.

즉, 랭킹 기능을 구현하는데 있어 Redis에서 제공하는 sorted-set을 사용하면 빠르고 용이하게 개발을 할 수 있을 것이라고 생각하여 Redis를 사용하게 되었다.

💡 랭킹 구현에 sorted set이 적합한 이유

  1. 집합이기 때문에 중복을 해결해 준다. (1등이 두 명이 될 수는 없다는 랭킹의 특징을 쉽게 나타낼 수 있다)
  2. score를 하나하나 내가 비교할 필요가 없다. (score 값을 비교하여 정렬을 하기 때문에 내가 일일이 score 값을 비교할 필요가 없다)

🐬 docker로 redis 사용하기

$ docker run --name redis_prac -p 6379:6379 -d redis:alpine

docker image를 사용하여 redis를 사용할 수 있다. 도커 짱

📊 sorted set 명령 알아보기

(0) redis-cli 접속하기

$ redis-cli

(1) member 추가하기

$ ZADD <key> <score> <member>

(2) member의 score 값 조회하기

$ ZSCORE <key> <member>

(3) score 작은 순서로 순위 조회하기

1순위부터 보고 싶다면 0을 사용해야 한다!

$ ZRANGE <key> <시작> <>

(4) score 큰 순서로 순위 조회하기

$ ZRANGE <key> <시작> <> REV

(5) score가 작은 순서대로 정렬했을 때 member 순위 조회하기

$ ZRANK <key> <member>

(6) score가 큰 순서대로 정렬했을 때 member 순위 조회하기

$ ZREVRANK <key> <member>

🔔 NestJS로 redis sorted set 사용해보기

docker-compose.yml

redis:
  image: redis:7.0
  container_name: redis
  env_file: ./.env
  restart: always
  ports:
    - $REDIS_LOCAL_PORT:$REDIS_DOCKER_PORT
  command: redis-server --requirepass ${REDIS_PASSWORD}

NestJS와 Mysql을 이미 docker-compose로 묶어서 사용하고 있어 redis도 docker image를 사용하였다.
각각 변수들은 .env로 관리해 주었다.

라이브러리 설치

$ npm install @liaoliaots/nestjs-redis ioredis

app.module.ts

...
import { RedisModule } from '@liaoliaots/nestjs-redis';

@Module({
  imports: [
    RedisModule.forRoot({
      config: {
        host: 'localhost',
        port: 6379,
        password: process.env.REDIS_PASSWORD,
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

라이브러리에서 제공하는 RedisModule을 사용하여 app.module.ts에 redis 연결을 위한 설정을 해준다.

service.ts

import { Injectable } from '@nestjs/common';
import { InjectRedis } from '@liaoliaots/nestjs-redis';
import Redis from 'ioredis';

@Injectable()
export class AppService {
  constructor(@InjectRedis() private readonly redis: Redis) {}

  async setRank(user_number: number) {
    const user_score = await this.redis.zscore('rank', `user:${user_number}`);
    await this.redis.zadd('rank', +user_score + 1, `user:${user_number}`);
  }

  async getRank() {
    const result = await this.redis.zrevrange('rank', 0, 6);
    return result;
  }
}

호출할 때마다 해당 user의 score 값을 1씩 증가시키는 setRank 함수와 순위를 가져오는 getRank 함수를 만들어주었다. 라이브러리에서 대부분의 함수를 redis-cli에서 사용하는 명령어 이름으로 작성해두었기 때문에 원하는 기능의 함수를 찾기 편했다!

docker image를 실행시킬 때 redis에 비밀번호 설정을 해주었기 때문에 auht <password> 명령을 통해 접근해야 redis-cli 사용이 가능하다.

실시간 랭킹 기능을 구현하기 위해 redis의 sorted-set을 사용해 보았는데 사실 redis는 cache 용도로 가장 많이 사용된다. 그래서 나도 조만간 redis를 사용해 다양한 캐싱 전략을 직접 구현해 보고 글로 정리하고자 한다 🔥

참고
https://redis.io/docs/data-types/sorted-sets/
https://hub.docker.com/_/redis
https://github.com/liaoliaots/nestjs-redis
https://luran.me/359

profile
호기심 많은 백엔드 개발자입니다 😝

0개의 댓글