본문 바로가기

Developer/DataBase

redis 란, 기본사용법

<목차>

  • Redis 소개
  • 왜 Collection이 중요한가?
  • Redis Collections
  • Redis 운영
  • Redis 데이터 분산
  • Redis Failover

<다루지 않을 것>

  • Redis Persistence(RDB, AOF) : 메모리에 저장하는것. 실시간으로 사용 불가함
  • Redis Pub/Sub
  • Redis Stream
  • 확률적 자료구조 : Hyperloglog
  • Redis Module

 

1. Redis 소개

  • In-Memory Data Structure Store
    인메모리데이터이기 때문에 버그패치 등 직접 고쳐서 사용할 수 있지만, 기업용으로 사용할 경우에는 고치면 외부에 공개해야함.
  • Open Source(BSD 4 License)
  • Support data structures
    • Strings(key-value), set, sorted-set, hashes, list
    • Hyperloglog, bitmap, geospatial index
    • Stream
  • Onlye 1 Committer - 레디스를 고칠수 있는 사람은 전세계에 한 명

 

Cache란? Cache를 왜 쓸까요?

나중에 요청을 결과에 미리 저장해두었다가 빠르게 서비스 해주는 것을 의미한다.

 

Factorial?

10! = 10 * 9! = 10 * 9 * 8! ...

20800!를 계산해두고 어딘가에 저장해뒀다가 20811! 계산은 금방한다. -> 다이나믹 프로그래밍처럼 생각하면된다.

 

 

CPU Cache

용량은 위로갈수록 크고

속도는 아래로갈수록 빠르다.

우리가 쓰는 레디스는 Memory에 있다.

 

어디서 많이 사용하나요?

웹 서비스 구조로 생각했을 때,

DB 도 내부적으로 캐시가 있긴하지만, 여러가지를 계속 접근하다보면 캐시를 계속 삭제하기 때문에 다시 또 디스크에 접근하는 경우가 발생한다.

Cache 구조 #1 - Look aside Cache

일반적으로 가장 많이 쓰는 형식

 

Cache 구조 #2 - Write Back

write 하는 데이터가 많은 경우에 사용한다.

쓰는 데이터가 너무 많을 경우 데이터를 Cache 에 저장해놓고 특정 시간마다 DB에 적재한다.

 

일반적으로 인서트 1번씩 인서트 500번을 날리는것보다, 한번에 500개를 붙여서 날리는게 말도안되게 빠르다. : 배치작업

 

Write Back의 단점 : 

메모리에 저장되어있기 때문에, 리부팅하면 데이터 날아간다.

장애 발생할 경우 데이터 모두 날아갈 수도있다.

 

로그를 DB에 저장하시나요?

