본문 바로가기

Computer Science

[안전해시] 안정 해시 심층 가이드: 넷플릭스가 1초 만에 전 세계 2억 명의 요청을 처리하는 비결 글을 읽고

https://maily.so/devpill/posts/w6ovlgxyzk5?from=email&mid=2qzp1e8qez4

이 글을 읽고 드는 궁금증에 대해서 정리했어요.

 

안정 해시는 키를 노드 클러스터에 균일하게 분산시키는 기술입니다.

🟦 인프라에 대해서 잘 모르는데 노드, 키, 클러스터링은 무슨 의미지?

  • 노드라는건 1개의 서버이고 키는 표식인가? 클러스터링은 부하를 분산시키는 역할인가?

 

일관성 해시를 활용한 시스템은 규모가 커질수록 더욱 안정적으로 동작합니다.

🟦 내가 아는 해시처리는 암호화 인데 다른 의미인가?

  • 값이 바뀌는게 아니라 해시 테이블로 들어가서 해싱이 되면 input, output이 다른거 아닐까? 그러면 왜 해시처리를 하는거지? 어처피 값을 복화하 해야되는거 아닌가?
  • 내가아는 해시랑 안정해시의 해시랑 다른건가?

위 질문에 대한 답을 gpt와 같이 찾아봤어요.

 

 

노드, 키, 클러스터링이란?

  1. 노드(Node)
    • 데이터(키)를 저장하는 물리적 혹은 논리적 서버를 의미해요.
    • 예: “서버 A, 서버 B, 서버 C”라고 할 때 각각이 노드에 해당해요.
  2. 키(Key)
    • 데이터를 구분하는 식별자(ID)를 말해요.
    • 예: 특정 사용자의 회원번호, 특정 파일의 이름이나 URL 등
  3. 클러스터링(Clustering)
    • 여러 개의 서버(노드)를 묶어서 하나의 큰 시스템처럼 구성하는 것을 의미해요.
    • 예: Netflix가 전 세계에 분산된 여러 서버로 이루어진 클러스터를 운영하고 있는 것

왜 대용량 분산 시스템에서 ‘안정 해시(Consistent Hashing)’라는 방식을 사용할까?

  • 분산 시스템에서 서버가 늘어나거나 줄어들 때, 재배치되는 데이터(키)를 최소화하기 위해서예요.
  • 일반적인 해싱(예: index = hash(key) mod 서버개수)을 사용하면, 서버가 1대만 추가되어도 전체 데이터 재분배가 크게 바뀌죠.
  • 반면, 안정 해시에서는 새로운 서버가 추가되거나 기존 서버가 제거되어도 적은 수의 키만 움직이게 돼요.

해싱 처리는 값이 바뀌는 게 아닌가?

  • 암호화 관점의 해시(예: SHA-256)와 달리, 안정 해시에서의 해시는 데이터를 안전하게 감추려는 게 아니라, 노드(서버) 위치를 결정하려는 거예요.
    • 암호화를 위해 쓰는 해시는 정보 자체를 보호하지만, 여기서는 데이터를 보관할 위치를 결정하기 위한 지도 역할을 한다고 이해하면 됩니다
  • 입력(key)을 받아서 일정 범위(예: 0 ~ 2^32) 안에서 해시값을 구한 뒤, 그 값에 따라 어느 노드에 속하는지 빠르게 판단하게 돼요.
  • 여기서 복호화(Decryption) 같은 개념은 없어요. 왜냐하면 이 해시값은 ‘분산을 위한 좌표’ 같은 것이고, 그 자체를 복호화해 원본을 되돌릴 필요가 없기 때문이에요.

간단한 예시로 이해해보기

  1. 문자열을 해시하는 간단한 함수(예: hash(key))를 정의해요.
  2. 여러 노드(서버)가 있을 때, 각 노드도 같은 방식으로 해시하여 링(ring) 위에 놓아둬요.
  3. 키도 해시를 해서, 링(ring) 위 어디쯤 위치하는지 알아요.
  4. 키가 위치한 지점에서 시계 방향으로 돌다가 처음 만나는 노드가 그 키를 맡게 되는 거예요.

아래는 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 로 갈 수 있어요.

 

결론

🟦 안정해시가 뭔지 알아봤고 어떻게 인프라가 구성되는지 생각해봤어요.

- 노드, 키, 클러스터링 에 대해서도 알아봐야겠어요.

- 해시의 역사, 기능, 역할들도 다시 공부해 봐야겠네요. 암호화에 쓰이는 것 밖에 몰랐는데 다른 방법을 알아서 흥미로웠어요.

 

🟦 이런 구성을 처음 생각한 사람이 대단한거 같아요.

위 구조가 개선되야 할 점이라고 생각되는거는 해시값 순서로 링을 만드는 부분이에요. 노드가 많아지면 많아질 수록 시계방향의 링을 만드는데 시간이 걸릴 것 같아요.

 

 

728x90