TIL

24/01/23 TIL __ 채팅기능 캐싱하기(redis)

GABOJOK 2024. 1. 23. 23:48

 

 

실시간 채팅 기본기능 구현 후 추가적으로 실시간 채팅 도배 방지기능을 구현하려 했다.

 

처음에 생각한 방향은 이랬다.

유저가 현재 보낸 시점 기준으로 최근 10초간 n  회 이상 채팅을 입력했을 경우

이를 감지해서 일정 시간동안 채팅입력을 금지하는 기능을 넣고자 했다. 

이를 위해 유저가 데이터를 입력할때마다

db에  해당 유저 채팅 기록이 마지막 10초동안 몇개정도 있는지 확인후,

그에 맞는 로직을 작성하면 가능하겠다 싶었다.

 

그러나 !!!

이는 유저가 채팅을 보낼때마다 db를 조회하고 확인하는 번거로운 과정을 거쳐야만 한다. 

즉 트래픽이 몰리면 답이 없다. 

트래픽 경험을 염두에 두고 시작한 프로젝트였기 때문에 이와 연관된 기능인

캐싱 기능을 함께 곁들이고자 했다. 

 

 

🦊  Cache DB 선택 과정

cache db에도 종류가 정말 많았다. 

이중에서 어떤것이 지금 진행하는 프로젝트에 적합할지 알아봤다. 

 

먼저 가장 많이 이야기되는 Redis, Memcached, 그리고 기존 고민하던 db와 어떤게 다른지 Cassandra,  MongoDB 이들을 놓고 비교해 보았다. 

 

Redis와 Memcached는 Key Value타입의 db이고

mongoDB는 document타입, 

Cassandra는  column-family 스토어 타입으로 분류한다. (키 벨류 스토어 확장형).

 

각 db마다의 특성이 있고, 각각의 장단점이 있는데, 우리 서비스에는 어떤게 필요할까? 

실시간 채팅기능인 만큼  쓰기기능, 읽기기능 모두 많이 사용될것으로 생각되었다. 

 

상황 카테고리 최적의 db  차선의 db 최적db 측정값  차선db 측정값
키/값 쓰기  시간 memcached redis 2813ms 14638ms
쓰기  메모리 사용량 redis cassandra 62.7mb 102mb
읽기 시간  cassandra redis 6ms 8ms
읽기  메모리 사용량 cassandra redis, mongodb 0mb 1.3mb
모든 데이터 get 시간 mongodb redis 8ms 11ms
모든 데이터 get 메모리 사용량 mongodb cassandra 0.8mb 1.3mb

 

아래 자료를 보며 정리했다. 자세한 사항은 아래 링크를 보는걸 추천한다. 

https://www.sciencedirect.com/science/article/pii/S1319157816300453?via%3Dihub#t0015

 

살펴보면, 

Memcached는 시간 측면에서 최고의 쓰기 성능을 제공한다.

그러나 이를 제외한 다른 부분에서는 큰 장점을 보이지 못했고,

채팅기능은 쓰기기능만 존재하는게 아니기 때문에 cache db로 적합하지 않다고 생각되었다. 

 

 

반면 Redis는 다른 것보다 메모리를 더 효율적으로 사용한다.

읽기작업에 있어 메모리 측면을 보면 cassandra 가 1위이긴 하지만, 실제 2위와의 차이는 1.3mb밖에 나지 않는다. 

시간적 측면을 따져봐도 1위에 비해 2ms의 차이를 가지고 있다.

 

이에 반해 쓰기작업에 있어 메모리 측면을 보면, redis가 1위이며, 2위인 cassandra와는 거의 40mb 정도의 차이를 내고 있다. 

시간적 측면으로 보면 Memcached에 비해 5배 이상 차이가 나지만, 그럼에도 불구하고, 

또한 Redis는 읽기 작업 에서 Memcached 및 MongoDB  보다 훨씬 더 나은 성능을 보여준다. 

또 클러스터 모드도 제공하는 큰 장점이 있다. 
    
