본문으로 바로가기
제품데스크톱 도구TASTECAD도면 확인TASTEFILES파일 관리TASTEFTP파일 전송TASTESSH원격 작업공간TASTEZIP압축과 해제개발 도구TASTECODERust 통합 개발 환경TASTESTUDIO통합 개발·테스트 환경TASTEETL데이터 통합(ETL)서버TASTEREMOTE원격 데스크톱 서버TASTEGEO공간정보 서버TASTEMAIL메일 작업공간시작하기다운로드문서구매모든 제품
마켓플레이스
소식
개발소식오늘의 개발 소식개발 문서프로그래밍 언어RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron알고리즘알고리즘 기초알고리즘 핵심알고리즘 심화실무 알고리즘
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스커뮤니티 안내
협업기술 전수홈페이지 구축데스크톱 앱 구축릴리스·결제 파이프라인판매 파트너문의
서비스원격 데스크톱설치 패키지 커스텀
TASTEDEV
제품

데스크톱 도구

TASTECAD복잡한 도면을 가볍게 열다TASTEFILES필요한 파일만, 가장 빠르게TASTEFTP파일이 오가는 가장 단정한 길TASTESSH서버와 나 사이, 더 단단한 연결TASTEZIP더 빠르게 묶고, 더 안전하게 풀다

개발 도구

TASTECODERust 앱을 편집 · 빌드 · 디버깅 · 테스트까지 한 창에서TASTESTUDIO편집부터 원격 테스트와 승인 기반 AI 수정까지TASTEETL값을 보며 데이터를 옮깁니다

서버

TASTEREMOTE우리 회사 서버로 운영하는 원격 데스크톱TASTEGEOGeoServer 호환 공간정보 서버를 Rust로 다시 세웁니다TASTEMAIL메일의 주도권을 다시 내 손에

시작하기

다운로드모든 제품 최신 판문서제품 사용법구매구독 · 영구 라이선스모든 제품모든 제품 한눈에
모든 제품
마켓플레이스소식
개발

소식

오늘의 개발 소식언어 · 프레임워크 · GitHub · AI 공식 발표개발 문서언어 · 프레임워크 · 알고리즘 안내서

프로그래밍 언어

RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart

프레임워크

프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular
서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express
앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron

알고리즘

알고리즘 기초복잡도 분석 · 정렬 · 탐색 · 기본 자료구조 · 트리와 힙알고리즘 핵심그래프 탐색 · 최단 경로와 최소 신장 트리 · 동적 계획법 · 탐욕 알고리즘 · 분할 정복 · 백트래킹알고리즘 심화문자열 알고리즘 · 수학과 정수론 · 비트 연산 · 구간 트리 · 유니온 파인드실무 알고리즘코딩 면접 풀이 전략 · LRU 캐시 · 레이트 리미터 · 일관된 해시
개발 문서 전체 보기
커뮤니티
프로젝트빌더와 작업 발견개발 로그결정과 시행착오프롬프트재사용 가능한 지시AI 도구 이슈Claude · Codex · Gemini 문제와 우회법쇼케이스완성된 결과커뮤니티 안내공개하고 함께 만드는 방식
협업
기술 전수Rust·egui 네이티브 앱 만들기홈페이지 구축10개 언어, 계정, 문서·다운로드데스크톱 앱 구축Rust·egui, WebView 없이릴리스·결제 파이프라인서명 · MSI·deb·rpm·AppImage · 해시판매 파트너소개하고 결제액의 30% 받기문의구축 · 제휴 · 투자 문의
서비스
원격 데스크톱브라우저로 내 컴퓨터에 접속설치 패키지 커스텀필요한 기능만, 설정은 미리
구매
KO
한국어EnglishDeutschEspañolFrançaisItalianoPortuguês (Brasil)日本語简体中文繁體中文
로그인
로그인
TASTEDEV

작은 도구부터, 더 좋은 개발까지.

