nosql & redis

NOSQL

특징

데이터 간 관계가 중요하지 않음 (조인등의 연산이 없음)
고성능 머신에 데이터를 저장하는것이 아니라 분산해서 저장하고 분산해서 처리함.
분산해 저장하고, 분산해 복제해 데이터 유실이나 서비스 중지가 없음

CAP 이론을 따름
Partitioning(부분결함 용인) - 데이터가 유실 될 수 있다.
Consistency, Availability 두개 중 하나를 택할 수 있다.

RDB에서 10만쿼리를 처리할 때
memcached와 Redis등의 NoSQL제품에서 실행하면 30만 쿼리 이상 처리량을 내는것 조차 가능해진다.

클라이언트가 접속 /해제를 반복해도 NoSQL 데이터베이스 테이블(파일)은 오픈한 상태를 유지하도록 제어하고 있다. 이로 인해 테이블의 열기/닫기 비용이 발생하지 않아 경합을 크게 줄일 수 있으며 성능도 향상된다.

NoSQL 등장배경
대규모데이터 단순한 데이터를 처리하기 위해 나옴
기존 RDB의 단점을 보완하는 시스템

NOSQL단점
RDBMS에서 지원하는 기능을 갖고 있지 않기도 한다.

  1. 트랜잭션 미지원
    트랜잭션을 사용할 수 없기 때문에 실패한 작업을 없던 일로 하는것도 자동으로 할 수 없게된다.
    또한 쓰기 도중에 크래시가 발생하면 어중간한 데이터를 없던일로 하는것도 마지막으로 커밋한 데이터까지 무사함을 보장할 수 없다. 즉 없어도 별로 상관없는 데이터에만 사용할 수 있다.
  2. 스키마가 없다.
    NoSQL은 키에 해당하는 값을 가진것이 대부분이다. 값에는 임의의 바이트 열을 넣을 수 있는데 거기에 실제로 무엇이 들어있는지 직접 액세스 해보지 않으면 모른다. 각 열에 나눠져있는게 아니기에 RDBMS와 비교하면 이해하기 어렵다.
  3. 기본키 이외의 인덱스를 사용할 수 없다.
    검색조건이 id가 아닌 이메일이나 주소 일 경우 새로운 인덱스를 부여해야한다. nosql의 대부분은 키와 값의 두가지 항목밖에 갖지않는다는 특성상 기본 키이외에 인덱스를 취할 수 없다.


NoSQL의 용도
캐시
주로 NoSQL은 RDBMS의 캐시로서 사용된다. 이러한 용도로 정평이 나있는 제품이 memcached이다. NoSQL에서 캐시 히트한 경우는 고속의 NoSQL프로토콜로 액세스 할 수 있으므로 SQL문을 실행할 필요가 없어 처리가 빨라진다.
캐시로 사용하려면 RDBMS를 어떻게 갱신할지에 대한것이 과제이다.

세션데이터
캐시가 아닌 NoSQL만으로 데이터를 갖는다는 사례도 존재한다.
예를들어 최종 액세스 시각관리 등이 있다. SNS는 친구가 언제쯤 온라인 상태가 되었는지 추적하기 위해 사용한다.
이부분은 다소 정확성이 떨어져도 큰 문제가 되지 않으며 검색키도 사용자 id 이외가 될 수 없기때문에 다른 인덱스도 필요 없다.이 경우 무정지성을 다소 희생하고 성능쪽을 추구해도 좋다고 말할 수 있다.

특징
RDB
데이터의 일관성을 보증할 수 있다(트랜잭션)
설계시 중복이 삭제됨, 업데이트시 비용이 적다(정규화)
복잡한 형태의 쿼리도 가능(Join 등)
대량의 데이터를 다룰 때 한계가 있따

NoSQL
특정용도로 특화되어있음
분산이 용이함
Join 미제공
데이터를 여러 서버에 분산시키는게 좋음
데이터에 대한 캐시가 필요한경우
배열 형식의 데이터를 고속으로 처리할 필요가 있는경우
단점
각 솔루션에 특징을 이해해야함

NoSQL의 종류
https://www.youtube.com/watch?v=Dvi_TsBMFJo 12분

keyValue
key/value 형태로 데이터를 저장하며 unique key 하나의 value를 가지고 있는 형태 put, get만 갖고있음
대표제품: 레디스