로그를 DB 에 저장할 때, 매번 데이터를 저장하지 않고 Write Back 을 사용하면 좋다. (

 

Redis에서는 Collection을 제공하는데 Collection이 왜 중요한지? 개발의 편의성, 개발의 난이도 

  • 라이브러리를 많이 제공하면 개발하기 편함
  • 랭킹서버 등을 만들 때, Redis의 Sorted Set 을 이용하면 작업하기 편함

 

Redis Collections

개발할 때, 자료구조를 잘 선택하는 것이 중요하다.

  • Strings : 키 - 밸류로 저장
    • Set <key> <value>
    • Get <key>
  • List : head, tail 사용 가능하지만 중간 데이터 찾기 오래걸림
    • Lpush <key> <A>
    • Rpush <key> <B, C>
    • LPOP <key>
    • RPOP <key>
  • Set : 중복 데이터 저장하지 않음. 데이터가 있는지 없는지만 체크하는 용도
    • SADD <key> <Value>
    • SMEMBERS <key> : 모든 VALUE 돌려줌
    • SISMEMBERS <key> : 존재하면 1, 존재하지 않으면 0
  • Sorted Sets : Score 로 정렬 가능
    • ZADD <key> <Score> <value> : value 가 이미 존재하면 해당 Score 로 변경됨
    • ZRANGE <key> <StartIndex> <EndIndex>, ZREVRANGE <key> <StartIndex> <EndIndex> : 해당 Index 범위값 모두 돌려줌. 유저 랭킹보드로 사용 가능. score 가 double 타입이기 때문에 값이 정확하지 않을 수 있는데, 실수가 표현할 수 없는 정수값들이 존재하기 때문. 
  • Hash : 키 - 밸류 구조 안에 서브 키 - 서브 밸류가 인서트 가능

Collection 주의 사항

  • 하나의 컬렉션에 너무 많은 아이템을 담지 말고, 10,000개 이하 수준으로 유지하는게 좋음.
    • 매번 몇만개씩 가져오면 성능 이슈 발생
  • Expire는 Collection의 item 개별로 걸리지 않고 전체 Collection에 대해서만 걸림
    • 즉 10,000개의 아이템을 가진 Collection에 대해 expire가 걸려있다면 그 시간 이후에 모두 같이 삭제
    • 일부만 걸리도록 사용하는 사람들도 있긴한데 이렇게 하면 메모리를 너무 많이 사용한다.

 

 

Redis 운영

  • 메모리 관리를 잘하자. 진짜진짜 중요
    • Redis는 In-Memory Data Store
    • Physical Memory 이상을 사용하면 문제가 발생
      • Swap 이 있다면, Swap 사용으로 해당 메모리 Page 접근시마다 늦어짐. -> 디스크 사용
    • Maxmemory를 설정하더라도 이보다 더 사용할 가능성이 큼.
      • 특정 메모리 이상으로 사용하지 말라고 선언하는 옵션
      • 레디스는 메모리 풀을 사용하는게 아니고, 메모리 할당/해제를 jemalloc 사용. jemalloc보다 더 잘 짜서 사용할 수 있는 사람은 없겠지만, 레디스가 정확하게 어느정도 메모리를 사용하고 있는지 알 수 없음. 해제했다고 하지만 실제로는 붙잡고 있는 경우도있음. : 메모리 파편화
    • RSS 값을 모니터링 해야함
      • 실제 used memory는 2GB로 보고가 되지만 1GB의 RSS를 사용하는 경우가 자주 발생
    • 많은 업체가 현재 메모리를 사용해서 Swap을 쓰고 있다는 것을 모를때가 많음. -> 그래서 어느순간 레디스가 느려졌다고 생각함
    • 큰 메모리를 사용하는 instance 하나보다는 적은 메모리를 사용하는 instance 여러개가 안전함.
      • CPU 4 Core, 32GB Memory
        • 24GB 하나보다는, 8GB 3개가 좋다
        • 비슷한 사이즈의 메모리를 사용하는게 좋다.
      • 레디스는 필연적으로 마스터-슬레이브 포크를 하게 되어있음
        • 이 때, Read가 많은 경우는 괜찮은데, Write 가 헤비하게 일어나면 메모리를 최대 2배까지도 사용한다.
        • 처음에 포크했을 때는 메모리를 복사하지 않는데, write하면 메모리 복사가 일어남
    • 메모리가 부족할 때는?
      • 돈이 많으면 좀 더 메모리 많은 장비로 Migration
      • 돈이 없으면 데이터 줄이기
      • 기본적으로 Collection 들은 다음과 같은 자료구조 사용
        • Hash -> Hash Table 을 하나 더 사용
        • Sorted Set -> Skiplist와 HashTable 을 이용
        • Set -> HashTable 사용
        • 위의 자료구조들은 메모리를 많이 사용함 (메모리 파편화, 포인터 등으로 메모리 많이 사용됨)
      • Ziplist를 사용하자.
        • In-Memory 이다보니, 적당한 사이즈 까지는 풀 서치를 하더라도 빠르게 사용할 수 잇다.
  • O(N) 관련 명령어는 주의하자.
    • Redis 는 Single Threaded
      • 단순한 get/set의 경우, 초당 10만 TPS 이상가능(CPU속도에 영향받음)
      • 하나라도 긴 시간을 요하는 명령이 걸리면 문제가 생김
    • 대표적인 O(N)명령 : KEYS(모든 키 순회), FLUSHALL, FLUSHDB, Delete Collections, Get All Collections
      Flush의 경우 명령어 호출했을 때는 멈추지만 필요할 땐 써야함
      • Keys 대신해서 scan명령을 여러번 나눠서 처리할 수 있다.
        scan <키값>
    • Collection의 모든 item을 가져와야할 때?
      • Collection의 일부만 가져오기 : Sorted Set
      • 큰 Collection을 작은 여러개의 Collection으로 나눠서 저장. 하나 당 몇천개 안 쪽으로 저장.
  • Replication
    • A 라는 서버에 있는 데이터를 B 서버에서도 모두 동일하게 가지고있음. (Master - Slave, Original - Replicaof)
    • Async Replication
      • Replication Lag 이 발생할 수 있다.
        • A서버 데이터를 B서버에서 복제하는 그 사이에 이슈가 발생할 수 있다.
        • 시간차가 길어질 경우 연결을 끊어버림. 그리고 다시 마스터-슬레이브 연결을 시도하는데 이때 부하가 늘어남.
    • DBMS로 보면 statement replication가 유사하다.
    • Replication 설정 과정
      • Secondary에 replicaof or slaveof 명령 전달
      • Secondary는 Primary에 sync 명령 전달
      • Primary 는 현재 메모리 상태를 저장하기 위해 - Fork
      • Fork 한 프로세서는 현재 메모리 정보를 disk에 dump
      • 해당 정보를 secondary에 전달
      • Fork 이후의 데이터를 secondary에 계속 전달
    • Replication 주의할점
      • Replication 과정에서 fork 가 발생하므로 메모리 부족이 발생할 수 있다.
      • Redis-cli --rdb 명령은 현재 상태의 메모리 스냅샷을 가져오므로 같은 문제를 발생시킴
      • AWS나 클라우드의 Redis는 좀 다르게 구현되어서 좀 더 안정적
      • 많은 대수의 Redis 서버가 Replica를 두고있다면
        • 네트워크 이슈나 사람의 작업으로 동시에 replication이 재시도 되도록 하면 문제가 발생될 수 있다.
  • redis.conf 권장 설정 Tip
    • Maxclient 설정 50000
    • RDB/AOF 설정 off : 속도 빠름. 안정성 높음.
    • 특정 commands disable
      • Keys
      • AWS의 ElasticCache는 이미 하고있음
    • 전체 장애의 99%가 Keys와 slave 설정을 사용해서 발생
    • 적절한 ziplist 설정

 

레디스 데이터 분산

  • 데이터의 특성에 따라서 선택할 수 있는 방법이 달라짐
    • Cache 일때는 우아한 Redis
    • Persistent 해야하면 안 우아한 Redis

데이터 분산 방법

  • Application 레벨에서 분산
    • Consistent Hashing : twemproxy를 사용하는 방법으로 쉽게 사용 가능
      • 서버가 추가되거나 사라지면 이슈 발생
    • Sharding
      • 데이터를 어떻게 나눌것인가?
      • 데이터를 어떻게 찾을것인가?
      • 하나의 데이터를 모든 서버에서 찾아야하면?
      • Range : 특정 Range를 정의하고 해당 Range 에 속하면 거기에 저장.
        • 서버 별로 일하는 양이 불균형해진다. 불균형하더라도 서버의 상황에 따라서 놀고있는 서버와 일하고 있는 서버를 바꿀수도 없다.
        • modular 로 처리할 때는 서버 대수를 2배로 늘려야 데이터 변환이 적고, 균등하게 분배할 가능성이 높다.
      • Indexed 방법으로 진행할수도 있는데, Key 가 어디에 저장되어야할지 관리하는 서버가 필요.
        • 대신, 키 관리 서버가 죽으면 사용불가.
  • Rediscluster
    • Hash 기반으로 Slot 16384로 구분
      • Hash 알고리즘은 CRC16을 사용
      • Slot = crv16(key) % 16384
      • Key가 Key{hashkey} 패턴이면 실제 crc16에 hashkey가 사용된다.
      • 특정 Redis 서버는 이 slot range 를 가지고 있고, 데이터 migration은 이 slot 단위의 데이터를 다른 서버로 전달하게된다. (migrateCommand 이용)
    • 레디스 클러스터의 HA 는 기본적으로 일반적인 Redis 구조와 동일
      Primary 가 죽으면 Secondary가 Primary 로 승격 (replication 구조로 이루어져있음)

      •  각 슬롯으로 데이터를 넣어주는데 범위에 잘못된 데이터를 넣어주면 에러 리턴 및 몇번 슬롯으로 보내줘야하는지 알려줌
      • 그리고 클라이언트가 맞는 슬롯에 맞게 다시 데이터 전달해줘야함
    • 장점
      • 자체적인 Primary, Secondary Failover.
      • Slot 단위의 데이터 관리
    • 단점
      • 메모리 사용량이 더 많음
      • Migration 자체는 관리자가 시점을 결정해야함
      • Library 구현이 필요함
  • Redis Failover
    • Coordinator 기반 Failover
      • Zookeeper, etcd, consul 등의 Coordinator 사용
        위와 같은 구조에서, Redis 1번이 죽으면, Health Checker는 Redis #2를 Primary 로 승격시킨다.
        Health Checker는 Coordinator 에 current Redis가 Redis #2 라고 업데이트한다.
        Coordinator는 API 서버에 current Redis 가 변경되었다고 알려준다.
        코드로 모두 작성해두어야함
      • 장점은, Coordinator 기반으로 설정을 관리한다면 동일한 방식으로 관리 가능
    • VIP / DNS 기반 Failover
      • 클라이언트에 추가적인 구현이 필요없다.
      • VIP 기반은 외부로 서비스를 제공해야하는 서비스 업자에 유리 (클라우드 업체 등)
      • DNS 기반은 DNS Cache TTL을 관리해야함. (아마존에서 DNS 기반으로 처리중)
        • 사용하는 언어별 DNS 캐싱 정책을 잘 알아야함
        • 툴에 따라서 한번 가져온 DNS 정보를 다시 호출하지 않는 경우도 존재
    • Redis Cluster의 사용

 

Monitoring Factor

  • Redis Info를 통한 정보
    • RSS - 가장 먼저 확인해야함. 실제 피지컬 메모리 얼마나 사용하고 있는지
    • Used Memory - 레디스가 생각하는 메모리 사용량
    • Connection 수 -레디스가 커넥션을 엄청 연결했다가 끊고 한다면 문제가 생겼음을 알수있다.
    • 초당 처리 요청 수
  • System
    • CPU
    • Disk
    • Network rx/tx

 

CPU 가 100% 를 칠 경우

  • 처리량이 매우 많다면?
    • 좀 더 CPU 성능이 좋은 서버로 이전
    • 실제 CPU 성능에 영향을 받음
  • O(N) 계열의 특정 명령이 많은 경우
    • Monitor 명령을 통해 특정 패턴을파악하는것이 필요
    • Monitor 잘못쓰면 부하로 해당 서버에 더 큰 문제를 일으킬수도있음. (짧게 쓰고 분석)

 

결론

  • 기본적으로 Redis는 매우 좋은 툴
  • 그러나 메모리를 빡빡하게 쓸 경우, 관리하기 어려움
    • 32기가 장비라면 24기가 이상 사용하면 장비 증설 고려
    • Write 가 Heavy 할 때는 migration도 매우 주의
  • Client-output-buffer-limit 설정이 필요.
  • Cache일 경우는 문제 적게 발생
    • Redis가 문제가 있을 때는 DB 등의 부하가 어느정도 증가하는지 확인 필요
    • Consistent Hashing도 실제 부하를 아주 균등하게 나누지는 않음. Adaptive Consistent Hashing을 이용해볼 수도 있음.
  • Persistent Store의 경우
    • 무조건 Primary/Secondary 구조로 구성이 필요
    • 메모리를 절대 빡빡하게 사용하면 안됨.
      • 정기적은 migration이 필요. (Secondary에서 백업)
      • 가능하면 자동화 툴 만들어서 이용
    • RDB/AOF 가 필요하다면 Secondary에서만 구동. (AOF가 I/O가 균등하기 때문에 좀 더 안정적)
    • 돈을 계속해서 들여서 관리하는거말고 방법이 없음.