채움의 길
[읽고쓰기] 가상 면접 사례로 배우는 대규모 시스템 설계 기초 - 6장 키/값 저장소 설계 본문
키/값 저장소 설계
키/값 저장소

키/값 데이터베이스라고도 불리는 비 관계형 데이터베이스이다.
이 저장소에 저장되는 값은 고유 식별자를 키로 가져야 하며, 이 키는 유일해야 한다.
- 해당 키에 매달린 값은 키를 통해서만 접근이 가능하다.
- 성능상의 이유로, 키는 짧을 수록 좋다.
- 값은 문자열일수도, 리스트 혹은 객체일수도 있다.
ex) 아마존 다이나모, memcached, Redis 등이 이에 속한다.
put(key, value)
get(key)
put과 get 명령어로 데이터를 쓰고, 읽을 수 있다.
단일 서버 키/값 저장소
단일(한 대) 서버만 사용하는 키/값 저장소의 가장 직관적인 방법은 키/값 쌍 전부를 메모리에 헤시 테이블로 저장하는 것이다.
이는 빠른 속도를 보장하지만, 모든 데이터를 메모리 안에 두는 것이 불가능할수도 있다는 약점이 있다.
⇨ 이를 개선하기 위해 데이터 압축(Compression) 혹은 자주 쓰는 데이터만 메모리에 두고 나머지는 디스크에 저장하는 방식을 사용할 수 있다.
하지만 그럼에도.. 현실적으로 단일 서버는 힘들다. 분산 키값 저장소를 만들어야 한다!
분산 키/값 저장소
분산 키/값 저장소 = 분산 해시 테이블 이라고 봐도 된다.
CAP 정리
- 일관성 (Consistency) : 분산 시스템에 접속하는 클라이언트는 어떤 노드에 접속하든, 언제나 같은 데이터를 봐야한다.
- 가용성 (Availability) : 분산 시스템에 접속하는 클라이언트는 일부 노드에 장애가 발생하더라도 항상 응답을 받을 수 있어야 한다.
- 파티션 감내 (Partition Tolerance) : 파티션은 두 노드 사이에 통신 장애가 발생하였음을 의미하는데, 네트워크에 파티션이 생기더라도 시스템은 계쏙 동작해야 한다.
CAP 중 두 가지를 충족하려면 나머지 하나는반드시 희생되어야 하기 때문에 CA, AP, CP로 적용된다고 봐야 한다.
(하지만 파티션 감내는 반드시 이뤄져야 하기 때문에 실무에서는 대부분 CP, AP만 존재한다고 보면 된다.)
데이터 파티션
데이터를 작은 파티션들로 분할한 다음 여러 대 서버에 저장하는 게 가장 단순한 해결책이다.
이 때 파티션 단위로 나눌 때 고려해야 할 사항들이 있다.
- 데이터를 여러 서버에 고르게 분산시킬 수 있는가
- 노드가 추가되거나 삭제될 때 데이터의 이동을 최소화할 수 있는가
이 사항들은 5장에 나왔던 개념인 안정 해시를 사용하여 적용할 수 있다.
안정 해시를 사용하여 데이터를 파티션하면 좋은 점은 뭐가 있을까?
- 규모 확장 자동화 : 시스템 부하에 따라 서버가 자동으로 추가/삭제 되도록 만들 수 있음
- 다양성 : 각 서버의 용량에 맞게 가상 노드의 수를 조정할 수 있으며, 고성능 서버의 경우 더 많은 가상 노드를 갖도록 설정할 수 있음
안정 해시의 해시 링 위 키가 시계 방향으로 가장 가까운 서버에 저장될 수 있도록 하는 프로세스가 장점이 되는 것 같다.
데이터 다중화
높은 가용성과 안정성을 확보하기 위해서는 데이터를 n개 서버에 비동기적으로 다중화할 필요가 있다.
(N : 튜닝 가능한 값)
N개 서버 선정법
- 어떤 키를 해시 링 위에 배치, 안정 해시에 따라 첫 N개 서버에 데이터 사본을 보관하는 것
- 주의 사항
- 가상 노드를 사용할 경우, n개의 노드가 대응될 실제 물리 서버의 개수보다 많아질 수 있음
- 따라서 같은 물리 서버를 중복 선택하지 않도록 주의해야 함
데이터 일관성
여러 노드에 다중화된 데이터는 적절히 동기화 되어야 한다.
정족수 합의 프로토콜을 사용하면 쓰기/읽기 연산 모두에 일관성 있게 보장할 수 있다.
[정족수 합의 프로토콜이란?]
분산 시스템에서 데이터 일관성과 내결함성을 보장하기 위해 전체 노드 중 최소한의 정족수 노드들이 동의해야 데이터를 읽거나 쓸 수 있도록 하는 합의 알고리즘.
주로 데이터베이스 다중화 환경에서 일시적인 노드 장애가 발생해도 시스템이 정상 작동하게 함.
- N : 사본 개수
- W : 쓰기 연산에 대한 정족수
- R : 읽기 연산에 대한 정족수
W와 R은 쓰기/읽기를 각각 성공했다고 판단하기 위한 중재사의 수를 말하며, 이 때 중재자는 클라이언트와 노드 사이에서 proxy 역할을 한다.
어떤 부분에 일관성을 더 중심적으로 가져갈 것이냐에 따라 N, W, R의 값이 달라질 수 있다.
- R = 1, W = N ⇨ 빠른 읽기 연산 최적화
- W = 1, R = N ⇨ 빠른 쓰기 연산 최적화
- W + R > N ⇨ 강한 일관성을 보장함
- W + R <= N ⇨ 강한 일관성을 보장하지 않음
1번과 2번에 '빠른'이 붙는 이유는 중재자의 개수가 적을 수록 서버 내에서 빠른 처리가 가능하기 때문이다.
일관성 모델은 아래처럼 나눠볼 수 있다.
- 강한 일관성 모델 : 모든 읽기 연산은 가장 최근에 갱신된 결과를 반환
- 약한 일관성 모델 : 읽기 연산은 가장 최근에 갱신된 결과를 반환 못 할수도 있음
- 결과적 일관성 모델 : 약한 일관성의 한 형태이며, 갱신 결과가 결국엔 동기화 되는 모델
강한 일관성 모델
사실 여기까지 읽었을 땐 '당연히 강한 일관성이 제일 좋은 거 아님?' 했는데 실상은 그렇지 않나보다.
강한 일관성 모델을 이뤄내기 위해서는 모든 사본에 현재 쓰기 연산의 결과가 반영될 때 까지 해당 데이터에 대한 읽기/쓰기를 금지하기 때문이다.
이렇게 되면 새로운 요청의 처리가 반복적으로 중단될 것이다. 일관성을 위해서 사용자 경험을 낮추는 거라고 볼 수 있는데, 이런 프로세스가 가능한 서비스는 몇 없을 것이다.. 특히나 고가용성 시스템엔 더욱 적합하지 않다.
결과적 일관성 모델
약한 일관성 모델의 한 형태이지만, 갱신 결과가 결국엔 동기화 된다는 차이점이 있다.
이 모델은 쓰기 연산이 병렬적으로 발생하면 시스템에 저장된 값의 일관성이 깨어질 수 있는데, 이 문제는 클라이언트가 해결해야 한다.
실제로 이 모델은 다이나모, 카산드라에서도 쓰이고 있다고 한다.
다음은 결과적 일관성 모델의 클라이언트가 해결해야 할 부분을 알아보도록 하자.
비 일관성 해소기법: 데이터 버저닝
데이터 버저닝
버저닝은 말 그대로 버전 관리를 한다는 것이다.
데이터 버저닝은 그럼 데이터를 변경할 때마다 해당 데이터의 새로운 버전을 만든다는 뜻이 될 것이다.
벡터 시계
시간에도 방향이 있다..라는 의미의 개념 같다.
정확하게는 [서버, 버전]의 순서쌍을 데이터에 매단 것인데, 어떤 버전이 선행/후행 버전인지, 다른 버전과 충돌이 있는지 판별하는데 사용된다.
D([S1, V1], [S2, V2], ... , [Sn, Vn])
# D : 데이터
# Si : 서버 번호
# Vi : 버저닝 카운터
버전 충돌을 확인하는 방법은
버전 X, 버전 Y가 있다고 가정했을 때, Y의 벡터 시계 구성요소 가운데 X의 벡터 시계 동일 서버 구성요소보다 작은 값을 갖는 것이 있는지 보는 것이다.
D1([S0, 1], [S1, 2])
D2([S0, 2], [S1, 1])
(이해를 위한 D1, D2 카운트)
위와 같은 데이터에서는 D1의 S0과 S1이 D2의 S0과 S1이랑 비교했을 때 S0은 작지만 S1은 크기 때문에 충돌한다고 볼 수 있는 것이다.
하지만 벡터 시계 방법에도 단점은 있다.
- 충돌 감지 및 해소 로직이 클라이언트에 들어가야 함
- 클라이언트의 구현이 복잡해지는 단점
- [서버, 버전] 순서쌍이 굉장히 빨리 늘어남
- 오래된 순서쌍이 발생하여 불필요한 데이터가 쌓일 수 있으므로, 이에 대한 임계치를 설정해야 함
- 버전 간 선후 관계가 정확하지 않아 효율성이 낮은게 우려되지만 실제 벡터 시계를 사용하는 다이나모에서는 그런 일이 발견되지 않았다고 한다.
장애 감지
장애는 서비스에서 아주 흔하게 발생하기 때문에 이를 감지하는 것이 매우 중요하다.
보통은 두 대 이상의 서버가 다른 서버의 장애를 똑같이 보고해야 실제 장애로 간주하며, 사실 모든 노드 사이에 멀티캐스팅 채널을 구축하여 서버들끼리 서로를 감시하도록 하는 게 가장 손쉽다.
하지만 서버가 많을 땐 이를 감시하는 것에도 적지 않은 리소스가 들게 될 것이기 때문에 비효율적이다.
분산 서버에서는 가십 프로토콜을 사용해볼 수 있다.
가십 프로토콜
분산 시스템 내의 노드들이 소문이 퍼지듯 무작위로 이웃 노드들과 주기적으로 상태 정보(데이터)를 교환하여 전체 네트워크에 정보를 빠르게 전파하는 감염형 통신 방식이다.
동작 원리는 아래와 같다.
- 각 노드는 멤버십 목록을 갖고 있다. (멤버십 목록이란 이웃 노드들에 대한 각각의 ID, 하트비트 카운터, 갱신된 시간의 리스트를 말한다.)
- 각 노드는 주기적으로 자신의 하트비트 카운터를 증가 시킨다.
- 각 노드는 무작위로 선정된 노드들에게 주기적으로 자신의 하트비트 카운터 목록을 보낸다.
- 하트비트 카운터 목록을 받은 노드는 멤버십 목록을 최신값으로 갱신한다.
- 어떤 멤버의 하트비트 카운터 값이 지정된 시간동안 갱신되지 않으면 해당 멤버는 장애 상태인 것으로 간주한다.
일시적 장애 처리
가십 프로토콜로 장애를 감지한 시스템은 가용성 보장을 위해 필요한 조치를 해야 한다.
이 때, 위에서 언급됐던 '정족수' 개념이 다시 나온다.
- 엄격한 정족수 : 데이터 일관성을 높이는 대신, 쓰기/읽기 연산을 금지함
- 느슨한 정족수 : 데이터 가용성을 높이며, 쓰기 연산을 수행할 W개의 정상 서버 + 읽기 연산을 수행할 R개의 정상 서버를 해시 링을 통해 고름 (장애 서버는 무시)
장애 서버로 가게 되는 요청은 다른 서버가 잠시 맡아 처리하고, 그 동안 발생하는 변경 사항들은 해당 서버가 복구되었을 때 일괄적으로 반영하여 데이터 일관성을 보존한다.
이를 위하여 임시로 쓰기 연산을 처리한 서버에는 그에 관한 단서를 남겨둔다고 한다.
이런 처리 방안을 단서 후 임시 위탁 기법이라 한다.
영구 장애 처리
반-엔트로피 프로토콜이라는 개념이 등장하는데, 엔트로피(무질서)의 반대 뜻을 가진 프로토콜이라는 걸 알 수 있다.
좀 더 풀어서 설명하자면, 분산 시스템에서 여러 노드 간의 데이터 불일치를 탐지하고 해결하여 최종적으로 모든 데이터가 일치하도록 만드는(결과적 일관성) 가십 프로토콜의 일종이다.
결과적 일관성을 만들고, 사본간의 일관성이 망가진 상태를 탐지하며 전송 데이터 양을 줄이기 위해서는 머클 트리가 필요하다.
머클 트리란, 여러 데이터를 하나로 요약하여 데이터의 무결성을 빠르고 안전하게 검증하는 이진 해시 트리 구조로
실제 장애 처리 상황에서는 두 머클 트리의 루트 노드부터 비교를 시작하여 점점 아래로 내려가며 다른 해시값인 노드를 찾아가는 흐름을 가진다.

이 후의 내용들은 대부분 중복되는 듯하여 생략...
'지식 채우기 > 서적' 카테고리의 다른 글
| [읽고쓰기] 가상 면접 사례로 배우는 대규모 시스템 설계 기초 - 5장 안정 해시 설계 (0) | 2026.01.19 |
|---|---|
| [읽고쓰기] 가상 면접 사례로 배우는 대규모 시스템 설계 기초 - 4장 처리율 제한 장치의 설계 (0) | 2026.01.16 |