컬럼형 - ordered key, value store(컬럼형)
데이터가 내부적으로 key를 순서로 sorting되어 저장됨
key 안에 (column:value)조합으로 된 여러개의 필드를 가지는 구조
대표제품 : Hbase, Cassandra
열의 추가는 비용이 저렴하고 행 단위로 처리된다. 각 행은 서로 다른 열을 가지거나 아예 없을 수 있따.

Document key/value store
저장되는 value의 데이터 타입으로 Document라 구조화된 데이터 타입을 사용(JSON,XML,YAML) 등
복잡한 계층구조 표현 가능
제품에 따라 추가기능(Soriting, Join, Grouping) 지원
대표제품 : MongoDB, CouchDB

GraphDB
페묵에서 만든 Tao: 각각의 entity를 저장하고 이를 관계로 연결함
neo4j또한 있음
노드사이의 관계를 알아야 할때 씀. 페북같은곳에서 씀

Document DB
몽고디비가 여기에 속함
json Document형태로 저장한다. 어떤 모양의 데이터든 저장할 수 있다

Key Value DB
CassandraDB, DynamoDB가 여기에 속함
카산드라: 읽고 쓰기가 엄청 빠름
DynamoDB : 서버리스 디비 읽고 쓰기가 엄청 빠름
Document db에 비해 데이터 종류가 제한적임.

GraphDB
페묵에서 만든 Tao: 각각의 entity를 저장하고 이를 관계로 연결함
neo4j또한 있음
노드사이의 관계를 알아야 할때 씀. 페북같은곳에서 씀

MySQL Cluster(NDB)
어떤때는 RDBMS로 사용하고 어떤 때는 NoSQL로 사용되는 제품이 있다. 대표적인 예가 NDB이다.

WriteScaling
다중마스터 구성을 통해 지역별 최적화를 진행하는것.

다중마스터 구성
도쿄 사용자에 대해서는 도쿄의 마스터를 업데이트하고 미국 서해한의 사용자는 미국 서해안의 마스터를 업데이트하면 업데이트에 걸리는 지연시간을 짧게 줄일 수 있따. 이렇듯 지역에 따라 최적화된 다중 마스터 구성도 앞으로 주목을 받게 될 것이다.
다중 마스터 구성의 과제는 갱신 충돌을 어떻게 감지할 것인가다. 동일 기본키에서 동시에 다른 마스터에서 업데이트 되는 현상이 발생했을 때 어느쪽을 통일하면 좋을까? 이러한 충돌을 감지하여 통일하는 구조가 필요하다

자동 shard 편성
한대의 서버에서 어플리케이션의 모든 갱신처리를 해야 하면 트래픽 증가로 조만간 파탄에 이르게 된다. 따라서 데이터마다 복수의 서버로 분산하여 한 대당 데이터 양을 줄이고 갱신 처리도 분산시키는 수평분할(sharding)이라고 하는 접근 방법이 웹 서비스에서는 잘 사용된다.
Sharding 구성을 취하기 위해서는 기본적으로 어떤 id가 어느 shard로 가는가 라는 매핑정보를 관리할 필요가 있다. 이를 위한 로직을 넣으면 스케일을 확장하기 매우 쉬운 서비스를 구축할 수 있다.
원래 2계통의 sharding을 구성한 상황에서 3계통으로 나누고 싶은경우는 데이터를 이동하는것이 힘들어진다. 이러한 재배치를 완전히 자동화 하는 데이터베이스도 나오고 있는데 Mysql cluster, Nosql세계에서는 Mongodb, Hbase등이 이러한 shard의 추가 및 재배치에 대응하고 있다.

레디스

KV형태임. 데이터를 디스크로 쓰기(committing)전 메모리에 캐싱함으로써 놀랄 만한 성능을 얻는다. 대신에 하드웨어 장애 시 데이터가 유실될 위험성은 커진다.

Redis는 블로킹 큐(blocking queue) 또는 스택(stack)이 될 수 있으면서 발행- 구독(publish-subscribe) 시스템이 될 수도 있다. 그리고 우리가 구성 가능한 만기 정책, 지속성 수준, 복제 옵션 등의 기능을 갖고 있다.

Redis는 모든 값을 문자열로 저장한다. 그러나 문자열로 된 숫자를 정수(integer)로 인식하고 처리해주는 연산들을 제공한다. 만일 우리 데이터셋에 있는 단축 키들의 전체 개수를 유지하고 싶다면, 다음과 같이 count를 생성한 후 INCR 명령을 사용해서 그 값에 1을 더할 수 있다.
뭔소린가 했는데 결국 아래와 같다

