어흥

[대규모 시스템 설계] 6. 키-값 저장소 설계 - 2 본문

개발/대규모 시스템 설계

[대규모 시스템 설계] 6. 키-값 저장소 설계 - 2

라이언납시오 2023. 8. 16. 17:03
728x90
반응형

시스템 컴포넌트

- 데이터 파티션
- 데이터 다중화
- 일관성
- 일관성 불일치 해소
- 장애 처리
- 시스템 아키텍처 다이어그램
- 쓰기 경로
- 읽기 경로

 

데이터 파티션

데이터를 작은 파티션들로 분할한 다음 여러 대 서버에 저장한다. 데이터를 파티션 단위로 나눌 땐 다음 2가지를 고려한다

- 데이터를 여러 서버에 고르게 분산할 수 있는가

- 노드가 추가되거나 삭제될 때 데이터의 이동을 최소화할 수 있는가

 

5장에서 다룬 안정 해시를 통해 이런 문제를 해결할 수 있다.

우선 서버를 해시링에 배치한다. 어떤 키-값 쌍을 어떤 서버에 저장할지 결정하려면 우선 해당 키를 같은 링 위에 배치하고 시계 방향으로 순회하다 만나는 첫 번째 서버에 저장한다. 안정 해시를 사용해서 데이터를 파티션하면 다음과 같은 장점이 있다

- 규모 확장 자동화: 시스템 부하에 따라 서버가 자동적으로 추가되거나 삭제되도록 만든다

- 다양성: 각 서버의 용량에 맞게 가상 노드의 수를 조정할 수 있다. 고성능 서버는 더 많은 가상 노드를 갖도록 설정한다.

 

데이터 다중화

높은 가용성과 안정성을 확보하기 위해선 데이터를 N개 서버에 비동기적으로 다중화한다(N은 튜닝 가능한 값). N개 서버를 선정하는 방법은 특정 키를 해시 링 위에 배치한 후, 그 지점으로부터 시계 방향으로 링을 순회하면서 만나는 첫 N개 서버에 데이터 사본을 보관한다.

데이터 다중화 방법

이때 주의사항으론 실제 물리 서버의 개수가 N보다 작은 경우도 존재할 수 있는데, 같은 물리 서버를 중복 선택하지 않도록 해야 한다. 안정성을 담보하기 위해 데이터의 사본은 다른 센터의 서버에 보관하고, 센터들은 고속 네트워크로 연결한다.

 

데이터 일관성

여러 노드에 다중화된 데이터는 적절히 동기화가 되어야 하며 이때 정족수 합의(Quorom Consensus) 프로토콜을 사용하면 읽기/쓰기 연산 모두에 일관성을 보장할 수 있다.

N: 사본 개수
W: 쓰기 연산에 대한 정족수.
쓰기 연산이 성공한 것으로 간주되려면 적어도 W개의 서버로부터 쓰기 연산이 성공했다는 응답을 받아야 한다
R: 읽기 연산에 대한 정족수.
읽기 연산이 성공한 것으로 간주되려면 적어도 W개의 서버로부터 읽기 연산이 성공했다는 응답을 받아야 한다

N: 3인 경우에 대한 정족수 합의 프로토콜 예시

 

W:1 → 데이터가 1대 서버에만 기록된다(X)

→ 쓰기 연산이 성공했다고 판단하기 위해 중재자는 최소 한대의 서버로부터 ACK를 받아야 한다(O)

→ Ex) 서버 1로부터 성공했다는 ACK를 받았다면, 서버 2와 3의 결과를 기다릴 필요 없다

중재자란?
클라이언트와 노드 사이에서 Proxy 역할을 한다

W,R,N의 값을 정하는 것은 응답 지연과 데이터 일관성 사이의 타협점을 찾는 전형적인 과정

W + R > N인 경우, 일관성을 보증할 최신 데이터를 가진 노드가 최소 하나는 겹치기 때문에 강한 일관성이 보장된다.

 

그렇다면 적절한 구성은? 요구되는 일관성 수준에 따라 아래중 하나로 설정하면 된다

1. R = 1, W = N: 빠른 읽기 연산에 최적화된 시스템
2. W = 1, R = N: 빠른 쓰기 연산에 최적화된 시스템
3. W + R > N: 강한 일관성이 보장됨(Ex. N = 3, W = R = 2)
4. W + R <= N: 강한 일관성이 보장되지 않음

 

일관성 모델

일관성 모델은 데이터 일관성의 수준을 결정하며 종류가 다양하다

