10KB = 10000 Byte
UTF-8 인코딩: 1~4바이트/문자 → 2,500~10,000자

✅ 데이터 압축
- 압축 알고리즘 적용: LZF, LZ4, Zstandard 등 고속 압축 알고리즘을 사용하여 데이터 크기 감소

✅ CP, AP 시스템 추가 예시
- MongoDB (CP 시스템)
- 비동기 복제를 사용하여 데이터의 여러 복사본을 분산시킨다
- 주요 구성 요소
- 프라이머리: 모든 쓰기 작업을 처리하는 마스터 노드
- 세컨더리: 프라이머리로부터 데이터를 복제받아 동일한 데이터 세트를 유지하는 노드
- 기본적으로 프라이머리가 모든 읽기와 쓰기를 처리하는데, 프라이머리 실패 시 새로운 프라이머리 선출에 최대 12초가 소요됨
- 선출 과정 동안 모든 쓰기 작업이 중단됨
- Cassandra (AP 시스템)
- 카산드라는 Peer-to-peer 시스템
- 즉, 카산드라는 프라이머리 노드 없이 모든 노드가 읽기 작업과 쓰기 작업을 수행할 수 있고 복제본을 분리된 다른 노드에 저장
- 프라이머리 노드 없이 모든 노드가 같은 작업을 수행하므로 SPOF가 없다는 장점이 있음
- 데이터는 복제 팩터 (데이터 복제본의 수) 에 따라 복제되며, 안정해시 기반 데이터 파티셔닝 제공
- 파티션이 발생해서 복제본이 업데이트된 데이터 사본을 받지 못하는 상황이 발생할 수 있으나, 다른 노드들은 여전히 사용자가 사용할 수 있으므로 데이터 일관성이 깨짐
- 그러나 Cassandra는 최종 일관성(eventual consistency) 제공
- 즉, 모든 노드간 데이터가 동기화되기 전까지 각 노드는 서로 다른 버전의 데이터를 가지고 있을 수 있음
- 카산드라가 최종 일관성을 보장하는 방법
- 복제 팩터: 데이터의 복제본 수를 결정하여 여러 노드에 데이터를 분산 저장
- 일관성 수준 설정: 읽기/쓰기 작업에 대한 일관성 수준 조절 가능
- 읽기 복구: 일관성이 맞지 않는 데이터를 발견하면 자동으로 최신 데이터로 업데이트
- 가십 프로토콜: 노드 간 주기적인 데이터 동기화를 통해 일관성 유지 (Cassandra의 모든 노드는 데이터 및 노드 상태에 대한 정보를 브로드캐스트하는 가십 프로토콜이라는 피어 투 피어 통신 프로토콜을 통해 서로 통신)
- Reference


✅ 안정 해시 구현 시 사용할 수 있는 방법
- 데이터 센터 인식 파티셔닝: 해시 링에 노드를 배치할 때 각 데이터 센터의 노드를 번갈아가며 배치
- 가상 노드 식별자 생성: 각 물리적 서버에 대해 여러 개의 가상 노드를 생성할 때, 데이터 센터 ID를 포함시킴. ex) "DC1-Server1-VNode1", "DC2-Server1-VNode1" 등의 형식으로 식별자 생성
- 해시 함수 수정: 가상 노드의 해시 값을 계산할 때, 데이터 센터 ID를 고려하는 해시 함수 사용. 이를 통해 같은 데이터 센터의 노드들이 해시 링에서 연속적으로 배치되는 것을 방지 가능
- 복제 전략 수립: 데이터의 복제본을 다른 데이터 센터의 노드에 저장하도록 설정
- 고속 네트워크 연결: 데이터 센터 간 고속 네트워크를 구축하여 효율적인 데이터 복제와 동기화를 가능하게 함
여러 노드에 다중화된 데이터는 적절히 동기화가 되어야 한다.
정족수 합의(Quorum Consensus) 프로토콜을 사용하면 읽기/쓰기 연산 모두에 일관성을 보장할 수 있다.