support@tastedev.netTASTEDEV 후원하기
제품제품군구매문서다운로드마켓플레이스고객 지원
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스
정책개인정보처리방침이용약관구독 약관환불 정책커뮤니티 운영 정책
계정로그인가입하기
방문자
오늘
012345678901234567890123456789012345678901234567890123456789
어제
012345678901234567890123456789012345678901234567890123456789
누적 조회
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
상호
지엑스소프트
대표자
최영구
사업자등록번호
853-44-00368
주소
인천광역시 미추홀구 경인로 351 9층 V24
전화
+82-10-4404-1523
이메일
whitecalvin@tastedev.net
개인정보 보호책임자
최영구

© 2026 TASTEDEV제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

일관된 해시 문서
일관된 해시
일관된 해시의 개념동작 원리구현복잡도연습 문제실무 활용
토론

일관된 해시 안내서 · 5/6

연습 문제

아래 문제는 모두 구현 장의 hash64와 HashRing(정렬된 points, 위치에서 노드로 가는 owner)을 그대로 쓴다고 가정합니다. 먼저 스스로 풀어 보고 풀이와 비교해 보세요.

문제 1. 노드별 몫 계산하기

링이 주어졌을 때 각 노드가 링 전체(2^64) 중 몇 비율을 맡는지 계산하세요. 가상 노드 수를 1, 10, 100, 1000으로 바꿔 가며 가장 큰 몫과 가장 작은 몫이 평균과 얼마나 차이 나는지 확인합니다.

접근: 각 점은 바로 앞 점 다음부터 자기 위치까지를 맡습니다. 정렬된 점을 돌며 (p - 앞 점) mod 2^64를 그 점 주인의 몫에 더하면 됩니다. 인덱스 0의 앞 점은 마지막 점이며, Python의 pts[-1]이 이 감싸기를 자연스럽게 처리합니다. 점이 하나뿐이면 차가 0이 되므로 따로 처리합니다.

RING_SIZE = 1 << 64

def ownership(ring: HashRing) -> dict[str, float]:
    pts = ring.points
    if len(pts) == 1:
        return {ring.owner[pts[0]]: 1.0}
    share: dict[str, int] = {}
    for i, p in enumerate(pts):
        arc = (p - pts[i - 1]) % RING_SIZE   # i == 0 wraps to the last point
        node = ring.owner[p]
        share[node] = share.get(node, 0) + arc
    return {node: s / RING_SIZE for node, s in share.items()}

for v in (1, 10, 100, 1000):
    r = HashRing(vnodes=v)
    for n in range(10):
        r.add(f"node-{n}")
    s = ownership(r)
    print(v, round(max(s.values()) * 10, 2), round(min(s.values()) * 10, 2))

몫에 노드 수 10을 곱해 평균을 1로 맞췄습니다. 가상 노드가 늘수록 최댓값과 최솟값이 1에 가까워지는 것을 볼 수 있습니다.

문제 2. 가용 영역이 겹치지 않는 복제본

노드마다 속한 가용 영역(zone)이 있습니다. 키의 복제본 R개를 고르되, 서로 다른 물리 노드이면서 영역도 모두 달라야 합니다. 조건을 만족하는 노드가 부족하면 찾은 만큼만 돌려줍니다.

접근: 키 위치에서 시계 방향으로 점을 하나씩 걸으며, 이미 고른 노드이거나 이미 쓴 영역이면 건너뜁니다. 링을 한 바퀴 돌면 멈춥니다.

import bisect

def zone_replicas(ring: HashRing, zone_of: dict[str, str], key: str, n: int) -> list[str]:
    pts = ring.points
    start = bisect.bisect_left(pts, hash64(key))
    chosen: list[str] = []
    zones: set[str] = set()
    for step in range(len(pts)):
        node = ring.owner[pts[(start + step) % len(pts)]]
        if node in chosen or zone_of[node] in zones:
            continue
        chosen.append(node)
        zones.add(zone_of[node])
        if len(chosen) == n:
            break
    return chosen