1
2
3
4
$ SET count 2
$ INCR count
$ get count //3이나옴

INCR 외에도 DECR(1을 뺌), DECRBY(지정한 정수를 뺌)가 있다.

SET과 INCR 같은 두 개의 연산(명령)을 하나의 블록으로 묶으면 모두 다 성공 아니면 모두 실패로 처리된다(하나의 트랜잭션으로)

MULTI를 사용할 때, 우리가 정의하는 명령들은 PostgreSQL처럼 실제로 실행되지 않고 큐에 저장만 되었다가 EXEC 명령을 실행하면 그 때 차례대로 실행된다.SQL의 ROLLBACK과 유사하게, 트랜잭션 큐의 명령들을 지우는 DISCARD 명령을 사용하면 트랜잭션을 중지시킬 수 있다. 그러나 ROLLBACK과는 달리 DISCARD는 데이터베이스를 이전 상태로 복구하지 않고 트랜잭션만 실행을 취소시킨다. 메커니즘은 다르지만 효과는
동일하다.

컬렉션 데이터 타입들(리스트(list), 해시(hash), 셋(set), 정렬 셋(sorted set))은 키 하나당 엄청난 개수의 값들
을 가질 수 있다(22개의 요소들 또는 40억 개 이상),

redis 해시

해시(Hash)는 키-값 쌍을 몇 개라도 가질 수 있는 Redis 객체이다.

1
2
3
4
5
redis 127.0.0.1:6379> MSET user:eric:name "Eric Redmond" user:eric:password s3cret
OK
redis 127.0.0.1:6379> MGET user:eric:name user:eric:password
1) "Eric Redmond"
2) 's3cret"

위와 같이 논리적으로 키 필드를 구분(:)하는 대신, 키-값 쌍으로 된 데이터를 포함하는 해시를 생성할 수도 있다.

1
2
redis 127.0.0.1 : 6379> HMSET user:eric name "Eric Redmond" password s3cret
OK

해시의 모든 데이터 값을 읽을 때는 키 필드만 주면 된다.

1
2
3
redis 127.0.0.1:6379> HVALS user:eric
1) "Eric Redmond"
2) "s3cret"

또는 다음과 같이 모든 해시 키를 읽을 수 있다.

1
2
3
redis 127.0.0.1:6379> HKEYS user:eric
1) "name"
2) "password"

또는 해시 키(다음 예에서는 password)를 지정하여 하나의 값만 읽을 수도 있다.

1
2
redis 127.0.0.1:6379> HGET user:eric password
"s3cret"

문서형 데이터베이스인 MongoDB/CouchDB와 다르게 Redis의 해시는 중첩될 수 없다(리스트 같은 다른 복잡한 데이터 타입에서도 안 된다). 즉, 해시는 문자열 값들만 저장할 수 있다(예를 들어, 해시의 값으로 또 다른 해시 데이터를 저장할 수 없다는 의미이다)..

redis list

리스트(List)는 여러 개의 순서적인 값들을 포함하며, 큐와 스택 모두로 동작할 수 있다

insert 사용

1
2
redis 127.0.0.1:6379> RPUSH eric.wishlist 7wks gog prag
(integer) 3

리스트의 특정값 읽기 (처음부터 끝)

1
2
3
4
redis 127.0.0.1:6379> LRANGE eric.wishlist 0.1
1) "7wks"
2) "gog"
3) "prag"

LREP은 주어진 키로부터 일치되는 값들을 삭제한다. 이때 삭제할 일치 값들의 개수를 알려줄 필요가 있다. 다음과 같이 0으로 주면 일치되는 값들 모두를 삭제한다.
0보다 큰 수를 주면 그 개수만큼의 일치 값들만 삭제하되 오른쪽 부터 일치값을 찾는다.

1
redis 127.0.0.1: 6379> LREM eric:wishlist o gog

기타 앞에서부터 꺼내는 LPOP, 오른쪽부터 꺼내는 RPOP 등이 있으며 , 큐와 유사한 효과를 낼 때는 LPUSH와 RPOP(또는 RPUSH와 LPOP)을 사용하고, 스택의 경우는 LPUSH와 LPOP(또는 RPUSH와 RPOP)을 사용하면 된다.

