Data Structure Use Case(자료 구조 활용 사례)

Data Structure Use Case(자료구조 활용 사례)

Sorted Set - 실시간 리더보드

랭킹 산정 과정에서 가중치를 부여할 수 있고, 실시간으로 랭킹을 집계해야하는 경우엔 RDBMS는 성능 이슈가 존재할 수 있다. 이러한 요구사항을 충족시키기 위해선 Sorted Set이 가장 적합한데, 데이터가 저장될 때부터 정렬되어 있기 때문에 랭킹을 산정하는데 유리하다.

  • Score에 가중치를 부여해 랭킹을 산정

  • ZADD 커맨드를 통해 데이터를 추가 및 갱신하여 항상 정렬된 상태로 유지

  • ZRANGE / ZREVRANGE 커맨드를 통해 특정 범위의 데이터를 조회

랭킹 합산

리더보드가 일정 기간마다 초기화 된다고 할때, 다음과 같은 방법으로 랭킹을 합산하려 시도해볼 수 있다.

  1. 일별로 랭킹을 저장하는 Sorted Set을 생성

  2. 하나의 테이블에서 일별로 저장된 데이터를 모두 가져온 뒤 사용자별로 합산

  3. 합산된 값을 정렬하여 랭킹을 산정

하지만 레디스의 ZUNIONSTORE 커맨드를 사용하면 더욱 간단하게 랭킹을 합산할 수 있다.

ZUNIONSTORE weekly-score:202408-3 3 daily-score:20240815 daily-score:20240816 daily-score:20240817 WEIGHTS 1 2 1
-- ZUNIONSTORE  <생성할 키 이름> <합산할 키 개수>  <합산할 키1>  <합산할 키2>  ...  [WEIGHTS <가중치1> <가중치2> ...]

Sorted Set - 최근 검색 기록

이 기능을 일반적은 RDBMS로 구현하면 아래와 같은 쿼리로 구현해볼 수 있다.

SELECT *
FROM keyword
WHERE user_id = 123
ORDER BY reg_datge DESC
LIMIT 10;

여기서 중복 검색어 제거 및 오래된 검색 기록 삭제 등 여러 작업이 추가적으로 필요할 수 있어 복잡해질 수 있다.

하지만 Sorted Set을 사용하면 아래와 같은 이유로 간단하게 구현 할 수 있다.

  • Set이기 때문에 저장될 때부터 중복을 허용하지 않음

  • Score에 시간을 부여해 자동으로 정렬되어 저장

  • 키 값은 serach-keyword:<user_id>로 저장하여 사용자별로 구분하여 저장

  • 데이터 삭제는 ZREMRANGEBYRANK search-keyword:<user_id> -6 -6 커맨드를 이용해 특정 범위의 데이터를 삭제

Sorted Set - 태그

특정 오브젝트에 대한 태그를 지정하는 기능이 추가될 수 있는데, RDBMS로 구현하면 적어도 두 개의 테이블이 추가로 필요하다.

  1. 태그 테이블

  2. 태그-오브젝트 매핑 테이블

마찬가지로 레디스의 Set을 사용하면 아래와 같은 이유로 간단하게 구현 할 수 있다.

SADD posts:59:tags IT REDIS NOSQL
-- SADD <태그를 저장할 키> <태그1> <태그2> ...

또한, 특정 태그를 가진 오브젝트를 조회하는 경우에도 아래와 같이 간단하게 조회할 수 있다.

SADD tag:IT:posts 59
SMEMBERS tag:IT:posts
-- 특정 태그를 가진 오브젝트 조회
SINTER tag:IT:posts tag:REDIS:posts
-- 여러 태그를 가진 오브젝트 조회(교집합)

랜덤 데이터 추출

보통 RDBMS에서 랜덤 데이터를 추출할 때에는 ORDER BY RAND()를 사용하는데, 다량의 데이터를 처리할 경우 성능 이슈가 발생할 수 있다. 하지만 레디스를 사용하면 아래와 같이 간단하게 랜덤 데이터를 추출할 수 있다.