MongoDB는 전체 데이터를 가져오는 데 있어 최고의 성능을 제공하지만,

이미 채팅기능에서 도입된 db이며, 이 4가지 db중 가장 긴 쓰기 시간을 소요하기 때문에 cache db 로 적합하지 않다고 생각되었다.

 

따라서 모든 면에서 괜찮은 성능을 제공하는 레디스로 결정했다. 

 

 

 

 

🪴  레디스의 데이터 타입 선택

 

레디스는 정말 많은 데이터 타입을 지원한다. 

처음 레디스를 살펴봤을 때에는 레디스에 다양한 데이터 타입에 대해 전혀 알지 못했던 때라 정말 혼돈의 카오스였다. 

이번 프로젝트에서는 어떤 데이터 타입이 현재 우리의 프로젝트에 적합할지 알아보기로 했다. 

 

string, list, set, hash, sorted set, bitmaps, hyperloglogs

 

정말 많은데,  간략하게 어떤 것들인지만 알아보았다.

 

 

🐈‍⬛ String


- 단순 증감 연산에 적합. 
- 일반적 문자열,
- jpeg이미지, 
- string이 가능한 binary data
      - binary data란 이미지, 오디오파일 등.

 

 

 

🐈‍⬛ Bitmaps

- string 의 변형
- bit 단위 연산이 가능. 
- 저장공간 절약의장점. 
- key(문자열) / offset(비트위치 정보 숫자)/ value(오직 0,1만 가능)

 

🐈‍⬛ List


- array 형식 데이터 구조.
- 그러나 배열처럼 연속된 메모리 주소를 할당받는게 아니라 node라는게 존재하고, 

- 이 안에 데이터가 있으며, 이걸 가리기는 주소를 할당받는다.
- 순서대로 데이터 저장.
- double-ended linked list로 구현.
- 양방향 즉 리스트의 앞과 뒤 양쪽에서 요소를 추가하거나 제거할 수 있다. 
- 리스트 중간에 있는 요소에 접근하거나 수정하는 작업은 상대적으로 느릴 수 있다.
- 리스트의 크기는 동적으로 변하고, 레디스에서 리스트의 크기 제한이 없다(메모리 한도 내) 

----> 메모리 효율 높아지는 장점이 있고 이래서 대규모 데이터 타입 처리에 많이 사용한다
- 배열은 크기를 미리 정해놓게 되는 정적 자료구조인데, 이에 따라 수정 불가
- 연속된 메모리의주소 할당하는 배열
- 히스토리 가능에도 사용.

 

 

🐈‍⬛ Hashes

- field 와  value로 구성되어있다.
- key하위에 subkey를 이용해서 추가적 hash table을 제공.
- 메모리 허용 한도 내에서 field 맘껏 가능.

//key subkey value subkey value .... 의 형태로 저장.

HSET user:1001 name "홍길동" email "gildong@example.com" phone "010-1234-5678"



🐈‍⬛ Sets

- 중복된 데이터를 담지 않기 위해 사용.
- 유니크한 key값.
- 교집합, 합집합 이런 차이를 빠르게 추출 가능.

 

 

 

🐈‍⬛ Sorted Sets

- set에 score라는 필드가 추가된 데이터형
- 일반 set은 그냥 insert한 순서대로 들어가는데,
- 데이터가 저장될 때 부터 score 순으로 정렬 저장.
- 오름차순 내부정렬함.
- score는 중복이 가능하고, 이땐 사전순으로 정렬.
- 유저 랭킹 같은거에 적합.

 

 

🐈‍⬛  HyperLogLog

- 대규모 데이터 집합에서 고유한 요소의 개수 추정할 때 효율 좋다.
- 고유요소 카운트 추정할때 매우 적은양 메모리 사용하며,
- 정확한 카운트가 아닌 추정치를 제공한다. 오차율은 0.81% 내외.
- 어떤 이벤트 발생 횟수, 혹은 고유 방문자 수 추정 등에 이용.

 

 