중재자는 클라이언트와 노드 사이에서 프록시 역할을 한다
❓ 중재자는 보통 어떤 컴포넌트로 구현되는지?
= 어떠한 컴포넌트들이 클라이언트 <-> 노드 사이의 프록시 역할을 하면서, 정족수 합의 프로토콜을 구현할 수 있는지?
- Kubernetes 의 etcd (모든 클러스터 데이터를 백업하는 Key-value 저장소)
- Etcd는 Replicated state machine(이하 RSM) 으로, RSM 은 분산 컴퓨팅 환경에서 서버가 몇 개 다운되어도 잘 동작하는 시스템을 만들고자 할 때 선택하는 방법의 하나이다.
- etcd는 하나의 write 요청을 받았을 때, 쿼럼 숫자만큼의 서버에 데이터 복제가 일어나면 작업이 완료된 것으로 간주하고 다음 작업을 받아들일 수 있는 상태가 됨
- ex. RSM을 구성하는 서버의 숫자가 3대인 경우 쿼럼 값은 2(3/2+1)
- 여기서 쿼럼 수식이 N(서버 수)/2+1 인 이유는 다수의 합의 요청이 가능하기 위함이다
- 보통 클러스터 수는 홀수 개의 노드로 구성한다고 함. ref; 반드시 클러스터 노드의 수가 짝수가 되면 안 되는 건가?
W, R, N의 값을 정하는 것은 응답 지연과 데이터 일관성 사이의 타협점을 찾는 전형적인 과정이다.
가능한 W, R, N 의 구성
❓ 일관성을 보장하게 되는 최종 시점도 중요하지 않은지? 왜 시간에 대한 언급이 없나..
ex. 1분에 1회씩은 W+R>N 을 보장한다거나.. 무작정 W+R>N 이 될때까지 기다리진 않을테니까..
- cassandra 예시 https://cassandra.apache.org/doc/3.11/cassandra/configuration/cass_yaml_file.html
- write_request_timeout_in_ms
- read_request_timeout_in_ms
- 타임아웃 설정을 통해서 보장 가능. 실제 타임아웃에 걸리면
(ReadTimeoutException): Cassandra timeout during read query at consistency QUORUM (2 responses were required but only 1 replica responded) after nodetool cleanup이런식으로 에러가 발생함
버저닝 과 벡터 시계 는 이 문제를 해소하기 위해 등장한 기술이다.

D1[(Sx, 1)]으로 변한다.D2([Sx, 2])로 바뀔 것이다.D3([Sx, 2], [Sy, 1]])로 바뀐다.D4([Sx, 2], [Sz, 1])일 것이다.D5([Sx, 3], [Sy, 1], [Sz, 1])로 바뀐다.
- 위에서 말하는 클라이언트 == 저장소 시스템과 상호작용하는 소프트웨어 컴포넌트
- 애플리케이션 서버, 데이터베이스 클라이언트 라이브러리 일 수 있음.
- 애플리케이션 서버가 클라이언트라고 가정한다면, 아래와 같은 코드를 짤 수 있음
public class VectorClockClient {
// 벡터 시계를 저장하는 맵. 키는 서버 ID, 값은 해당 서버의 버전
private Map<String, Integer> vectorClock;
// 생성자: 빈 벡터 시계로 초기화
public VectorClockClient() {
this.vectorClock = new HashMap<>();
}
// 특정 서버의 버전을 업데이트하는 메소드
public void update(String server, int version) {
// 현재 저장된 버전과 새 버전 중 더 큰 값을 선택하여 저장
vectorClock.put(server, Math.max(vectorClock.getOrDefault(server, 0), version));
}
// 다른 벡터 시계와 충돌을 감지하는 메소드
public boolean detectConflict(Map<String, Integer> otherClock) {
// 현재 벡터 시계의 각 서버에 대해
for (String server : vectorClock.keySet()) {
if (otherClock.containsKey(server)) {
// 현재 벡터 시계의 버전이 더 크면 충돌
if (vectorClock.get(server) > otherClock.get(server)) {
return true;
}
}
}
// 다른 벡터 시계의 각 서버에 대해
for (String server : otherClock.keySet()) {
if (vectorClock.containsKey(server)) {
// 다른 벡터 시계의 버전이 더 크면 충돌
if (otherClock.get(server) > vectorClock.get(server)) {
return true;
}
}
}
// 충돌이 없으면 false 반환
return false;
}
// 충돌을 해결하고 병합된 벡터 시계를 반환하는 메소드
public Map<String, Integer> resolveConflict(Map<String, Integer> otherClock) {
// 현재 벡터 시계를 복사하여 새로운 맵 생성
Map<String, Integer> resolvedClock = new HashMap<>(vectorClock);
// 다른 벡터 시계의 각 항목에 대해
for (Map.Entry<String, Integer> entry : otherClock.entrySet()) {
// 두 벡터 시계 중 더 큰 버전을 선택하여 병합된 시계에 저장
resolvedClock.put(entry.getKey(), Math.max(resolvedClock.getOrDefault(entry.getKey(), 0), entry.getValue()));
}
// 병합된 벡터 시계 반환
return resolvedClock;
}
}
// 그래서 데이터를 읽을 때 충돌 발생한 것을 클라이언트 (애플리케이션 서버) 가 감지하고 해소
public Data read(String key) {
ServerResponse response = server.read(key);
Data data = response.getData();
Map<String, Integer> serverClock = response.getVectorClock();
if (vectorClockClient.detectConflict(serverClock)) {
// 충돌 발생, 해결 로직 실행
resolveConflict(key, data, serverClock);
} else {
// 충돌 없음, 로컬 벡터 시계 업데이트
vectorClockClient.update(serverId, serverClock.get(serverId));
}
return data;
}
보통 두 대 이상의 서버가 똑같이 서버 A의 장애를 보고해야 실제로 해당 서버에 장애가 발생했다고 간주한다