- 강한 일관성: 모든 읽기 연산은 가장 최근에 갱신된 결과를 반환한다. 즉, 클라는 절대로 낡은 데이터를 보지 못한다.

- 약한 일관성: 읽기 연산은 가장 최근에 갱신된 결과를 반환하지 못할 수 있다.

- 결과적 일관성: 약한 일관성의 한 형태로, 갱신 결과가 결국에는 모든 사본에 반영된다(동기화)

 

강한 일관성 달성 방법: 모든 사본에 현재 쓰기 연산의 결과가 반영될 때까지 해당 데이터에 대한 R/W 금지. 하지만 새로운 요청의 처리가 중단되기 때문에 고가용성 시스템에는 적합하지 않다.

다이나모나 카산드라는 결과적 일관성 모델을 사용한다. 이 경우, 쓰기 연산이 병렬적으로 발생하면 시스템에 저장된 값의 일관성이 깨질 수 있는데 이는 클라가 해결해야 한다. 클라에서 버전 정보를 활용해 일관성이 깨진 데이터를 읽지 않도록 해야 한다

 

비 일관성 해소 기법: 데이터 버저닝

데이터를 다중화하면 가용성은 높아지지만 사본 간 일관성이 깨질 가능성은 높아진다. 버저닝과 벡터 시계는 이 문제를 해결하기 위해 등장한 기술이다. 버저닝은 데이터를 변경할 때마다 해당 데이터의 새로운 버전을 만든다. 따라서 각 버전의 데이터는 변경 불가능하다.

벡터 시계는 [서버, 버전]의 순서쌍을 데이터에 매단것이다. D([S1,v1],[S2,v2],...,[Sn, vn])과 같이 표현한다(D: 데이터, vi: 버전 카운터, Si: 서버 번호).

만일 데이터 D를 서버 S1에 기록하면, 시스템은 아래 작업 가운데 하나를 수행한다

- [S1,vi]가 있으면 vi+1을 한다

- 없다면 새 항목 [S1,1]을 만든다

1. 클라가 데이터 D1을 시스템에 기록하며 이 쓰기 연산을 처리한 서버는 S1이다 → D1([S1,1])

2. 다른 클라가 데이터 D1을 읽어서 D2로 업데이트하고 기록한다. 이때도 처리 서버는 S1이다 → D2([S1,2])

3. 다른 클라가 데이터 D2를 읽어서 D3로 업데이트하고 기록한다. 이때 처리 서버는 S2다 → D3([S1,2],[S2,1])

4. 3과 동시에 다른 클라가 D2를 읽어서 D4로 업데이트하고 기록한다. 이때 처리 서버는 S3다 → D4([S1,2],[S3,1])

5. 어떤 클라가 D3와 D4를 읽으면 데이터 간 충돌이 있다는 것을 알게 된다. D2를 S2,3가 각각 다른 값으로 바꿨기 때문. 이 충돌은 클라에서 해소하고 서버에 기록한다. 이때 처리 서버는 S1이다 → D5([S1,3],[S2,1],[S3,1])

 

벡터 시계를 사용하면 버전 X가 버전 Y의 이전 버전인지 쉽게 판단 가능하다. 버전 Y에 포함된 모든 구성요소의 값이 X에 포함된 모든 구성요소 값보다 같거나 큰지만 보면 된다. Ex) D([S0,1],[S1,1])은 D([S0,1],[S1,2])의 이전 버전이다

어떤 버전 X와 Y 사이에 충돌이 있는지 보려면 Y의 벡터 시계 구성요소 가운데 X의 벡터 시계 동일 서버 구성요소보다 작은 값을 갖는 것이 있는지 확인하면 된다(모든 요소가 작다면 이전버전이다). Ex) D([S0,1],[S1,2])와 D([S0,2],[S1,1])은 서로 충돌

 

그러나 벡터 시계를 사용해 충돌을 감지하고 해소하는 방법은 2가지 단점이 있다

1. 충돌 감지 및 해소 로직이 클라에 들어가야하므로, 클라 구현이 복잡해진다

2. [서버:버전]의 순서쌍 개수가 굉장히 빨리 늘어난다. 이 문제를 해결하려면 임계치를 정하고 임계치를 넘어서면 오래된 데이터 순으로 지워야 하는데 이렇게 하면 버전 간 선후 관계가 정확하지 않다. 하지만 다이나모 DB 문헌에선 그런 문제가 발생한 적 없다고 한다.

728x90
반응형
Comments