RANDOMKEY
-- 랜덤 키 추출, 하지만 레디스 인스턴스에 한 종류의 데이터만 저장되어 있을 경우에만 사용 가능
HRANDFIELD user:hash WITHVALUES 3
-- 원하는 개수만큼 랜덤 필드와 값을 추출(개수를 음수로 지정하면 중복을 허용)

다양한 카운팅 방법

데이터 개수를 세는 요구 사항은 다양한 방법으로 구현할 수 있는데, 요구 사항에 따라 최적의 구현 방법을 선택해야 한다.

  1. 단순히 데이터 개수만 세는 경우

  2. 어떤 아이템이 저장됐는지까지 알아야 하는 경우

  3. 약간의 오차를 허용하면서 빠르게 데이터 개수를 세는 경우

좋아요 처리

좋아요 처리는 규모에 따라 많은 트래픽이 발생할 수 있으며, 한 사용자가 중복으로 좋아요를 누르는 경우를 방지해야 한다. 이를 위해 레디스의 Set을 사용하면 아래와 같이 간단하게 구현 할 수 있다.

SADD comment-like:12554 user:1001
-- 키를 사용자 ID로 저장하여 중복 좋아요 방지
SCARD comment-like:12554
-- 좋아요 개수 확인

읽지 않은 메시지 개수

읽지 않은 메시지는 중복된 데이터를 고려할 필요 없이 단순히 개수만 세면 되기 때문에 해시를 사용하면 아래와 같이 간단하게 구현 할 수 있다.

HINCRBY user:234 channel:35 1
-- 채널에 새로운 메시지가 도착할 때마다 1씩 증가
HINCRBY user:234 channel:35 -1
-- 메시지를 읽으면 감소

DAU

DAU는 다른 데이터들보다 훨씬 큰 숫자를 처리해야함과 동시에 중복을 허용하지 않아야 한다. Set을 저장하는 방법을 고려해볼 수 있다면, 너무 많은 데이터를 저장해야하기 때문에 메모리 사용량이 증가하고, 성능 저하로 이어질 수 있다.(보통 키 하나당 200~300만개까지 권장)

때문에 다른 방법을 사용해야하는데, String 자료 구조에 Bit 연산을 수행하여 해결할 수 있다.(사용자 id는 정수값이어야 함)

SETBIT dau:20240815 1001 1
-- 1001번 사용자가 2024년 8월 15일에 접속했음을 1로 표시
BITCOUNT dau:20240815
-- 2024년 8월 15일에 접속한 사용자 수(1의 개수) 확인
BITOP AND dau:20240815 dau:20240816 dau:20240817
-- 2024년 8월 15일, 16일, 17일에 모두 접속한 사용자 수 확인

Geospatial - 위치 기반 애플리케이션

위치 데이터는 주로 경도와 위도 좌표 쌍으로 표현되며, 보통 위치 기반 애플리케이션은 다음과 같은 기능을 제공하고 있다.

  • 사용자의 현재 위치 파악

  • 사용자의 이동에 따른 실시간 위치 업데잍츠

  • 사용자 위치 기준으로 주변 정보 제공

일반적인 데이터 저장소는 단순히 위치 데이터를 저장하는 것으로 끝나지만, 레디스의 Geospatial 자료 구조를 사용하면 다음과 같이 더욱 효율적으로 처리할 수 있다.

GEOADD user 50.1234 30.1234 142
-- ID가 142인 사용자의 위치를 저장 및 업데이트
GEOPOS restaurant ukalendu
-- ukalendu 레스토랑의 위치 조회
GEOSEARCH restaurant FROMLONLAT 50.1234 30.1234 BYRADIUS 10 KM
-- 50.1234, 30.1234 좌표에서 10km 이내에 있는 식당 검색
GEOSEARCH key FROMMEMBER member BYBOX 4 2 KM
-- 특정 멤버 주변의 데이터 검색 및 직사각형 영역 검색

참고자료

Last updated

Was this helpful?