한 리스트의 끝에서 값들을 꺼내어(pop) 다른 리스트의 앞쪽에 넣는다(push), 그 명령은 RPOPL PUSH(오른쪽에서 pop.
왼쪽에 push)이다.

1
2
redis 127.0.0.1:6379> RPOPLPUSH eric:wishlist eric:visited
"prag"

꺼낼 값이 존재할 때까지 블로킹하는 명령어가 BRPOP

정렬셋
정렬셋은 리스트처럼 정렬되며 셋처럼 고유한 값들을 갖는다. 데이터를 추가할 때 해시나 리스트는 일정한 시간이 걸리는 반면, 정렬 셋은 log(N)의 시간이 소요된다 (여기서 N은 셋의 크기이다).

발행구독

다수의 구독자들이 한 발행자의 메세지를 읽기 위해 Redis는 전문화된 발행- 구독 명령들을 제공한다.

서버정보 조회

Redis 시스템의 설정 변경에 들어가기에 앞서 INFO 명령을 간단히 살펴볼 필요가 있다. 이 명령에 나오는 값들도 설정 변경 시에 바뀌기 때문이다. INFO 명령은 서버의 시스템 데이터 내역을 출력한다.

레디스의 구성

/etc/redis에 위치함
Redis에는 지속성(Durability) 옵션들이 몇 개 있다. 첫 번째는 지속성이 전혀 없는 것으로서, 모든 값들을 그냥 메인 메모리에 유지하는 것이다. 만일 Redis를 기본적인 캐시 서버로 실행한다면 이것이 적절한 선택이다. 데이터의 지속성을 유지하려면 항상 지연시간이 증가되기 때문이다.

Redis가 다른 캐시(memcached 와 같이 빠른 액세스를 하는 캐시)와 차별되는 것 중 하나가 데이터 값들의 디스크 저장을 자체적으로 지원해주는 것이다. 기본적으로 키-값의 쌍으로 된 데이터들은 가끔씩만 저장된다. LASTSAVE 명령을 실행하면 Redis가 디스크에 성공적으로 썼던 가장 최근 시간을 유닉스의 타임스탬프 형식으로 알 수 있다. 또는 INFO 명령에서 출력하는 서버 정보 중에서 last_save_time 필드를 보면 된다. SAVE 명령을 실행하면 메모리에 있던 데이터 값을 디스크에 쓰게 되므로 그 시점까지의 지속성이 유지된다.

구성 파일의 스냅샷 설정을 변경하면 또 다른 지속성 방법을 설정할 수 있다.

스냅샷
save(디스크에 쓰기)할 시간 및 변경되어야 하는 키의 개수를 추가 또는 변경하면 스토리지에서 디스크로 쓰는 비율(시간 간격)을 변경할 수 있다. save 필드는 디폴트로 세 개가 있다. save 키워드가 제일 앞에 나오고, 그 다음에 시간(초)과 디스크에 쓰기 전에 변경되어야 하는 키들의 최소 개수가 따라 나온다

예를 들어, 만일 키가 하나라도 변경되는 경우 매 5분 (300초)마다 save를 촉발시키고자 한다면 다음과 같이 한다.
save 300 1

만일
10,000개의 키들이 변경되면 60초 이내에 save하게 되며, 10개의 키가 변경되면 300초 이내에 save한다. 그리고 어떤 키건 하나라도 변경되면 900초(15분) 이내에 save하라는 것이다.
save 900 1
save 300 10
save 60 10000

Append-Only 파일

기본적으로 Redis는 불확정 지속성(eventually durable)을 갖는다. 즉, 우리의 save 설정에 정의된 간격(변경된 키의 개수와 시간) 내에서 비동기적으로 값을 디스크로 쓰거나, 또는 클라이언트가 실행시킨 명령에 의해서 디스크에 쓴다. 이것은 보조 캐시나 세션 서버에는 용납될 수 있다. 그러나 재무 데이터와 같이 지속성이 필요한 데이터의 저장에는 불충분하다. 만일 Redis 서버에 장애가 생겨서 돈이 없어지기라도 한다면 사용자들이 수긍할 수 없기 때문이다.
Redis는 추가만 되는 append-only 파일(appendonly.aot)을 제공하는데, 이 파일은 모든 쓰기 명령들의 기록을 유지한다. 이것은 4장에서 알아본 Hbase의 WAL(선 로그, write ahead logging)과 같다. 만일 값이 디스크에 저장되기 전에 서버가 다운되면, 그 이후에 Redis서버가 시작될 때 다운되기 직전에 실행되던 명령들이 실행되어 상태(데이터)가 복구된다. 그러므로 redis.cont 구성 파일에서 appendonly는 반드시 yes로 설정되어야 한다.

