Replication

  • replicaof 커맨드로 간단하게 복제 연결
  • 비동기식 복제
  • Master가 다운되더라도 Replica가 서비스를 계속 할 수 있음
  • HA 기능이 없으므로 장애 상황 시 수동 복구
    • replicaof no one
    • 서버 다운시 AOF 또는 RDB로 재시작
  • replication 과정에서 fork가 발생하므로 메모리 부족 발생 가능
  • 특정 커맨드 disable 하기

 

Sentinel

센티널은 일반 노드들을 실시간으로 모니터링(monitoring)하는 역할

자동 페일오버 가능한 HA 구성

  • 마스터가 비정상이면 자동 페일오버(Automatic Failover)
  • 연결 정보 변경 필요 없음
  • 센티널 노드는 항상 3대 이상, 홀수로 존재해야 함
    • 과반수 이상의 센티널이 동의해야 페일오버 진행
    • 예) 네트워크의 잠깐의 오버타임 때문에 마스터 노드가 죽었다고 생각하는 하나의 센티널이 있다고 가정할 때, 과반수 이상의 센티널이 "OK"해야 비로소 그 마스터 노드는 죽은 것이고, 그때 Replica에서 마스터 노드를 선출한다.
  • 알림 : 감시하고 있는 인스턴스가 페일오버 되었을 때 Pub/Sub으로 애플리케이션(클라이언트)에게 알리거나 shell script로 관리자에게 이메일이나 SMS으로 알림 가능

 

Cluster

스케일 아웃과 HA 구성

 

  • 키를 여러 노드에 자동으로 분할해서 저장 (샤딩)
  • 모든 노드가 서로를 감시하며 마스터 비정상 상태일 때 자동 페일오버
  • 최소 3대의 마스터 노드 필요

 

아키텍처 선택 기준

데이터 유형과 해당 데이터에 대한 액세스 패턴을 잘 고려해서 선택해야 한다.

읽기 전략

Look-Aside(Lazy Loading)

데이터를 읽는 작업이 많을 때 사용 (가장 일반적으로 사용하는 방법)

Cache Hit

  1. 데이터를 요청한다.
  2. 먼저 cache 서버(redis)를 확인한다.
  3. cache에 데이터가 있으면 DB를 조회하지 않고 cache에 있는 데이터를 바로 반환한다. (Cache Hit)

Cache Miss

 

  1. 데이터를 요청한다.
  2. 먼저 cache 서버(redis)를 확인한다.
  3. cache 서버에 데이터가 없으면 DB를 조회하여 cache 서버에 저장하고 데이터를 반환한다. (Cache Miss)

 

Lazy Loading

cache는 찾는 데이터가 없을 때에만 입력되기 때문에 lazy loading이라고도 부름

이 구조는 redis가 다운되더라도 바로 장애로 이어지지 않고 DB에서 데이터를 가지고 올 수 있음

 

Cache Warming

대신 cache에 붙어있던 커넥션이 많이 있었다면 그 커넥션이 다 DB에 붙게 돼서 DB에 갑자기 많은 부하가 몰릴 수 있음

이런 경우 cache miss가 많이 발생해서 성능에 저하가 올 수 있음

→ 미리 DB에서 cache로 데이터를 밀어넣어 주는 작업

Cache Warming🔥

 

쓰기 전략

Write-Around

일단 모든 데이터는 DB에 저장되고 cache miss가 발생했을 때만 cache(redis)에서 데이터를 끌어오는 방법

이 경우 cache 데이터와 DB 데이터가 다를 수 있다는 단점이 있다.

 

Write-Through

DB에 데이터를 저장할 때 cache에도 함께 저장하는 방법

cache는 항상 최신 정보를 가지고 있다는 장점이 있지만 

저장할 때마다 두 단계 스텝을 거쳐야 하기 때문에 상대적으로 느리다.

 

무조건 cache에 데이터를 저장하기 때문에 일종의 리소스 낭비를 가지고 올 수 있어

expire time을 설정하는 것이 좋다.

 

Redis는 다양한 데이터 타입과 데이터 타입별 커맨드를 제공합니다.

Key 설정 포인트

  • Key는 문자열이므로 최대 512MB까지 가능
    • 길이 등 가독성을 고려해야하고, 스키마를 유지할 수 있도록 설계할 것
    • ex) user:1000:followers

 

Strings