zone_of = {"a1": "az-1", "a2": "az-1", "b1": "az-2", "b2": "az-2", "c1": "az-3"}
ring = HashRing(vnodes=50)
for node in zone_of:
    ring.add(node)
print(zone_replicas(ring, zone_of, "order:1001", 3))  # one node from each zone

문제 3. 노드 추가 시 이전 계획 세우기

새 노드를 추가하기 전에, 어떤 해시 구간을 어느 기존 노드에서 가져와야 하는지 목록으로 뽑으세요. 결과는 (시작, 끝, 원래 주인) 튜플이며 구간은 시작 초과, 끝 이하입니다.

접근: 새 노드를 넣은 링에서 새 노드의 각 점 p에 대해, 바로 앞 점부터 p까지가 넘어오는 구간입니다. 원래 주인은 "추가 전 링에서 p를 조회한 결과"입니다. 새 노드의 점이 연달아 있어도 이 규칙은 그대로 맞습니다.

def migration_plan(ring: HashRing, new_node: str) -> list[tuple[int, int, str]]:
    old_points = list(ring.points)
    old_owner = dict(ring.owner)
    ring.add(new_node)
    pts = ring.points
    plan = []
    for i, p in enumerate(pts):
        if ring.owner[p] != new_node:
            continue
        j = bisect.bisect_left(old_points, p) % len(old_points)
        plan.append((pts[i - 1], p, old_owner[old_points[j]]))
    return plan

def in_range(h: int, start: int, end: int) -> bool:
    if start < end:
        return start < h <= end
    return h > start or h <= end   # the range wraps past 0

검증은 키 수천 개를 추가 전후로 조회해, 주인이 바뀐 키가 정확히 계획의 구간 안에 있고 원래 주인도 일치하는지 확인하면 됩니다.

문제 4. 가중치 랑데부 해시

노드마다 용량 가중치가 있습니다. 키가 가중치에 비례해 배정되고, 노드 하나를 빼면 그 노드의 키만 움직이는 랑데부 해시를 만드세요.

접근: 노드마다 키와 섞은 해시로 0과 1 사이의 균등한 수 u를 만들고 점수 -w / ln(u)가 가장 큰 노드를 고릅니다. 이 점수는 가중치 w에 비례하는 확률로 1등이 됩니다. u가 정확히 0이나 1이 되지 않도록 상위 53비트에 0.5를 더해 나눕니다.

import math
from collections import Counter

def hrw(key: str, weights: dict[str, float]) -> str:
    best, best_score = "", -math.inf
    for node, w in weights.items():
        u = ((hash64(f"{node}:{key}") >> 11) + 0.5) / (1 << 53)   # 0 < u < 1
        score = -w / math.log(u)
        if score > best_score:
            best, best_score = node, score
    return best

weights = {"small": 1.0, "medium": 2.0, "large": 4.0}
keys = [f"obj:{i}" for i in range(70_000)]
first = {k: hrw(k, weights) for k in keys}
print(Counter(first.values()))   # roughly 10000 / 20000 / 40000
del weights["medium"]
moved = [k for k in keys if hrw(k, weights) != first[k]]
print(all(first[k] == "medium" for k in moved))  # True

정리

  • 몫 계산은 정렬된 점 사이 간격을 주인별로 더하는 것입니다.
  • 복제본 선택은 시계 방향으로 걸으며 조건에 맞지 않는 점을 건너뜁니다.
  • 이전 계획은 새 점 앞 구간과 추가 전 주인을 짝지으면 됩니다.
  • 가중치 랑데부 해시는 -w / ln(u) 점수로 비례 배정과 최소 이동을 함께 얻습니다.
이 장에 대해 질문이 있나요?
이전 장복잡도다음 장실무 활용

댓글 0개

인기 댓글순최신순

로그인 · 로그인하면 댓글을 남길 수 있습니다.