그 다음에는 append-only 파일에 얼마나 자주 명령을 추가할 것인지를 결정해야 한다.
always로 설정하면 지속성은 더 좋다. 모든 명령이 수록되기 때문이다. 그러나 처리 속도가 느려지므로 사람들이 빠른 Redis를 사용하는 이유를 무색하게 만든다. 디폴트 설정은 everysec이다. 이것은 1초에 한 번씩만 명령을 수록한다. 절충안인 셈이다. 그러나 처리 속도는 충분히 빠르지만 최악의 경우 마지막 1초의 데이터는 유실될 수 있다. 마지막으로 no 옵션이 있다. 이것은 운영체제에게 처리를 맡기는 것으로서, 거의 선택하지 않으며, 이 옵션을 선택할 바에야 차라리 append-only 파일 옵션을 모두 지정하지 않는 게 더 좋을 것이다.

보안

만일 Redis에서 보안을 원한다면 좋은 방화벽과 SSH 보안을 사용하는 것이 더 나을 것이다. 흥미롭게도 Redis는 명령어 수준의 보안 기능을 제공하여 명령들을 숨기거나 실행을 금지할 수 있다. 예를 들어. 다음에서는 이 서버의 FLUSHALL 명령어(시스템의 모든 키 삭제)를 추측하기 어려운 값인 c283d93ac95287986023793b411e4ba2로 이름을 변경한다.
rename - command FLUSHALL c283d93ac9528f9860237936411e4ba2
그런 다음 이 서버에서 FLUSHALL을 실행하려 하면 에러가 발생한다. 우리가 이름을 변경한 엉뚱한 값이 FLUSHALL 대신 실행되려고 했기 때문이다.

또는 명령어를 빈 문자열로 설정하여 쓸 수 없게 할 수도 있다.
rename - command FLUSHALL “ “

마스터 슬레이브 복제

만일 하나의 서버를 어떤 다른 서버의 슬레이브로 설정하지 않는다면 그 서버가 마스터가 된다. 데이터는 모든 슬레이브 서버들에 복제된다.
실무에서는 일반적으로 가용성이나 백업 용도로 복제를 구현하므로 슬레이브 서버는 다른 머신에 둔다.

블룸필터

블룸 필터는 셋에서 어떤 항목이 존재하지 않음을 확인(검사)하는 확률적 데이터 구조이다. 그리고 어떤 값이 시스템에
존재하지 않는지 여부를 빨리 알아볼 필요가 있을 때 유용하다.
블룸 필터는 어떤 값을 비트 시퀀스(연속된 비트)로 변환하고 그것을 통합 유지되는 모든 비트 시퀀스와 비교함으로써 그 값이 존재하지 않음을 확인한다. 달리 말해, 새로운 값이 추가되면 그것을 현재의 블룸 필터 비트 시퀀스와 OR 연산한다. 그리고 그 값이 시스템에 이미 있는지 여부를 확인하고 싶을 때는 블룸 필터의 비트 시퀀스에 대해 AND 연산을 수행한다. 이 개념을 그림으로 나타내면 아래와 같다.

36p 그림 참고하기

출력의 시작 부분에서는 and나 the 같은 흔한 단어들을 많이 볼 수 있을 것이다. 그리고 끝 부분 가까이에서는 unindustria처럼 거의 사용하지 않는 단어들이 점점 더 많이 나오게 될 것이다.
이 방법의 장점은 중복 단어들을 검출할 수 있다는 것이며, 단점은 false positives(한 번도 나왔던 적이 없는 단어가 블룸 필터에는 나왔었다고 표시되는 것)가 나올 가능성이 있다는 것이다.