가장 기본적인 Redis 데이터 유형

텍스트, 직렬화된 개체 및 이진 배열을 포함한 바이트 시퀀스를 저장, 이미지 저장 가능

캐싱에 자주 사용되지만 카운터를 구현하고, 비트 연산을 수행할 수 있는 기능도 제공

단일 문자열은 최대 512MB

 

  • 문자열 저장하고 검색
> SET user:1 minju
OK
> GET user:1
"minju"
  • 직렬화된 JSON 문자열 저장하고 지금부터 100초 후에 만료되도록 설정
> SET ticket:27 "\"{'username': 'minju', 'ticket_id': 321}\"" EX 100
  • 카운터 증가
> INCR views:page:2
(integer) 1
> INCRBY views:page:2 10
(integer) 11

Bitmaps

String의 확장, bit 단위 연산

데이터 저장공간 절약

정수로 된 데이터만 카운팅 가능

데이터 on/off 가능 - SETBIT

  • 하루 총 방문자 수 세기

 

Lists

문자열의 목록

Linked List와 같은 형태, 스택과 큐 형태 구현

백그라운드 시스템을 위한 대기열 관리 구축

Blocking 기능을 이용해 Event Queue로 사용

  • 트위터 피드 기능
    • 키가 있을 때만 데이터 저장 가능 - LPUSHX / RPUSHX : 이미 캐싱되어 있는 피드에만 신규 트윗을 저장해서 자주 사용하는 유저에게만 데이터를 넣어줄 수 있음

 

  •  
  • 리스트를 대기열처럼 취급(선입선출)
> LPUSH work:queue:ids 101
(integer) 1
> LPUSH work:queue:ids 237
(integer) 2
> RPOP work:queue:ids
"101"
> RPOP work:queue:ids
"237"
  • 리스트를 스택처럼 취급(선입, 후출)
> LPUSH work:queue:ids 101
(integer) 1
> LPUSH work:queue:ids 237
(integer) 2
> LPOP work:queue:ids
"237"
> LPOP work:queue:ids
"101"
  • 리스트 길이 조회
> LLEN work:queue:ids
(integer) 0
  • 한 리스트에서 요소를 atomic하게 pop하고 다른 목록으로 push
> LPUSH board:todo:ids 101
(integer) 1
> LPUSH board:todo:ids 273
(integer) 2
> LMOVE board:todo:ids board:in-progress:ids LEFT LEFT
"273"
> LRANGE board:todo:ids 0 -1
1) "101"
> LRANGE board:in-progress:ids 0 -1
1) "273"
  • 100개를 초과하지 않는 리스트를 만들려면 다음을 호출할 때마다(LTRIM) 호출(LPUSH)
> LPUSH notifications:user:1 "You've got mail!"
(integer) 1
> LTRIM notifications:user:1 0 99
OK
> LPUSH notifications:user:1 "Your package will be delivered at 12:01 today."
(integer) 2
> LTRIM notifications:user:1 0 99
OK

Hashes

하나의 key 안에 여러 개의 field-value 쌍의 컬렉션

field = subkey, field 개수는 제한 없음

기본 객체를 나타내고 카운터를 그룹화하여 저장

 

  • 기본 사용자 프로필을 해시로 나타냄
> HSET username:123 username minju firstName Minju lastName Jeon country KR
(integer) 4
> HGET user:123 username
"minju"
> HGETALL user:123
1) "username"
2) "minju"
3) "firstName"
4) "Minju"
5) "lastName"
6) "Jeon"
7) "country"
8) "KR"
  • 장치 777이 서버에 핑을 보내거나, 요청을 발행하거나, 오류 보낸 횟수에 대한 카운터 저장
> HINCRBY device:777:stats pings 1
(integer) 1
> HINCRBY device:777:stats pings 1
(integer) 2
> HINCRBY device:777:stats pings 1
(integer) 3
> HINCRBY device:777:stats errors 1
(integer) 1
> HINCRBY device:777:stats requests 1
(integer) 1
> HGET device:777:stats pings
"3"
> HMGET device:777:stats requests errors
1) "1"
2) "1"

Sets

고유한 문자열, 중복 X, 정렬X → 집합

고유한 항목 추적(예: 지정된 블로그 게시물에 액세스하는 모든 고유한 IP 주소 추적)

관계 나타내기(예: 주어진 역할을 가진 모든 사용자 집합)

