ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redis란?
    DataBase/Redis 2021. 5. 19. 14:39
    반응형

    회사에서 레디스를 사용을 한다. 내가 레디스에 아는 내용은 '레디스는 캐시서버이다. 그래서 빠른 조회 성능을 가진다.' 이 두가지 밖에 없었다. 이렇게 된다면 실무에 빠르게 적응하기가 힘들다고 판단을 하였고, 주말에(어버이날 일정 끝낸다음) 공부를 진행중이다. 아무것도 아는 내용이 없었기 때문에 영상를 보면서 공부를 하였다.

     

     

     

     

    Redis란?

    • In Memory Data Structure Store
    • Open Sorce(BSD 3 License)
    • 키-값 구조 관리 시스템 - 비 관계형이며, 키-값으로 구성되어있기 때문에 쿼리 없이도 데이터를 가져 올 수 있다.
      • 키-값 구조뿐만 아니라 아래의 자료구조를 지원 한다.
        • Strings, set, sorted-Set, hashes, list
          • Strings - Key Value
          • Sorted-Set - 정렬되어있는 Set
        • Hyperlog, bitmap, geospatial index
        • Stream
    • Only 1 Commiter
    • 캐시 또는 Persistence Data Storage로 사용 가능하다.

     

     

    캐시(Cache)를 사용할 때

    캐시란?

    나중에 요청할 결과를 미리 저장해주었다가 빠르게 서비스를 해주는 것

     

    사용 이유 및 예시

    사용 이유

    보통 캐시서버의 데이터는 In-Memory에 존재 하기 때문에 디스크에 접근하여 데이터를 가져오는 것보다 접근속도가 매우 빠르다. 

     

    예시

    전 회사에서 홍수, 태풍이 발생할 시 피해액을 계산해주는 일들이 매우 시간이 걸렸다. 그렇기 때문에 사용자가 요청할 때마다 피해액을 계산해주는 것이 아니라 피해액 계산에 필요한 데이터가 존재할 때 피해액을 계산하여 캐시서버에 저장을 하였다. 이렇게 된다면 나중에 사용자는 결과를 빠르게 받아 볼 수 있을 뿐만 아니라, 결과 데이터를 통하여 다른 작업을 할 수 있기 때문에 사용자는 성능이 매우 빠르다라고 생각할 수 있었다.

     

     

    사용법

    Look aside Cache

    1. 클라이언트의 요청은 받은 Web Server는 데이터가 존재하는지 먼저 Cache를 확인
    2. Cache에 데이터가 있으면 Cache에서 가져온다.
    3. 만약 Cache에 데이터가 없다면 DB에서 가져온 후 Cache에 다시 저장한다.

    Write Back

    1. 클라이언트의 요청은 받은 Web Server는 모든 데이터를 Cache에 저장
    2. Cache에 특정 시간동안의 데이터가 저장
    3. Cache에 있는 데이터를 DB에 저장한다.
    4. DB에 저장된 데이터를 삭제한다.

    쉽게 말하면 데이터를 전부 캐쉬에 저장하고, 특정시간마다 캐시에 저장된 데이터를 DB에 저장하는 방법이다.
    insert를 1개식 500번 수행하는것(Look aside Cache)보다 한번에 500번을 저장하는 것이 빠르기 때문에 성능상으로는 뒤쳐지는 방식은 아니다.

    일정시간동안 데이터를 유지해야 되지만 이 데이터의 저장공간은 메모리 공간이기 때문에 문제가 발생이 될 시 데이터가 손실될 수 있다는 단점을 가진다. 그렇기 때문에 다시 재생가능한 데이터나, 극단적으로 heavy 한 데이터에서 write back 많이 사용한다.

     

     

    레디스의 특징

    레디스는 Colleciton을 제공을 해준다. 하지만 Memcached는 제공하지 않는다. 즉 레디스와 Memcached는 Collection을 제공하냐 안하냐의 차이가 있다.

     

    사용자가 많은 부분에서 랭킹을 구현

    • 디스크 기반 storage(DataBase)에 모든 사용자를 저장하고 Order By 정렬 한 후 가져온다. 
      • 하지만 이러한 방법은 데이터가 많으면 많을수록 속도가 느려지는 단점이 발생이 된다.
    • Redis 의 Sorted Set 을 사용하면 랭킹 서버를 쉽게 구현 가능하며 replication 까지도 가능하다. 하지만 이렇게 제공하는 걸 가져다가 쓴다는 건 한계에 종속적이 되긴 한다.
      • 랭킹에 저장장할 ID가 1개 당 100byte 일때 10,000,000,000 명 1TB 이기 때문에 한계에 봉착할 수 밖에 없다.

    친구 리스트를 KEY / VALUE로 저장

    Transaction 1 (친구 B를 추가)   Transaction 2 (친구 C를 추가)
    1. 친구리스트 friend:123를 읽는다.
    2. friend:123 끝에 B를 추가한다.
    3. 해당값을 friend:123에 저장한다.
    1. 친구리스트 friend:123를 읽는다.
    2. friend:123 끝에 C를 추가한다.
    3. 해당값을 friend:123에 저장한다.

    위와같이 트랜잭션 2개가 동시에 발생이 되었을 때 일어나는 문제는 아래와 같다.

    Race condition

    아래의 표를 보게 된다면 3번재와 4번째 프로세스가 순서대로 진행이 된다면 리스트 덮어쓰기가 발생이되고, 최종 형태에서 T1과 T2 둘 중 어떤것이 먼저 발생할지 예측할 수 없기 때문에(context switching) 최종데이터는 A,B / A,C 이 랜덤으로 유지가 될 수 있다.

     

    시간순서 Transaction 1 Trancation 2 최종형태
    1 friend:123 읽기   A
    2   friend:123 A
    3 친구 B추가   A
    4   친구 C추가 A
    5 friend:123 쓰기   A,B
    6   friend:123 쓰기 A,C

    레디스에서의 Race condition

    레디스는 Atomic하기 때문에 Race conditon을 피할 수가 있다. 즉 레디스 Transaction은 한번의 하나의 명령만 수행할 수 있다. 게다가 single-threaded 특성을 유지하고 있기 때문에 다른 인메모리보다는 이슈가 덜하다.

    하지만 이 특징이 더블클릭 같은 동작으로 같은 데이터가 2번씩 들어가게 되는 불상사는 막을 수 없기 때문에 별도 처리가 필요하다.

    따라서 레디스는 remote data storage 로서 여러 서버에서 같은 데이터를 공유하고 보고싶을 때 많이 사용한다. 그래서 우리는 인증 토큰을 저장(String, Hash)하거나 유저 API limit 을 두는 상황 등에서 레디스를 많이 사용하고 있다.

     

     

    레디스 Collection

    Collecgtion 종류

    String

    가장 일반적인 형태로, key - value 로 저장하는 형태이다.

    List

    Array 형식의 데이터 구조이다. List 를 사용하면 처음과 끝에 데이터를 넣고 빼는건 속도가 빠르지만 중간에 데이터를 삽입하거나 삭제하는건 어려움이 있다.

    Set

    중복된 데이터를 담지 않기 위해 사용하는 자료구조이다. 중복된 데이터를 여러번 저장하면 최종 한번만 저장된다.
    이걸 사용했을 때 모든 데이터를 전부 다 갖고올 수 있는 명령이 있으므로 주의해서 사용해야 한다.

    Sorted Set

    Set과 같은 형태를 가졌지만 순서가 존재한다.

    유저 랭킹 보드서버 같은 구현에서 사용할 수 있다. 그럼 이때 데이터 삽입은 ZADD key 점수 value 명령어로 수행하고 정렬된 값은 zrange 를 통해 가져올 수 있다.

     

    Collection 사용시 주의 사항

    • 하나의 컬렉션에 너무 많은 아이템을 담으면 좋지 않음
      • 10000개 이하 몇천개 수준으로 유지하는게 좋음
    • Expire는 collection의 item 개별로 걸리지 않고 전체 collection에 대해서만 걸림
      • 즉 10000개의 아이템을 가진 Collection에 exprice가 걸려있다면 그 시간 후에 10000개의 아이탬이 모두 삭제

     

     

    레디스 운영

    1. 메모리 관리를 잘하자

    Max Memory : Redis 가 아는, 자기가 사용하는 메모리

    레디스는 메모리 할당이나 해제같은 관리에 메모리 풀을 사용하지 않는다. Memory Allocate 의 구현에 따라서 레디스 성능이 왔다갔다 할 수 있다.

    게다가, 메모리 파편화 때문에 Max Memory 를 설정하더라도 이보다 더 사용하게 될 수도 있으므로 별도 관리가 필요하다.
    이게 무슨 말인지 좀 더 설명해보겠다.

    메모리 페이지 사이즈가 4096 일때, 우리가 1byte 만 달라고 요청하더라도 실제로 jemalloc 은 4096 byte 를 가져와야한다. jemalloc  메모리를 페이지 단위로 반환하기 때문이다.

    따라서 만약 여기서 또 내가 + 4096 byte 를 더 달라고 요청하면? 나는 실제로는 4097 만 쓰고있지만, 8K 만큼을 할당받아서 사용하고 있는 꼴이 된다.

    메모리 파편화 가 바로 이런 현상인데, 4.x 대부터 메모리 파편화를 완화시키고자 jemalloc 에 힌트를 주는 기능이 들어갔었다. 하지만 jemalloc 버전에 따라 다르게 동작할 수 있기 때문에 확신을 주는 기능은 아니라고 한다.

    그럼 Redis 를 운영하며 메모리 파편화를 좀 덜 일으킬 수 있는 방법은 없을까?

    메모리 파편화를 좀 완화시키고 싶다면,

    • 다양한 사이즈를 가지는 데이터보다는 유사한 크기의 데이터를 가지는 경우가 유리하다. 그래서 메모리를 유사한 크기로 두고 관리해주어야 한다.
    • 큰 메모리를 사용하는 instance 하나보다는 적은 메모리를 사용하는 instance 여러개가 안전하다. 관리는 귀찮을 수 있지만 운영의 안정성은 높을 수 있다.

    두 방법을 접목시키면, 결국 이런 식으로 표현할 수 있다.

    24 GB instance < 8GB Instance * 3

    Redis는 쓰기 요청이 발생하면 copy on write 방식으로 작동한다. 따라서 쓰기 작업을 시작하는 순간 필연적으로 fork() 를 수행해서 갱신할 메모리 페이지를 복사한 후 쓰기 연산하는 구조이다. 당연히 여기서 최대 메모리를 2배 까지 쓰게 될 수 있다. 이건 우리가 셋팅하는 maxmemory 설정 의도와 조금 다르게 동작하는 부분이므로 주의가 필요하다. 또, 참고로 read 수행에는 메모리 복사가 발생하지 않는다.

    Redis 의 쓰기연산이 이렇게 동작하고 있기 때문에 위와 같이 인스턴스를 쪼개서 운영하고 관리하는 게 좋다.
    8GB Instance 를 썼을때 write → fork() 하면 32GB Instance (8GB * 3 + 8GB) 가 되는데, 24GB Instance 1개 였을때 fork() 하면 최종적으로는 48GB 의 메모리를 써야한다.

    그럼 메모리가 부족할때는 어떤 조치를 하면 좋을까.

    이럴 땐 좀 더 메모리 공간이 여유로운 장비로 마이그레이션이 필요한데, 메모리가 빡빡하면 마이그레이션 중에도 문제가 발생할 수도 있다. 따라서 메모리를 줄이기 위한 설정들이 있는지 정리해보았다.

    • 메모리 줄이기 위한 설정들
      • Hash  HashTable 을 하나 더 사용한다.
      • Sorted Set  Skiplist  Hash Table 을 둘 다 사용한다. 값으로도 찾아야하고, 인덱스로도 찾아야하므로.
      • Set  Hash Table 을 사용한다.

    하지만 Skiplist  Hash Table 자료구조도 내부적으로 동작하는 메모리 단편화나 포인터 같은게 있기 때문에 결과적으로는 메모리를 적지 않게 사용한다. 따라서 다른 대안도 생각해 볼 만한데,
    만약 1개 컬렉션에 데이터가 많다면 ziplist 를 사용하는게 속도는 조금 느려지지만 메모리는 적게 쓰는 방법이다. 게다가 원래 쓰는 자료구조 대신 내부적으로 ziplist 를 쓰도록 간단히 설정만 바꿔줄 수도 있다.

    • zip list 를 쓸 수 있는 이유
      in-memory 특성 상, 적당한 사이즈의 데이터까지는 특정 알고리즘을 안쓰고 그냥 풀서치(선형 탐색)를 해도 빠르다. 실제로 zip list 를 쓴 것과 안쓴것의 메모리 사용량은 2-30% 정도 차이가 난다고 한다.

     

     

     

     

     

     

    참고

    velog.io/@hyeondev/Redis-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C

    www.youtube.com/watch?v=mPB2CZiAkKM

    반응형

    'DataBase > Redis' 카테고리의 다른 글

    레디스 사용법  (0) 2021.06.06
Designed by Tistory.