블룸 필터들은 처리 속도가 더 늦은 내부 시스템(데이터베이스, 제한된 자원, 네트워크 요청) 에대한 불필요한 트래픽을 감소시키는 데 뛰어나다. 만일 여러 IP 주소를 갖는 더 느린 데이터 베이스가 있고, 우리 사이트에 접속하는 모든 새로운 사용자들을 유지 관리하고 싶다면, 블룸 필터를 사용해서 해당 IP 주소가 우리 시스템에 존재하는지 여부를 우선 확인할 수 있다. 만일 블룸 필터가 false를 반환하면, 그 IP 주소가 아직 추가되지 않았음을 알게 되어 그에 적절한 응답을 할 수 있다. 만일 블룸 필터가 true를 반환하면, 그 IP 주소는 백엔드 데이터베이스에 존재하거나 또는 존재하지 않을 수 있는 것이므로 이차적인 확인이 필요할 것이다.

블룸 필터의 합당한 크기를 계산하는 게 중요한 이유가 그 때문이다. 크기가 잘 산정된 블룸 필터는 에러율을 줄일 수 있으며(없앨 수는 없다), 또는 false positive의 가능성을 줄일 수 있다.

366부터

redis Sentinel

https://coding-start.tistory.com/126
과반수 이상의 Sentinel이 “OK” 해야 비로소 그 마스터 노드는
죽은 것이고, 그때서야 Slave 노드에서 마스터 노드를 선출하게 되는 것입니다. 그렇기 때문에 Sentinel은
3개이상의 홀수 인스턴스를 띄운다 원칙을 지켜주셔야합니다.

운영환경에서 레디스는 일반적으로 마스터와 복제로 구성됩니다. 운영중 예기치 않게 마스터가 다운되었다면, 관리자가 이를 감지해서 복제를 마스터로 올리고 클라이언트들이 새로운 마스터에 접속할 수 있도록 해 주어야 합니다. 센티널은 마스터와 복제를 감시하고 있다가 마스터가 다운되면 이를 감지해서 관리자의 개입없이 자동으로 복제를 마스터로 올려줍니다.

센티널은 다음과 같은 기능을 합니다.

모니터링 Monitoring : 센티널은 레디스 마스터, 복제들을 제대로 동작하는지 지속적으로 감시합니다.
자동 장애조치 Automatic Failover : 센티널은 레디스 마스터가 예기치 않게 다운되었을 때 복제를 새로운 마스터로 승격시켜 줍니다. 그리고 복제가 여러 대 있을 경우 이 복제들이 새로운 마스터로 부터 데이터를 받을 수 있도록 재 구성하고, 다운된 마스터가 재 시작했을 때 복제로 전환되어 새로운 마스터를 바라볼 수 있도록 합니다.
알림 Notification : 센티널은 감시하고 있는 레디스 인스턴스들이 failover 되었을 때 Pub/Sub으로 Application(client)에게 알리거나 shell script로 관리자에게 이메일이나 SMS로 알릴 수 있습니다.

Open Graph

펱이스북에서 만든것으로 페이지를 표현하는 것
https://ogp.me/
og:xxxxx 로 구성된 태그임(og:title, og:image, og:description)

카톡에서 메세지로 링크 보냈을 때 아래 추가설명 뜨는게 얘를 띄워주는것임

Failover

Active한 시스템에 장애가 발생했을 때 StandBy 서버가 Active로 전환해서 서비스가 계속 운영되게 하는것. (HA)
자동화된 Failover를 위해 Coordinator, VIP, DNS를 이용하는 방법이 있다.

Coordinator: 기존서비스와 Coordinator의 연동필요
VIP: 장애가 발생하면 VIP가 가르키는 실제주소를 바꿈. client는 VIP를 계속 사용함.
DNS: 장애가 발생하면 DNS를 변경함. DNS는 TTL이 존재하므로 이를 짧게 잡아야하고, 특정 어플리케이션은 DNS를 영구 캐시할 수 있으므로 주의!

shading

Horizontal Partitioning을 의미함

방식은 Range, Modular, Indexed 가 있다.
Range 는 0~1000 은 1번서버, 1001부터 2000은 2번서버 할당

Modular은 데이터가 증가하여 서버를 늘려야할 경우 2배씩 늘린다.
서버추가에 따른 리밸런싱이 발생해도 데이터 이동을 최소화 하기 위해

Indexed는 각 데이터가 위치하는곳을 관리하는 서비스를 추가하는것.

Consistent Hashing

Modular 방식의 경우 서버의 추가 삭제시에 리밸런싱이 계속 일어난다.
Consistent Hashing은 리밸런싱을 적게 발생시킴
해시한 값보다 크고 가장 가까운 위치의 서버에 데이터를 위치시키는것.
원형구조에서 우측으로 간다고 보면 됨