교집합, 합집합 등 일반적인 집합 연산 수행

 

  • 사용자 123 및 456의 즐겨찾는 책 ID set 저장
> SADD user:123:favorites 347
(integer) 1
> SADD user:123:favorites 561
(integer) 1
> SADD user:123:favorites 742
(integer) 1
> SADD user:456:favorites 561
(integer) 1
  • 사용자 123이 책 742와 299를 좋아하는지 조회
> SISMEMBER user:123:favorites 742
(integer) 1
> SISMEMBER user:123:favorites 299
(integer) 0
  • 사용자 123과 456이 공통적으로 좋아하는 책 조회
> SINTER user:123:favorites user:456:favorites
1) "561"
  • 사용자 123이 좋아하는 책이 몇 권인지 조회
> SCARD user:123:favorites
(integer) 3

Sorted Sets

정렬된 고유한 문자열 set

key, value에서 score까지 함께 저장 → 둘 이상의 score 값이 동일할 경우 사전순 정렬

  • 순위표. 예를 들어 대규모 온라인 게임에서 가장 높은 점수의 정렬된 목록을 쉽게 유지 관리
  • 속도 제한기. 과도한 API 요청을 방지하기 위해 슬라이딩 윈도우 속도 제한기 구축

  • 플레이어의 점수가 변경되면 실시간 순위표 업데이트
> ZADD leaderboard:455 100 user:1
(integer) 1
> ZADD leaderboard:455 75 user:2
(integer) 1
> ZADD leaderboard:455 101 user:3
(integer) 1
> ZADD leaderboard:455 15 user:4
(integer) 1
> ZADD leaderboard:455 275 user:2
(integer) 0
  • 상위 3명의 플레이어 점수 조회
> ZRANGE leaderboard:455 0 2 REV WITHSCORES
1) "user:2"
2) "275"
3) "user:3"
4) "101"
5) "user:1"
6) "100"
  • 사용자 2의 등급 조회
> ZREVRANK leaderboard:455 user:2
(integer) 0

HyperLogLogs(HLL)

2007년 Philippe Flajolet이 발표한 알고리즘

시간복잡도 O(1)로 효율적인 공간 활용을 위해 unique count를 통한 완벽한 정확도 제공, set의 카디널리티를 구하는데 주로 사용됨

확률적 자료구조를 이용한 추정 원리 : https://d2.naver.com/helloworld/711301

최대 12KB를 사용하고 0.81%의 오류 제공

count만 셀뿐이어서 value는 저장하지 않음 (내부 데이터 확인 불가능)

AWS 엘라스틱캐시, 클라우드 미터링(돈을 매기는 지표)

 

  • 몇 가지 항목 추가
> PFADD members 123
(integer) 1
> PFADD members 500
(integer) 1
> PFADD members 12
(integer) 1
  • set의 구성원 수 추정
> PFCOUNT members
(integer) 3

Streams

신규 기능

append-only 로그, 로그를 저장하기에 최적의 자료구조

메시지 브로커로 특화

실시간으로 이벤트를 기록하고 동시에 syndicate 한다. (일괄적으로 ..??)

kafka에서 영향을 받음

  • 이벤트 소싱(예: 사용자 작업, 클릭 추적 등)
  • 센서 모니터링(예: 현장 장치의 판독값)
  • 알림(예: 각 사용자의 알림 기록을 별도의 스트림에 저장)

각 스트림 항목에 대해 고유한 ID를 생성 → 나중에 연결된 항목을 검색하거나 후속 항목 처리

여러 트리밍 전략(스트림이 무제한으로 커지는 것을 방지)과 둘 이상의 소비 전략(XREAD, XREADGROUP, 과 XRANGE)을 지원

 

  • 스트림에 여러 온도 판독값 추가
> XADD temperatures:us-ny:10007 * temp_f 87.2 pressure 29.69 humidity 46
"1658354918398-0"
> XADD temperatures:us-ny:10007 * temp_f 83.1 pressure 29.21 humidity 46.5
"1658354934941-0"
> XADD temperatures:us-ny:10007 * temp_f 81.9 pressure 28.37 humidity 43.7
"1658354957524-0"
  • ID에서 시작하는 처음 두 개의 스트림 항목 조회
