https://maily.so/devpill/posts/w6ovlgxyzk5?from=email&mid=2qzp1e8qez4
이 글을 읽고 드는 궁금증에 대해서 정리했어요.
안정 해시는 키를 노드 클러스터에 균일하게 분산시키는 기술입니다.
🟦 인프라에 대해서 잘 모르는데 노드, 키, 클러스터링은 무슨 의미지?
- 노드라는건 1개의 서버이고 키는 표식인가? 클러스터링은 부하를 분산시키는 역할인가?
일관성 해시를 활용한 시스템은 규모가 커질수록 더욱 안정적으로 동작합니다.
🟦 내가 아는 해시처리는 암호화 인데 다른 의미인가?
- 값이 바뀌는게 아니라 해시 테이블로 들어가서 해싱이 되면 input, output이 다른거 아닐까? 그러면 왜 해시처리를 하는거지? 어처피 값을 복화하 해야되는거 아닌가?
- 내가아는 해시랑 안정해시의 해시랑 다른건가?
위 질문에 대한 답을 gpt와 같이 찾아봤어요.
노드, 키, 클러스터링이란?
- 노드(Node)
- 데이터(키)를 저장하는 물리적 혹은 논리적 서버를 의미해요.
- 예: “서버 A, 서버 B, 서버 C”라고 할 때 각각이 노드에 해당해요.
- 키(Key)
- 데이터를 구분하는 식별자(ID)를 말해요.
- 예: 특정 사용자의 회원번호, 특정 파일의 이름이나 URL 등
- 클러스터링(Clustering)
- 여러 개의 서버(노드)를 묶어서 하나의 큰 시스템처럼 구성하는 것을 의미해요.
- 예: Netflix가 전 세계에 분산된 여러 서버로 이루어진 클러스터를 운영하고 있는 것
왜 대용량 분산 시스템에서 ‘안정 해시(Consistent Hashing)’라는 방식을 사용할까?
- 분산 시스템에서 서버가 늘어나거나 줄어들 때, 재배치되는 데이터(키)를 최소화하기 위해서예요.
- 일반적인 해싱(예: index = hash(key) mod 서버개수)을 사용하면, 서버가 1대만 추가되어도 전체 데이터 재분배가 크게 바뀌죠.
- 반면, 안정 해시에서는 새로운 서버가 추가되거나 기존 서버가 제거되어도 적은 수의 키만 움직이게 돼요.
해싱 처리는 값이 바뀌는 게 아닌가?
- 암호화 관점의 해시(예: SHA-256)와 달리, 안정 해시에서의 해시는 데이터를 안전하게 감추려는 게 아니라, 노드(서버) 위치를 결정하려는 거예요.
- 암호화를 위해 쓰는 해시는 정보 자체를 보호하지만, 여기서는 데이터를 보관할 위치를 결정하기 위한 지도 역할을 한다고 이해하면 됩니다
- 입력(key)을 받아서 일정 범위(예: 0 ~ 2^32) 안에서 해시값을 구한 뒤, 그 값에 따라 어느 노드에 속하는지 빠르게 판단하게 돼요.
- 여기서 복호화(Decryption) 같은 개념은 없어요. 왜냐하면 이 해시값은 ‘분산을 위한 좌표’ 같은 것이고, 그 자체를 복호화해 원본을 되돌릴 필요가 없기 때문이에요.
간단한 예시로 이해해보기
- 문자열을 해시하는 간단한 함수(예: hash(key))를 정의해요.
- 여러 노드(서버)가 있을 때, 각 노드도 같은 방식으로 해시하여 링(ring) 위에 놓아둬요.
- 키도 해시를 해서, 링(ring) 위 어디쯤 위치하는지 알아요.
- 키가 위치한 지점에서 시계 방향으로 돌다가 처음 만나는 노드가 그 키를 맡게 되는 거예요.
아래는 TypeScript로 가상 노드와 함께 간단히 표현한 예시예요.
import { log } from 'node:console';
// 간단한 해시 함수 (문자열 -> 0~999 범위)
function simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash * 31 + str.charCodeAt(i)) % 1000;
}
return hash;
}
class Node {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 실제 물리 노드(서버)
const physicalNodes = [
new Node('NodeA'),
new Node('NodeB'),
new Node('NodeC')
];
// 가상 노드 (각 물리 노드에 가상 노드를 여러 개 할당)
const virtualNodes: { node: Node; hashValue: number }[] = [];
// 예: 각 노드마다 3개씩 가상 노드를 만든다고 가정
physicalNodes.forEach((node) => {
for (let i = 1; i <= 3; i++) {
const virtualNodeName = `${node.name}-V${i}`;
const hashValue = simpleHash(virtualNodeName);
virtualNodes.push({ node, hashValue });
}
});
// 링(ring)에 해당하는 해시값 순서로 정렬
virtualNodes.sort((a, b) => a.hashValue - b.hashValue);
// ring이 어떻게 나오는지 확인
for (const node of virtualNodes) {
console.log('virtual', node.node, node.hashValue);
}
// 키(데이터)를 어느 노드가 맡는지 찾는 함수
function getAssignedNode(key: string): Node {
const hashOfKey = simpleHash(key);
log('hashOfKey:', hashOfKey); // target key값 확인
// 링 상에서 시계 방향으로 가장 가까운 가상 노드를 찾음
for (const vNode of virtualNodes) {
if (hashOfKey <= vNode.hashValue) {
return vNode.node;
}
}
// 링의 끝까지 못 찾았으면, 맨 앞에 있는 노드가 맡음
return virtualNodes[0].node;
}
// 예시: "User123"라는 키를 어느 노드가 맡는지 확인
const myKey = 'User123';
const assignedNode = getAssignedNode(myKey);
console.log(`Key "${myKey}" => Node: ${assignedNode.name}`);
- 이 코드에서 simpleHash 함수로 가상 노드와 키를 모두 해시해요.
- 링(ring) 위에서 시계 방향으로 가장 가까운 가상 노드를 찾고, 그 물리 노드(서버)에 키를 할당해요.
- 이런 식으로 분산을 하면, 노드가 추가되거나 삭제돼도 소수의 키만 재할당하게 되어 안정적이고, 확장성이 좋아요.
gpt-o1이 좋긴 좋네요. 질문의 의미와 예시 코드도 줬어요. 예제 코드를 돌리면서 콘솔로 확인해보니 이해가 쉽게됬어요.
virtual Node {
name: "NodeB",
} 136
virtual Node {
name: "NodeB",
} 137
virtual Node {
name: "NodeB",
} 138
virtual Node {
name: "NodeA",
} 345
virtual Node {
name: "NodeA",
} 346
virtual Node {
name: "NodeA",
} 347
virtual Node {
name: "NodeC",
} 927
virtual Node {
name: "NodeC",
} 928
virtual Node {
name: "NodeC",
} 929
hashOfKey: 735
Key "User123" => Node: NodeC
콘솔의 결과값이 이렇게 나오더라고요. virtual 이 3개씩 있고 그중에 걸리면 들어가는 식이니 분산된 환경에서 어느 노드에 들어가야하는지 빨리 찾을 수 있을 것 같아요
링(ring) 위에서 시계 방향으로 가장 가까운 가상 노드를 찾고, 그 물리 노드(서버)에 키를 할당해요. 이런 식으로 분산을 하면, 노드가 추가되거나 삭제돼도 소수의 키만 재할당하게 되어 안정적이고, 확장성이 좋아요.
이 문장이 어떤 의미인지 약간인지 감이 와요. 가장 가까운 가상노드 를 찾고 그것에 속하는 물리노드 에 할당된다. ⇒ 927,928,929 의 가상노드가 가까우니 할당되고 NodeC 물리서버에 할당된 것이에요.
노드가 추가되거나 삭제돼도 소수의 키만 재할당하게 되요. ⇒ NodeD 를 추가하고 새로운 key 를 넣는다고 가정해볼께요.
virtual Node {
name: "NodeB",
} 136
virtual Node {
name: "NodeB",
} 137
virtual Node {
name: "NodeB",
} 138
virtual Node {
name: "NodeA",
} 345
virtual Node {
name: "NodeA",
} 346
virtual Node {
name: "NodeA",
} 347
virtual Node {
name: "NodeD",
} 718
virtual Node {
name: "NodeD",
} 719
virtual Node {
name: "NodeD",
} 720
virtual Node {
name: "NodeC",
} 927
virtual Node {
name: "NodeC",
} 928
virtual Node {
name: "NodeC",
} 929
hashOfKey: 735
Key "User123" => Node: NodeC
hashOfKey: 129
Key "MINHO" => Node: NodeB
NodeD 라는 물리노드가 추가되므로서 가상노드 해시값이 718, 719, 720 으로 범위가 확장됬어요. 그리소 새로운 키 MINHO 는 가상노드의 해시값만 보고 NodeB 로 갈 수 있어요.
결론
🟦 안정해시가 뭔지 알아봤고 어떻게 인프라가 구성되는지 생각해봤어요.
- 노드, 키, 클러스터링 에 대해서도 알아봐야겠어요.
- 해시의 역사, 기능, 역할들도 다시 공부해 봐야겠네요. 암호화에 쓰이는 것 밖에 몰랐는데 다른 방법을 알아서 흥미로웠어요.
🟦 이런 구성을 처음 생각한 사람이 대단한거 같아요.
위 구조가 개선되야 할 점이라고 생각되는거는 해시값 순서로 링을 만드는 부분이에요. 노드가 많아지면 많아질 수록 시계방향의 링을 만드는데 시간이 걸릴 것 같아요.