이는 부하가 자칫 한쪽으로 쏠릴 수 있는데 부하에 안정적으로 하기위해 Virtual Node 개념을 적용하기도 함

해쉬를 적용할 땐 ip보다는 서버의 nickname를 활용한다.(상면이동, 사양변경 등 ip가 변경될 수 있다)

GUID

Global Unique ID
많은 기업은 Unique한 ID를 만들기 위해 별도의 GUID 서버를 쓰곤 한다.

비동기 큐

비동기 큐를 사용할 때 유저는 쓰기 요청 후 조회할 경우 아직 DB에 반영이 안되어 응답이 실패할 수 있는데
이런 경우를 최소화 하기위해 실제로 서비스에서는 캐시에 먼저 저장해서 결과를 볼 수 있게한다.(Write-Back이라고 하며 캐시에 쓰고 이후엔 진짜로 저장장소에 저장)

작업큐는 대규모 트래픽이 발생했을 땐 처리량을 조절하는 Back Pressure로 동작한다.

무정지배포

롤링업데이트 - 한대를 lb에서 내리고 배포하고, 그 다음 한대를 내리고 배포하고.. 순차적으로 하나씩 배포
블루그린 - 블루셋, 그린셋이 존재함. 현재 블루셋으로 서비스 중이라면 같은양의 그린을 준비해서 그린셋에 새로운 버전을 배포하고 블루셋을 바라보는 설정을 그린을 바라보도록 수정함. 이후 문제가 없다면 블루셋을 내림. 장애가 있다면 바라보는 것을 바꿈.

blue-green은 같은 수의 장비를 두셋트가 필요하다. cloud의 경우 기존장비를 내리면 되므로 유지비에 큰 차이가 없음. on-premis라면 낭비해야하는 서버가 생김

on-premis에서 blue-green을 도입하려면 사용하는 리소스를 절반으로 제한하고, 한 서버에 두개의 프로세스와 프록시를 실행한다.
즉 리소스를 풀로 사용하지 못하는 단점이 있음. 그래서 부하가 많은 상황에서 oom이 발생할 수 있음.

canary 배포 - 몇대만 배포해서 장애를 살펴보자! 100대 중 2~3대만 배포해서 확인해보자
그렇다면 여러번 접속했을 때 동작방식이 달라질 수 있는데. 이땐 유저에 태그를 걸어서 특정서버(배포된 서버)로 리다이렉트 되도록 하는 방식등을 활용할 수 있다.

Redis

모든 데이터의 키는 HashMap에 저장된다

list
lpush, lpop, rpop, lpop, lrange 명령어가 있다.
rpop, lpop은 데이터가 없으면 바로 리턴함. brpop는 블로킹되어 데이터가 들어오면 리턴함

set
sadd, sismemeber, srem, smembers 명령어가 있다

stored set
skiplist 자료구조를 사용함. log(N)의 검색 속도
zadd, zrange, zreverange, zrangebyscore 명령어가 있다.

hash
레디스 자체가 해쉬구조인데 그 안에 subkey -> subvalue를 갖는구조임
즉 hash -> subkey -> subvalue 형태
일반적인 key/value를 특정군의 데이터로 묶고싶을때 유용
hset,hget, hmset, hmget, hgetall, hvals 명령어가 있다.

레디스는 싱글쓰레드이기 때문에 명령들이 원자성을 보장한다.
Multi, Exec라고 여러개를 한번에 실행하는것이 있음. 많은것들을 한번에 실행하면 성능이 떨어질 수 있음

Redis pipeline
레디스를 사용하는 경우 클라이언트 라이브러리에서 데이터를 보내고 결과를 기다리고 보내고 결과를 기다리고 보내고 하기때문에 딜레이가 있다.
이를 개선하기 위해 파이프라인이란것을(라이브러리에서 제공) 활용하여 응답을 기다리지 않고 명령을 미리 보내는방식을 사용할 수 있다.
(원래 비동기로 처리하는 Async Redis Client라면 redis pipeline 기능을 따로 제공할 필요가 없다. ex. Java의 Lettuce Redis Client 같은 경우)