> XRANGE temperatures:us-ny:10007 1658354934941-0 + COUNT 2
1) 1) "1658354934941-0"
   2) 1) "temp_f"
      2) "83.1"
      3) "pressure"
      4) "29.21"
      5) "humidity"
      6) "46.5"
2) 1) "1658354957524-0"
   2) 1) "temp_f"
      2) "81.9"
      3) "pressure"
      4) "28.37"
      5) "humidity"
      6) "43.7"
  • 스트림의 끝에서 시작해서 최대 100개의 새 스트림을 읽고 항목이 기록되지 않는 경우 최대 300ms 동안 차단
> XREAD COUNT 100 BLOCK 300 STREAMS temperatures:us-ny:10007 $
(nil)

 

데이터 타입별 커맨드

https://redis.io/commands/

 

Commands

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker

redis.io

 

redis란?

공식문서에서는 다음과 같이 정의되어 있습니다.

Redis는 데이터베이스, 캐시, 메시지 브로커 및 스트리밍 엔진으로 사용되는 오픈 소스(BSD 라이센스), 메모리 내 데이터 구조 저장소입니다. Redis는 strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams과 같은 데이터 구조를 제공합니다. Redis는 기본 제공 복제, Lua 스크립팅, LRU 제거, 트랜잭션 및 다양한 수준의 온디스크 지속성을 갖추고 있으며 Redis Sentinel을 통한 고가용성 및 Redis 클러스터를 통한 자동 파티셔닝을 제공합니다.

 

redis는 remote dictionary server의 줄임말입니다.

사전 같은 구조를 통해 데이터를 저장, 관리할 수 있다는 의미입니다. 즉, key-value 구조를 이용한다는 것입니다.

 

redis는 db-engines.com에서 key, value 저장소 중 가장 높은 순위입니다.

2023년 1월 18일 기준

 

특징

  • key-value 구조의 NoSQL
    • 쿼리를 사용할 필요가 없음
  • In-Memory 데이터베이스
    • 매우 빠른 속도
    • 서버 재시작 시 모든 데이터가 사라지기 때문에 유의 필요
    • 복제와 백업은 다른 것으로 복제로는 복원이 불가, 중요한 데이터는 주기적 백업 필요
  • 다양한 자료구조 지원
    • sorted sets 타입을 활용하면 데이터가 저장됨과 동시에 정렬되기 때문에 실시간 랭킹 시스템 개발에 유용함
    • application 단에서 작성할 로직을 redis로 간단하게 처리할 수 있음
    • 자료구조 별로 다른 커맨드 제공
  • Single Thread 방식 동작
    • 한 번에 하나의 명령만 처리할 수 있음
    • 처리 시간이 긴 명령어가 들어오면 뒤 명령어들은 앞에 있는 명령어가 처리될 때까지 대기
    • 하지만 get, set 명령어의 경우 초당 10만 개 이상 처리할 수 있을 만큼 빠름
    • atomic을 보장하고, race condition을 회피
  • 버전 6.x부터 아래와 같은 부분에만 Multi Thread 도입
    • 클라이언트에서 전송한 명령을 읽고 파싱하는 부분
    • 명령어 처리 결과를 클라이언트에게 전송하는 부분
    • (주의) 명령어 실행 자체는 위의 메인스레드에서 수행하므로 여전히 싱글스레드라고 봐야함

 

왜 쓸까?

DB가 있는데도 redis라는 인메모리 데이터 저장소를 사용하는 이유는 무엇일까요?

 

서비스의 유저가 증가했을 때 모든 유저의 요청을 DB 접근으로만 처리한다면 DB 서버에 부하가 증가할 수 밖에 없습니다. 또한 스키마가 정해져 있어 큰 저장공간을 차지하는 테이블에는 컬럼 하나만 추가하는 것도 쉽지 않습니다.

 

이를 개선하고자 DB를 스케일 인, 스케일 아웃 하는 방식 외에 캐시 서버로 이용하는 것이 redis 입니다.

 

캐시는 한번 읽은 데이터를 미리 저장해두었다가 다음에 읽을 때는 빠르게 반환할 수 있도록 동작하는 공간입니다.

같은 요청이 여러 번 들어오는 경우 매번 데이터베이스를 거치는 것이 아니라 캐시 서버에서 결과값을 바로 내려주기 때문에 DB의 부하를 줄일 수 있고, 서비스 속도도 느려지지 않는 장점이 있습니다.

 

 

 

+ Recent posts