diff --git "a/Ch.5 \354\225\210\354\240\225 \355\225\264\354\213\234 \354\204\244\352\263\204/\352\260\234\353\205\220 \353\260\217 \352\264\200\353\240\250 \354\247\200\354\213\235 \355\225\231\354\212\265.md" "b/Ch.5 \354\225\210\354\240\225 \355\225\264\354\213\234 \354\204\244\352\263\204/\352\260\234\353\205\220 \353\260\217 \352\264\200\353\240\250 \354\247\200\354\213\235 \355\225\231\354\212\265.md" new file mode 100644 index 0000000..488ac5e --- /dev/null +++ "b/Ch.5 \354\225\210\354\240\225 \355\225\264\354\213\234 \354\204\244\352\263\204/\352\260\234\353\205\220 \353\260\217 \352\264\200\353\240\250 \354\247\200\354\213\235 \355\225\231\354\212\265.md" @@ -0,0 +1,267 @@ +# Ch.5 안정 해시 설계 + +## 학습 항목 목록 + + +## 1. rehash 문제와 안정 해시의 필요성 +- **출처**: 책 본문 + +### 무엇인가 · 왜 중요한가 +분산 캐시/DB에서 N대 서버에 키를 나누는 단순한 방법은 `serverIndex = hash(key) % N`. +이 방식은 서버 풀 크기가 고정이고 데이터가 균등할 때만 잘 동작한다. 안정 해시는 바로 이 방식의 약점을 풀기 위해 존재하므로, 이 문제 인식이 5장 전체의 동기다. + +### 동작 원리 (왜 대부분의 키가 재배치되는가) +- `hash(key)`는 키가 같으면 항상 같은 값이지만, 최종 위치는 `% N`, 즉 **N(서버 수)에 의존**한다. +- N이 바뀌면 나머지 값이 거의 전부 바뀐다 → 서버 1대만 죽어도 **그 서버와 무관한 키까지** 다른 서버로 재매핑됨 (평균 (N-1)/N ≈ 대부분). +- 재배치된 키는 새 서버에 데이터가 없어 **대규모 캐시 미스(cache miss storm)** → 트래픽이 원본 DB로 몰려 장애 전파. +- 문제의 원인은 서버 장애가 아니라 **위치 결정이 전체 서버 수 N에 묶여 있다는 것.** + +### 실제 세계 +- Memcached 클라이언트는 초기 `hash % N`에서 노드 변동 시 캐시 전체 무효화 문제로 **ketama(libketama, 2007)** 같은 consistent hashing으로 이동. +- 같은 문제가 샤딩된 DB, 로드밸런서에도 동일하게 발생. + +### 차주 설계와의 연결 +캐시/샤드 분배 질문에서 먼저 `hash % N`을 제시하고 "노드 변동 시 대규모 재배치"라는 약점을 스스로 지적한 뒤 안정 해시로 넘어가는 흐름이 정석. + +### 핵심 Q&A +- 안정 해시도 **재배치되는 키는 캐시 미스가 난다** — 미스를 없애는 게 아니다. +- 차이는 재배치되는 키의 **개수**: `%N`은 거의 전부, 안정 해시는 k/n 정도만. +- 목표는 "재배치 0"이 아니라 **변동된 노드 주변으로 재배치를 국소화(localize)**하는 것. + - 서버 삭제 → 죽은 서버의 키만 시계방향 다음 서버로 이동 + - 서버 추가 → 새 서버 구간에 들어오는 키만 새 서버로 이동, 나머지는 그대로 + +### 내 말로 정리 +> 데이터 해시값 / 서버 수 같은 modular 방식은 서버가 추가·삭제되면 대부분의 키가 재배치되고, 재배치된 서버엔 실제 데이터가 없으니 대량 캐시 미스가 발생한다. 안정 해시는 죽은 서버의 키들만(추가 시엔 새 서버 구간의 키만) 딱 골라 재배치하고, 그에 따른 소량의 캐시 미스만 감당하는 방식이다. + +--- + +## 2. 해시 공간·해시 링과 해시 함수 (+SHA-1) +- **출처**: 책 본문 + name-drop(SHA-1) 심화 + +### 무엇인가 · 왜 중요한가 +1번의 목표("위치 결정을 N에서 분리")를 실제로 구현하는 자료구조가 **해시 링**이고, 그 링을 만드는 재료가 **해시 함수**다. +- **해시 공간(hash space)**: 해시 함수가 낼 수 있는 모든 출력값의 범위. SHA-1은 160비트라 `0 ~ 2¹⁶⁰-1`. +- **해시 링(hash ring)**: 이 일직선 구간의 양 끝을 이어붙여 원으로 만든 것. `2¹⁶⁰-1` 다음이 다시 `0`. + +### 동작 원리 — 왜 "링(원)"인가 +- 서버와 키를 **같은 해시 공간** 위에 올린다: `hash(서버IP)`, `hash(key)` 각각 링 위 한 점. +- 규칙: 키 위치에서 **시계방향으로 돌다 처음 만나는 서버**가 담당. +- "원"이어야 하는 이유 = **wrap-around 규칙**: 가장 큰 해시값 근처 키는 오른쪽에 서버가 없을 수 있는데, 끝을 넘어 0으로 되돌아가 처음 만나는 서버에 배정한다. 이 "끝→0 되돌아감" 규칙 자체가 곧 링이다. (물리적 원이 아니라 규칙에 붙인 이름.) +- 키 위치는 `hash(key)`로 고정, 서버 추가/삭제해도 다른 서버 위치는 그대로 → 영향이 변동 노드 구간에 국소화됨. + +### 실제 세계 — SHA-1과 그 대안 (책이 생략한 부분) +- 안정 해시가 해시 함수에 요구하는 성질 3가지: **① 결정적(deterministic) ② 균등 분포(uniform) ③ 넓은 출력 공간(충돌 확률 낮음).** 보안은 불필요. +- SHA-1은 암호학적 해시라 느림 → 안정 해시엔 과함. 실무는 **비암호학적 해시**를 씀: + - **MurmurHash** (Cassandra 기본 `Murmur3Partitioner`), **MD5** (ketama/libketama), xxHash, CRC32. +- 해시 함수 = *임의 입력 → 고정 크기 숫자* 알고리즘의 총칭. 두 부류: 암호학적(SHA-1/256, MD5) vs 비암호학적(MurmurHash, xxHash, CRC). + +### 충돌(collision)과 SHA-1 +- **충돌**: 서로 다른 입력이 같은 해시값을 내는 것. 입력은 무한, 출력은 유한 → **모든 해시 함수에 충돌은 필연**(비둘기집 원리). SHA-256에도 존재. +- "안전한 해시" = 충돌이 없는 게 아니라, **충돌을 의도적으로 만드는 게 계산적으로 불가능**한 해시. +- **SHA-1이 깨진 이유**: 브루트포스(2⁸⁰)로 깬 게 아니라, 내부 구조의 **지름길(shortcut)**이 발견돼 SHAttered(2017, 구글)가 약 2⁶³ 계산으로 충돌 PDF 2개를 실제 제작 → 보안 표준에서 퇴출. +- **SHA-256 신뢰 근거**: ① 브루트포스 비용 2¹²⁸이 물리적으로 불가능(생일 역설 기준 `2^(n/2)`) + ② 수십 년 집단 검증에서 지름길 미발견. 단, **증명된 안전이 아니라 "아직 안 뚫린 안전"** (MD5→SHA-1→SHA-256 교체 역사). +- 안정 해시는 공격 대상이 아니므로 충돌 여부와 무관, 속도·균등성만 중요. + +### 차주 설계와의 연결 +"해시 함수 뭘 쓸까?" → "SHA-1도 되지만 보안이 불필요하니 MurmurHash 같은 빠른 비암호학적 해시가 실무엔 낫다"가 한 단계 깊은 답. + +### 내 말로 정리 +> 해시 링은 물리적 원이 아니라 "끝 다음은 0으로 되돌아간다"는 wrap-around 규칙이다. 제일 큰 해시값 근처 키도 0부터 다시 시계방향으로 탐색해 처음 만나는 서버에 배정되므로 담당 서버 없는 키가 안 생긴다. +> 충돌은 모든 해시에 필연적으로 존재한다(무한 입력 → 유한 출력). SHA-1/MD5가 위험한 건 충돌이 있어서가 아니라 공격자가 충돌을 의도적으로 만들 수 있게 됐기 때문이고, 보안(서명·비밀번호)에 쓰면 위조 위험이 있다. 하지만 안정 해시는 보안 목적이 아니라 어느 서버에 저장할지 결정하는 용도라 충돌과 무관하며, 빠르고 균등하게만 계산되면 된다. + +--- + +## 3. 서버/키 배치 · 서버 조회 · 재배치 범위 +- **출처**: 책 본문 + 구현 심화(이진 탐색) + +### 동작 원리 +- **배치**: `hash(서버IP)`, `hash(key)` 모두 같은 해시 링 위 점으로. +- **조회**: 키 위치에서 **시계방향 첫 서버**가 담당 = "키 해시값보다 크거나 같은 서버 중 최소값"(ceiling). 없으면 wrap → 첫 서버. +- **파티션**: 각 서버는 "반시계 방향 이전 서버 ~ 자기 자신" 구간을 담당. + +### 재배치 범위 (핵심) +- **서버 추가(s4)**: 영향 범위 = `s4 ~ 반시계 첫 서버(s3)` 구간의 키만 s4로 이동. 나머지 불변. +- **서버 삭제(s1)**: 영향 범위 = `반시계 이전 서버 ~ s1` 구간의 키만, **시계방향 다음 서버(s2)로** 이동. 나머지 불변. +- 즉 변동 노드와 그 반시계 이전 노드 사이 파티션 하나 분량만 움직임 = 1번의 "국소화"의 실체. + +### 조회는 왜 O(log S)인가 — 이진 탐색 & ceiling +- **이진 탐색**: 정렬된 목록에서 가운데를 보고 후보를 절반씩 버려가며 찾는 법. 선형 탐색 O(n) → 이진 탐색 O(log n). 전제: **정렬돼 있어야 함**. + - 가운데 값 < 키 → 왼쪽 절반 통째로 버림. 가운데 값 ≥ 키 → 후보로 기억하고 더 왼쪽 확인 → "크거나 같은 것 중 최소값"이 나옴. +- **ceiling**: 정렬 목록에서 "내 값보다 크거나 같은 것 중 최소" = 시계방향 첫 서버. +- **해시맵을 못 쓰는 이유**: 해시맵의 O(1)은 "정확히 일치하는 키" 찾기용. 안정 해시 조회는 키 해시값과 정확히 같은 서버가 거의 없고 **"다음으로 큰 값(successor)"**을 찾아야 하는데, 해시맵은 순서 개념이 없어 불가. → 정렬 구조 + 이진 탐색 필요. +- 서버가 몇 개뿐이면 무의미하지만, **가상 노드로 링 위 점이 수천~수만 개**가 되면 O(log n)이 실제로 중요. +- 구현: Java `TreeMap.ceilingKey()`(없으면 `firstKey()`로 wrap), C++ `std::map::lower_bound`, Python `bisect`. + +```java +TreeMap ring = new TreeMap<>(); +String getServer(String key) { + long h = hash(key); + Long pos = ring.ceilingKey(h); // h 이상인 첫 서버 위치 (이진 탐색, O(log S)) + if (pos == null) pos = ring.firstKey(); // 없으면 wrap → 첫 서버 + return ring.get(pos); +} +``` + +### 차주 설계와의 연결 +"서버 하나 죽으면 어떤 키가 옮겨지나?" → "죽은 노드와 반시계 이전 노드 사이 구간의 키만, 시계방향 다음 노드로." 조회는 정렬 구조 + 이진 탐색으로 O(log S). + +### 내 말로 정리 +> 키는 링에서 시계방향으로 이동하며 처음 만나는 서버(= 키 해시값보다 크면서 가장 가까운 서버)에 저장된다. 서버1이 삭제되면 반시계 이전 서버 ~ 서버1 구간의 키가 시계방향 다음 서버(서버2)로 이동한다. +> 조회 때 정확히 일치하는 서버가 아니라 "키보다 크면서 제일 가까운 서버"를 찾아야 하므로 비교가 필요하다. 정렬된 서버 목록에서 가운데 값을 보고, 키보다 작으면 그 이하를 버리고 남은 범위에서 또 절반을 조회하는 과정을 반복해(이진 탐색) 큰 것 중 가장 가까운 값을 찾는다. + +--- + +## 4. 기본 구현의 두 문제 (파티션·키 불균등) +- **출처**: 책 본문 (가상 노드의 존재 이유) + +### 동작 원리 — 두 문제 +- **문제 ① 파티션 불균등**: 파티션 = 인접 두 서버 사이 해시 공간. 서버 위치가 `hash(IP)`로 무작위 흩어져 고르게 배치된다는 보장이 없음 → 어떤 서버는 거대한 구간, 어떤 서버는 작은 구간 담당. 서버 삭제 시 옆 서버 파티션이 2배로 커지기도(그림 5-10). +- **문제 ② 키(부하) 불균등**: ①의 결과. 큰 파티션 = 많은 키 몰림 → 특정 서버 과부하, 다른 서버는 놀음(그림 5-11). 안정 해시의 "균등 분산" 목표 자체가 깨짐. + +### 근본 원인 +- 해시 함수는 "많이 뿌리면 고르게 퍼지는" 성질이 있는데, 링 위 점이 서버 수만큼(예: 4개)뿐이라 표본이 적어 통계적으로 쏠림. (동전 4번 vs 10000번 던지기) +- → 해결 방향: **링 위 점의 개수를 늘린다** = 가상 노드(항목 5). + +### 실제 세계 +- 기본형을 그대로 쓰는 프로덕션은 없음. Cassandra, DynamoDB 등 모두 가상 노드(또는 변형) 사용. + +### 차주 설계와의 연결 +"서버를 링에 올리면 됨"에서 끝내면 감점. "기본형은 파티션·키 불균등이 있어 가상 노드로 보완"까지 말해야 완성. + +### 내 말로 정리 +> 가상 노드 없이 올리면 서버 간 간격(파티션)이 크고 작고 불균형해지고, 그에 따라 저장되는 키도 한쪽에 몰리거나 너무 적어져 불균형해진다. 원인은 서버 수가 너무 적어서 해시의 균등 분포 성질이 발현되지 않기 때문이다. 그래서 해시 링 위에 `서버IP#1, 서버IP#2, ...`처럼 가상 노드를 여러 개 만들고(가상노드 → 실제 서버 매핑), 키가 어떤 가상 노드를 만나든 해당 실제 서버에 저장되게 해서 점 개수를 늘려 고르게 분산시킨다. + +--- + +## 5. 가상 노드 & 표준편차 트레이드오프 (+가중치) +- **출처**: 책 본문 + name-drop(표준편차 `[2]`) 심화 + 관련 심화(가중치) + +### 무엇인가 +- 실제 서버 하나를 링 위 한 점이 아니라 여러 점으로 표현: `hash("서버0#0")`, `hash("서버0#1")`, ... 각각이 가상 노드. 모두 같은 실제 서버로 매핑. +- 조회: 키가 시계방향으로 만난 가상 노드가 가리키는 실제 서버에 저장. +- 효과: 링 위 점이 많아져 파티션이 잘게 쪼개지고, "표본이 적어 쏠린다"(항목 4) 문제가 통계적으로 해소됨. + +### 표준편차의 의미 +- 표준편차 = 각 서버가 담당하는 키 양(부하)이 평균에서 얼마나 퍼져 있는지. 0이면 완벽 균등. +- 책 `[2]` 인용: **가상 노드 100개 → 표준편차 ~10%, 200개 → ~5%.** 가상 노드↑ → 표준편차↓ → 부하 더 균등. + +### 트레이드오프 (무한정 안 늘리는 이유) +| 늘리면 좋은 점 | 늘리면 나쁜 점 | +|---|---| +| 표준편차↓ (부하 균등), 재배치도 고르게 | 위치 정보 저장 **메모리↑** | +| | 링 자료구조 커져 조회 O(log n)의 n↑ (단, log이라 영향 작음 — 주 비용은 메모리) | +| | 분산 환경에서 노드 간 공유·전파할 **메타데이터↑**, 운영 오버헤드↑ | + +> **💬 [스터디 토론거리] Cassandra의 vnode 기본값 변화** +> **Cassandra는 서버당 가상 노드(vnode) 기본값을 초기 `256개`에서 최근 `16개`로 낮췄다.** +> 이유: 분산 시스템에서는 vnode 토큰 정보를 **모든 노드가 gossip으로 공유·동기화**해야 하는데, vnode가 많을수록 전파할 메타데이터와 운영 비용이 커진다. **"균등성을 조금 손해 보더라도 운영/메타데이터 비용을 줄이자"**는 결정. → 트레이드오프의 "비용"에는 메모리뿐 아니라 **분산 환경의 메타데이터·운영 비용**이 포함된다는 실제 사례. + +### 가중치(weighted) 가상 노드 (책에 없음) +- 책은 모든 서버 동일 성능 가정. 현실은 스펙이 다름 → **성능 좋은 서버에 가상 노드를 더 많이 배정**. +- 예: 서버 A(고성능) vnode 300개, 서버 B 100개 → A가 링 공간 3배 담당 → 키도 3배. "가상 노드 수 = 서버 가중치." +- DynamoDB, Cassandra가 노드 용량에 따라 토큰(=vnode) 수를 조절하는 방식으로 구현. + +### 차주 설계와의 연결 +- "vnode 몇 개?" → "많을수록 균등하나 메모리·메타데이터 비용. 보통 수백 개 수준에서 타협." +- "서버 성능 다르면?" → "가중치만큼 vnode 수 차등 배정." + +### 내 말로 정리 +> 가상 노드를 늘리면 표준편차가 낮아진다. 가상 노드가 많을수록 파티션이 균일해지고 키가 서버에 고르게 저장돼 한 서버에 몰리는 걸 방지한다. 하지만 무한정 늘리지 않는 이유는 그만큼 메모리가 많이 필요하기 때문이다(조회는 O(log N)이라 계산 시간 증가는 미미하고, 메모리가 주된 비용 — 분산 환경에선 메타데이터/운영 비용도). 그래서 스펙에 맞게 가상 노드 수를 정하고, 서버 성능이 다르면 성능이 좋은 서버에 가중치를 더 줘서 가상 노드가 더 많이 생기게 한다. + +--- + +## 6. 안정 해시의 이점 + 핫스팟/편향 키의 한계·보완 +- **출처**: 책 본문(마치며) + name-drop(핫스팟) 심화 + +### 이점 3가지 (책) +1. **재배치 최소화**: 서버 추가/삭제 시 `k/n`개 키만 이동. +2. **균등 분산**: 데이터가 고르게 퍼져 수평 확장 쉬움 (가상 노드로 달성). +3. **핫스팟 완화**: 특정 샤드 집중을 줄임. + +### 핫스팟의 두 종류 (핵심 — 책이 얼버무린 부분) +- **① 여러 인기 키가 우연히 한 서버에 몰림 → 안정 해시가 해결 ✅**: 케이티 페리·저스틴 비버가 각각 다른 키면 안정 해시(+vnode)가 다른 서버로 흩뿌림. +- **② 단 하나의 키가 초인기 → 해결 불가 ❌**: `hash("katy_perry")`는 항상 같은 서버로 감(결정적). vnode를 늘려도 **하나의 키는 하나의 서버**. 단일 초핫키(single hot key)는 안정 해시로 분산 불가. +- 결론: 안정 해시는 **"여러 키의 분포"는 고르게 하지만 "한 키의 집중"은 못 푼다.** + +### 단일 핫키 보완책 (안정 해시 "바깥"의 기법) +1. **복제 + 읽기 분산**: 핫키를 이웃 서버 N대에 복제(Dynamo preference list 방식), 읽기를 복제본에 랜덤 분산. 쓰기 일관성 비용. +2. **키 분할(salting)**: `katy_perry#0..#9`로 이름을 쪼개 링 전체에 흩어 저장, 읽을 때 랜덤 salt 선택. 복제와 달리 이웃이 아니라 **넓게** 분산. 어떤 키가 핫한지 알아야 하고 갱신 시 전부 반영. +3. **캐시 계층**: 앞단 로컬/전용 캐시로 흡수. **TTL 1초면 원 서버는 1/TTL = 초당 1번만** 받음(만료 직후 첫 요청만 미스). TTL = 원 서버 부하 ↔ 신선도 트레이드오프. (앱 서버 N대면 원 서버는 N/TTL) +4. **CDN/엣지**: 콘텐츠성 핫키는 사용자 근처 엣지에서 응답, 원본 차단. 저장소에 닿기 전 앞단에서 처리. + +> 방법1 vs 2: 둘 다 복사본을 여러 서버에 저장하지만, 복제는 안정 해시가 정한 **이웃 서버**에 모으고, 키 분할은 **키 이름을 바꿔 링 전체에 넓게** 흩뿌린다. + +### 차주 설계와의 연결 +"안정 해시로 핫스팟 해결됨"이라 단정하면 위험. "여러 키 몰림은 완화, 단일 초핫키는 복제·키 분할·캐싱 필요"로 구분해서 답할 것. + +### 내 말로 정리 +> 안정 해시의 이점은 서버 추가/삭제 시 키 재배치 최소화, 가상 노드로 데이터 고르게 분포, 핫스팟 완화(여러 핫키가 같은 서버에 몰리는 걸 흩뿌려 완화)다. 하지만 해결 못 하는 건 핫키 그 자체 — 키 하나에 요청이 너무 많으면 그 키는 항상 같은 서버로 가니 안정 해시로는 못 푼다. 이때는 복제(데이터를 여러 서버에 저장), 키 분할(키를 #1,#2로 쪼개 여러 서버로), 캐시 계층(TTL로 원 서버 부하를 초당 1번 수준으로), CDN 엣지 캐싱 같은 별도 기법으로 대응한다. + +--- + +## 7. 안정 해시의 기원과 이론 (Karger 1997, Akamai) +- **출처**: 참고문헌 `[1]` 위키피디아, `[6]` 스탠퍼드 CS168 + +### 탄생 배경 +- 1997년 MIT David Karger 등의 논문 *"Consistent Hashing and Random Trees"*에서 제안. +- 원래 도메인 = **분산 웹 캐싱**(책의 캐시 서버 예시와 같은 도메인). "DB 샤딩"보다 웹 캐싱이 먼저였을 뿐 본질은 동일. +- 목적: **많은 URL(키)을 많은 캐시 서버에 고르게 분산**하고, 캐시가 수시로 들락날락해도 "어느 URL이 어느 캐시에 있는지" 매핑이 거의 안 흔들리게. (`hash % N`은 N이 클라이언트마다 달라 분산·변경에 취약) +- ⚠️ 주의: 안정 해시는 "**여러 키의 분산**"을 푼 것이지 "한 키의 폭주 분할"이 아님(단일 핫키는 복제로 — 항목 6). + +### 이론적 보장 +- **Balance(균형)**: 키가 서버들에 대략 고르게 분포 (vnode로 강화). +- **Monotonicity(단조성)**: 서버 추가 시 키는 **오직 "기존 서버 → 새 서버" 방향으로만** 이동. "기존→기존" 뒤섞임은 절대 없음. → 이것이 재배치를 `k/n`으로 묶는 근거. (`hash%N`은 기존끼리 마구 뒤섞여서 대부분 이동) +- **`k/n`**: 키 k개, 슬롯 n개일 때 크기 변경 시 평균 k/n개만 재배치. 예: 키 1000만, 서버 10대 → 추가 시 평균 100만개(=한 서버 분량)만. + +### 실제 세계 — Akamai +- Karger 등이 안정 해시를 기반으로 1998년 **Akamai**(세계 최대급 CDN) 창업. 안정 해시가 CDN 라우팅 핵심 기술. +- 이론 → 거대 인프라로 이어진 대표 사례. 책 마무리에 Akamai가 실사용 예로 나오는 이유(원조). + +### 차주 설계와의 연결 +"왜 k/n만 재배치되나?" → 단조성으로 설명. "원래 CDN(Akamai)에서 나온 기술" 배경은 8·14장 CDN 설계와 연결. + +### 내 말로 정리 +> 안정 해시는 분산 웹 캐싱에서 나왔다(맞음). 단, 하나의 키 폭주를 분산하려는 게 아니라 많은 키를 여러 서버에 안정적으로 분산하고, 서버가 추가/삭제돼도 매핑이 흔들리지 않게 하려는 것이다. +> 단조성이란 서버가 추가/삭제될 때 키가 "기존 서버 → 새 서버"(또는 삭제된 서버 → 남은 서버) 방향으로만 이동하고, 멀쩡한 기존 서버들끼리는 키가 뒤섞이지 않는 성질이다. 그래서 모든 기존 키가 이동하지 않고 k/n만 이동한다. +> Akamai는 Karger가 안정 해시를 핵심 기술로 창업한 CDN 회사다. + +--- + +## 8. 안정 해시의 대안·발전형 (Rendezvous, Jump, Bounded-load) +- **출처**: 관련 심화 (책에 없음) + +### ① Rendezvous Hashing (HRW, Highest Random Weight) +- 링을 안 씀. 키마다 모든 서버에 대해 `score = hash(key+server)` 계산 → **최고점 서버**가 담당. +- 장점: 가상 노드 불필요 → **메모리 거의 안 씀**, 단조성 성립, 분산 균등. +- 단점: 조회 **O(n)** (키마다 전 서버 점수 계산). 서버 수천이면 느림. +- 언제: 서버 수가 적당히 작을 때(수십~수백), 메모리 귀할 때. 실제: MS CARP, Ceph 일부, CDN. + +### ② Jump Consistent Hash (Google, 2014) +- 수학 함수 하나. `jump_hash(key, num_buckets)` → 0~N-1 버킷 번호. **링/서버목록 저장 불필요, 메모리 0, 빠름, 표준편차 ~0.** +- 핵심 원리: "N 넣고 새로 계산"(hash%N)이 아니라 **"버킷이 1개→2개→...→N개로 자란 과정을 재현".** 버킷 j→j+1로 늘 때 정확히 `1/(j+1)`만 새 버킷으로 점프(키를 시드로 한 결정적 난수). → 설계상 균등(표준편차~0), 끝 버킷 추가 시 그 몫만 이동(단조성), 기존끼리 뒤섞임 없음. +- **치명적 제약**: 버킷은 **끝에서만** 추가/제거 가능. 중간 5번만 제거 불가. → 임의 서버가 랜덤하게 죽는 캐시엔 부적합, **논리적 샤드 개수 조절**에 적합. +- 실제: Google 내부 스토리지, 샤딩 시스템. + +### ③ Bounded-Load Consistent Hashing (Google, 2017) +- 안정 해시(+vnode) **위에 얹는 규칙**(vnode 대체 아님, 보완). 각 서버에 정원 = `(1+ε) × 평균부하` 상한. +- 키를 시계방향 첫 서버에 넣되, **그 서버가 정원 꽉 차면 다음 서버로**(해시테이블 open addressing 유사). +- 효과: "평균적 균등"이 아니라 **"어떤 서버도 (1+ε)배 못 넘음"의 최악 상한 보장.** 특히 **키마다 부하(무게)가 다른** 동적 쏠림에 강함(vnode는 키 개수만 고르게 나눔). +- 실제: **Google Cloud LB, Vimeo.** 로드밸런싱에 인기. + +### 한눈에 비교 +| 방식 | 메모리 | 조회 | 임의 서버 제거 | 특징 | +|---|---|---|---|---| +| 링+vnode(책) | 높음 | O(log n) | ✅ | 표준·범용 | +| Rendezvous | 매우 낮음 | O(n) | ✅ | 서버 적을 때 | +| Jump | 0 | 빠름 | ❌(끝만) | 논리 샤드 | +| Bounded-load | 링+α | O(log n)+ | ✅ | 부하 상한 보장 | + +### 차주 설계와의 연결 +"링 말고 다른 방법?" → Rendezvous(메모리 절약), Jump(논리 샤드), Bounded-load(부하 상한). 로드밸런서엔 Bounded-load, 서버 적은 캐시엔 Rendezvous. + +### 내 말로 정리 +> Rendezvous는 서버 수가 적을 때 좋다(가상 노드 없이 메모리 절약, 대신 조회 O(n)). +> Jump Hash는 특정 수학 함수에 키와 버킷 수를 넣으면 버킷 번호가 나오는 방식인데, N을 넣고 새로 계산하는 게 아니라 버킷이 1개→N개로 자란 과정을 재현하기 때문에 균등(표준편차~0)하면서 끝 버킷 추가 시에만 최소 이동이 일어난다. 대신 중간 서버 제거가 안 돼서 논리적 샤드 개수 조절에만 쓴다. +> Bounded-load는 가상 노드의 대체가 아니라 안정 해시 위에 "어떤 서버도 평균의 (1+ε)배를 넘지 못한다(넘치면 다음 서버로)"는 정원 상한을 얹어 최악의 과부하를 막는 방법이다. 완벽 균등이 아니라 상한 보장이며, 키마다 부하가 다른 동적 쏠림에 특히 강하다. + +---