8:2의 법칙 - 파레토의 법칙
80%의 행동이 전체 20%의 결과를 나타냄
비유:: 대한민국 부자의 20%가 전체의 80%를 소유함
즉 80%의 활동을 20%의 유저가 하기 때문에 20%의 데이터를 캐시하면 서비스의 대부분의 데이터를 커버할 수 있다!

적은 데이터가 빈번하게 접근될 때 유용하다.

Look aside cache
캐시에 있으면 캐시를 쓰고 캐시에 없으면 디비에서 조회해 캐시를 채워넣는것!

Key를 어떻게 정의해야 할까?
Api endpoint만으로 유일성이 보장될까?

  • api:feeds
    특정 유저의 feed. 유저 ID가 들어갈 수 있다. User1234라면
  • api:feeds:1234
    그 외 옵션(기간)
  • api:feeds:1234:20200901:20200920

Value 를 어떻게 정의해야 할까?
응답결과를 그대로 저장하거나, 실제 필요한 부분만 저장

시간순으로 가져오고, 특정 시간대만 가져오고자 한다면?
Sorted set사용을 하기 위해 Zadd를 사용하고 가져올 땐 zrevrange를 사용한다.

사용예

  • JWT의 경우에는 해당 키가 유용한지 여부만 저장하고, 실제 유저 정보는 jwt 안에 저장하는 경우가 많음
  • access token의 경우 value는 유저정보/권한, 토큰의 유효기간등을 넣기도 함
  • view count를 누적할 때 사용하기도 함. (+ api 호출 제한)

Thundering Herd
번개가 칠 때 동물들이 일제히 뛰어가는것을 나타내는것
즉 특정이벤트로 인해 많은 프로세스가 동작하는데 이 동작들이 경쟁하는 상황. 즉 특정서버(ex db)에 부하를 극도로 증가시키는것

10000개의 요청이 캐시를 히트하지 못해 디비에 간다. 이후 캐시에 다시 저장하기 위해 디비에 10000개의 요청이 일제히 간다

원인: 캐시가 없을 때 발생
캐시 서버의 추가/삭제 - 리밸런싱
해당키의 TTL에 의한 데이터 삭제
캐시서버 메모리 부족으로 해당키의 eviction

Cache Stampede
Cache의 Expire Time설정으로 인해 대규모의 중복된 디비 쿼리와 중복된 Cache쓰기가 발생하는 현상

해결방법: 키의 TTL이 완료하기 전에 Random 한 가상의 Expire Time을 설정해서 미리 키의 내용을 갱신하는 방법(Probabilistic Early Recomputation)
공식은 아래와 같다.

Now() + abs(DELTA * BETA * log(random())) > expiry
DELTA: 실제로 캐시 재계산을 위한 시간 범위(ex. 대략 500ms 근처에서 재계산이 일어나면 좋겠다.)
BETA: DELTA에 다시 가중치를 준다. 보통은 1을 쓰며 1보다 크면 적극적으로 작으면 소극적으로 재계산을 한다.
Expiry는 캐시가 Expire 될 시간을 말함

Hot Key
과도하게 읽기/쓰기 요청이 집중되는 key. 해당 Key의 접근으로 인해서 Storage(DB, Cache) 성능 저하가 발생하는 Key

캐시의 최대 성능이 100,000 TPS 일때 150.000 이 들어오면 5만캐는 캐시가 응답을 하지 않아 DB로 간다.
해결방법:
읽기분배 : Query Off(Read From Secondry) - 레플리카를 여러개둬서 부하분산. Write는 Primary, Read는 Secondary
Local Cache with Redis - 서버와 통신해서 값을 업데이트 해야함. 재수없으면 서버마다 다른 데이터를 갖고있을수도..데이터가 변경되었을 때 통지받는 메커니즘이 필요.(hazelcast, redis v6)
Multiple Write And Read From One - 여러 레플리카에 N번쓰고 하나에서 읽어간다. 쓰기부하가 읽기부하보다 작기때문에 가능. 좀 더 많은 캐시 장비를 써야하고, wirte시 많이 써야한다는점

Timeout를 잡을 땐 Caller의 Timeout설정이 Callee보다 커야한다.

읽어보면 좋을 주제
How to sync client-side/local cache with distributed cache like Redis
https://medium.com/nerd-for-tech/how-to-sync-client-side-local-cache-with-distributed-cache-like-redis-5ec39160cab8
Redis 6.0.0 부터 지원되는기능으로 Client Tracking 을 지원함
https://redis.io/commands/client-tracking/

/

Share