Post

Redis 활용하여 랭킹 기능 구현하기

본 포스트는 아래의 환경을 기준으로 작성되었습니다.
Redis 7.0

데이터베이스 쿼리가 아닌 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 값을 비교할 필요가 없다)

sorted set 명령 알아보기

(0) redis-cli 접속하기

1
$ redis-cli

(1) member 추가하기

1
$ ZADD <key> <score> <member>

add_member

(2) member의 score 값 조회하기

1
$ ZSCORE <key> <member>

get_score_1

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

1순위부터 보고 싶다면 0을 사용해야 합니다.

1
$ ZRANGE <key> <시작> <끝>

get_score_2

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

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

<img src=”https://velog.velcdn.com/images/chaerim1001/post/c691eaaf-517d-4e3d-b367-9c260cf085a6/image.png” alt=”get_score_3>

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

1
$ ZRANK <key> <member>

get_rank_1

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

1
$ ZREVRANK <key> <member>

get_rank_2

Example

지금부터는 NestJS에서 Redis의 Sorted Set을 활용하여 랭킹 기능을 구현해 보겠습니다.

(1) docker-compose.yml

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

1
2
3
4
5
6
7
8
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}

(2) 라이브러리 설치

1
$ npm install @liaoliaots/nestjs-redis ioredis

(3) app.module.ts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 {}

(4) service.ts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
  }
}

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

docker_redis_login


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

This post is licensed under CC BY 4.0 by the author.

Comments