🐈‍⬛  Streams

- 실시간 메세지, 이벤트 소싱, 로그수집 등 다양한 용도로 사용될 수 있는 시간따라 정렬된 메세지 시퀀스. 
- 이에따라 고유한 아이디가 부여, 이 아이디는 시간에 따라 정렬.
- append only. 중간에 데이터 바꿀수 없음. 쓰기, 읽기만 가능
- id기반 시간 범위로 검색해서 읽어오고
- 메세지 순서를 유지하면서 실사간으로 데이터를 처리하는 다양한 애플리케이션에 적합.
- 여러 필드도 지정할 수 있음.
- 해쉬와 차이는 sreams는 좀더 시간 위주, hashed는 데이터 구조화 및 효율 적 저장 위주.정적 데이터 저장에 적합.

//이런 구조로 진행
XADD <stream name> <ID> <field1> <value1> <field2> <value2> ...

//예시
XADD orders * order_id 12345 item "coffee"

 

 

 

💜 결정

 

여러가지 타입을 알아봤을때 채팅 메세지 캐싱 관련 기능은 

Streams 타입을 사용해서 진행하기로했다.

키 안에 다양한 필드가 존재해야 했기 때문에(유저닉네임, 내용) 

처음에는 아래와 같이 생각하고 알아보기 시작했었다.

 

1. string타입을 사용해서 문자열을 _ 로 구분해서 사용한다.

2. hash타입을 사용한다

 

그러나 막상 조사해보고 나니, Streams 타입이 현재 구현하려고 하는 기능에 적합해 보였다.

실시간 데이터 캐싱에 효율적이고, 시간따라 정렬도 해주며,

삭제, 수정할 일은 없는데 오직 쓰기 읽기만 가능하다는 단점도 중요하지 않았다.

메세지 순서를 유지한 다는 장점도 매우 매력적이였다.

거기에 Hashed처럼 여러 필드를 함께 넣을 수 있어 좋았다. 

 

(문득 hashed와 차이점이 무었일까 궁금해 찾아봤는데, 

hashed는 데이터 구조화 및 효율적 저장에 좀더 특화되어 있는 반면, 

Streams는 시간적 처리, 순서 에 좀더 집중되어 있었다.)

 

 

 

 

 

참고한 사이트 

https://www.sciencedirect.com/science/article/pii/S1319157816300453?via%3Dihub#t0015

 

A performance evaluation of in-memory databases

The popularity of NoSQL databases has increased due to the need of (1) processing vast amount of data faster than the relational database management s…

www.sciencedirect.com

https://kjw1313.tistory.com/110

 

Redis 적용 및 다양한 DataType

1. Redis 설치 소스와 바이너리 형태로 제공하며, 주로 리눅스에서 설치해 사용한다. 물론 Redis는 OS에 상관없이 어디든 사용 가능하다. window 설치, Mac 설치 링크를 참고. 편의를 위해서 이 포스팅에

kjw1313.tistory.com

 

https://blacklobster.tistory.com/8

 

간략 설명! 배열(Array)과 연결 리스트(Linked List)에 대해서 알아보자! - 차이점과 시간복잡도, 활용

예전에 프론트엔드 개발자 기술 면접 준비를 하면서 공부했던 배열(Array)과 연결 리스트(링크드 리스트, Linked List)에 대해서 간략하게 글을 써보려고 합니다. 자료구조 공부하면서 배열과 연결

blacklobster.tistory.com

https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85Collection-%EC%A2%85%EB%A5%98-%EC%A0%95%EB%A6%AC

 

[REDIS] 📚 자료구조 명령어 종류 & 활용 사례 💯 총정리

Redis 데이터 타입 (Collection) Redis의 장점 중 하나는 Key-Value 스토리지에서 Value는 단순한 Object가 아니라 다양한 자료구조를 갖기 때문이다. String, Set, Sorted Set, Hash, List 등 다양한 타입을 지원한다.

inpa.tistory.com