가십 프로토콜 같은 분산형 장애 감지 솔루션을 채택하는 편이 더 효율적이다.

✅ hinted handoff 실제 예시
- 카산드라가 노드 장애시에도 fault tolerance (결함 내성) 을 유지하는 주요 방법 중 하나는 hinted handoff 메커니즘이다
- cassandra.yaml에서 hinted_handoff_enabled이 true로 설정되어 있으면(기본 설정) 복제 노드 중 하나에 도달 할 수 없을 때 카산드라는 coordinator 노드에 hint를 저장
- coordinator
- 해당 힌트는 클러스터에 속한 위치에 대한 정보를 포함한다.
- 힌트는 코디네이터 노드의 $CASSANDRA_HOME/data/hints 디렉터리에 있는 플랫 파일에 저장됨. 힌트에는 힌트 ID, 변경 사항을 저장할 대상 복제본 노드, 복제본 노드로 전달할 수 없는 직렬화된 변경 사항(blob으로 저장), 변경 사항 타임스탬프 및 변경 사항을 직렬화하는 데 사용된 Cassandra 버전이 포함됨
- Cassandra의 이전 버전에서는 힌트를 힌트 테이블에 저장했는데, 최신 Cassandra 릴리스는 힌트를 저장하기 위해 디스크의 플랫 파일을 사용
- coordinator가 복제 노드가 다시 정상화되는 것을 알아차리게 되면 hint 정보를 복제 노드에서 replay (코디네이터 노드가 저장해 둔 힌트(hint) 정보를 복구된 복제 노드에 적용하는 과정) 시킨다.
- 기본적으로 Cassandra는 hint 대기열이 너무 길어지는 것을 방지하기 위해 최대 3시간 동안 hint를 저장한다.
- cassandra.yaml의 max_hint_window_in_ms 속성(기본 3시간)이 해당 hint가 저장되는 시간을 의미한다.
- max_hin_window_in_ms 시간이 지나면 일관성을 복원하기 위해서 노드가 다시 정상화되었을 때 수동으로 node repair를 실행해야 한다.
+) AWS DynamoDB 도 hinted handoff 사용한다고 함.
ref; https://cassandra.apache.org/doc/4.0/cassandra/operating/hints.html

반-엔트로피 프로토콜을 구현해서 사본들을 동기화해야 한다.머클 트리를 사용할 것이다.


블룸 필터가 흔히 사용된다.➕ Cassandra
- 카산드라는 일반적인 DB에서 쓰이는 B-Tree 대신 Log-Structured Merge Tree (LSM Tree) 사용
- 블룸 필터 : Bloom Filter는 어떤 원소 x가 어떤 집합 A 의 원소인지 확률적으로 판단하는 표시함수
- 블룸 필러를 이용한 membership query (어떤 집합 A와 어떤 원소 x가 'x가 A에 포함된' 관계인지 묻는 것) 의 결과 값은 '아마도 해당 집합의 원소인 것 같다', 또는 '확실히 해당 집합에 포함된 원소가 아니다'를 뜻한다.
이런 특성 때문에 블룸 필터는 단독으로 사용하는 것보다는 확률적인 방법이 아닌 다른 방법을 보조하는 역할로 사용하는 것이 적합하다

1. 데이터가 메모리 있는지 검사한다.
2. 데이터가 메모리에 없으므로 블룸 필터를 검사한다.
3. 블룸 필터를 통해 어떤 SSTable에 키가 보관되어 있는지 알아낸다.
4. SSTable에서 데이터를 가져온다.
5. 해당 데이터를 클라이언트에 반환한다.