본문으로 바로가기
제품데스크톱 도구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제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

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

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

복잡도

일관된 해시의 비용은 세 가지로 나눠 봐야 합니다. 키 하나를 찾는 조회 비용, 링을 들고 있는 메모리, 그리고 노드가 바뀔 때 옮겨야 하는 키의 양입니다. 마지막 항목이 이 알고리즘의 존재 이유이므로 가장 중요합니다. 아래에서 N은 물리 노드 수, V는 노드당 가상 노드 수, K는 전체 키 수입니다.

해시 링의 시간과 공간

링에는 점이 N·V개 있습니다.

연산정렬 배열(bisect)정렬 트리(TreeMap, std::map)
조회 getO(log(N·V))O(log(N·V))
노드 추가O(V·N·V) (배열 삽입 이동)O(V·log(N·V))
노드 제거O(V·N·V)O(V·log(N·V))
처음 만들기O(N·V·log(N·V)) (모아서 한 번 정렬)O(N·V·log(N·V))
공간O(N·V)O(N·V)

조회가 이진 탐색 한 번이므로 노드 100대에 가상 노드 200개, 곧 점 2만 개라도 비교는 15번 정도입니다. 실제로는 키 해시를 계산하는 시간이 탐색보다 큰 경우가 많습니다. 배열에 insort로 하나씩 넣는 방식은 넣을 때마다 뒤쪽 원소를 옮기므로 노드 변경이 잦다면 정렬 트리나 "모두 모아 한 번에 정렬"이 낫습니다. 노드 변경은 드물고 조회가 압도적으로 많다면 메모리 배치가 연속적인 정렬 배열이 오히려 빠릅니다.

import bisect

def build(points_with_owner):
    # O(M log M) once, instead of M separate insort calls
    items = sorted(points_with_owner)
    points = [p for p, _ in items]
    owner = dict(items)
    return points, owner

points, owner = build([(80, "C"), (10, "A"), (45, "B")])
print(points, owner[points[bisect.bisect_left(points, 50) % len(points)]])  # [10, 45, 80] C

옮겨지는 키의 양

해시가 고르게 퍼진다는 가정에서 기대값은 다음과 같습니다.

상황hash % N일관된 해시
N에서 N+1로약 K·N/(N+1)약 K/(N+1)
N에서 N-1로약 K·(N-1)/N빠진 노드의 키, 약 K/N
받는 쪽거의 모든 노드새 노드만(추가 시)

K/(N+1)은 새 노드가 공평한 몫을 가지려면 반드시 옮겨야 하는 양이므로 이보다 적게 옮기는 방법은 없습니다. 이 의미에서 일관된 해시는 최소 이동을 달성합니다. 다만 이것은 기대값이며, 가상 노드가 적으면 실제 이동량은 새 노드가 우연히 차지한 구간 크기에 따라 크게 흔들립니다.

부하 균형

노드마다 점이 하나뿐이면 N개의 무작위 점이 원을 나누는 간격 중 가장 큰 것은 평균의 약 ln N배입니다. 노드 100대라면 가장 바쁜 노드가 평균의 5배 안팎을 맡을 수 있습니다. 가상 노드 V개를 두면 한 노드의 몫은 V개 구간의 합이 되어 편차가 대략 1/sqrt(V)에 비례해 줄어듭니다. 그래서 실무에서는 노드당 수십에서 수백 개를 흔히 씁니다. V를 늘리면 메모리와 노드 변경 비용이 늘어나므로 균형과 비용 사이의 절충입니다.

import math

for v in (1, 10, 100, 1000):
    print(v, f"relative spread about {1 / math.sqrt(v):.0%}")
# 1 100%, 10 32%, 100 10%, 1000 3%

이 수치는 대략적인 크기만 보여 줍니다. 실제 키 분포가 고르지 않거나 특정 키가 유난히 뜨거우면(핫 키) 가상 노드로는 해결되지 않으며, 복제나 별도 캐시가 필요합니다.

다른 방법과 비교

방법조회메모리추가 시 이동임의 노드 제거비고
hash % NO(1)O(1)거의 전부거의 전부 이동노드 수 고정일 때만
링, 점 1개O(log N)O(N)약 1/(N+1)가능부하 편차 큼
링, 가상 노드 VO(log(N·V))O(N·V)약 1/(N+1)가능가장 널리 쓰임
랑데부(HRW)O(N)O(N)약 1/(N+1)가능가중치·복제 쉬움
점프 해시O(log N)O(1)약 1/(N+1)끝 버킷만버킷 번호만 반환
MaglevO(1)조회표 크기최소보다 약간 많음가능로드 밸런서용

랑데부 해시는 조회가 노드 수에 비례하지만 노드가 수십 대 이하라면 충분히 빠르고 코드가 가장 짧습니다. 점프 해시는 메모리가 필요 없고 균형도 매우 좋지만 노드에 이름이 아니라 0부터 N-1까지의 번호만 붙일 수 있고, 중간 번호를 빼는 것은 지원하지 않습니다. 그래서 장애로 임의의 서버가 빠지는 캐시보다는 샤드 번호가 순서대로 늘어나는 저장소에 어울립니다.

def jump_hash(key: int, num_buckets: int) -> int:
    # Lamping & Veach (2014): O(log n) time, no memory
    b, j = -1, 0
    while j < num_buckets:
        b = j
        key = (key * 2862933555777941757 + 1) & 0xFFFFFFFFFFFFFFFF
        j = int((b + 1) * ((1 << 31) / ((key >> 33) + 1)))
    return b

print(jump_hash(123456789, 10), jump_hash(123456789, 11))

버킷을 10개에서 11개로 늘리면 약 1/11의 키만 바뀌고, 바뀐 키는 모두 새 버킷 10번으로 갑니다.

정리

  • 링 조회는 O(log(N·V)), 메모리는 O(N·V)입니다.
  • 노드 추가 시 옮기는 키는 기대값 약 K/(N+1)로, 피할 수 없는 최소량입니다.
  • 가상 노드는 편차를 대략 1/sqrt(V)로 줄이지만 메모리와 변경 비용을 늘립니다.
  • 노드가 적으면 랑데부, 번호형 샤드면 점프 해시, 범용으로는 가상 노드 링이 무난합니다.
이 장에 대해 질문이 있나요?
이전 장구현다음 장연습 문제

댓글 0개

인기 댓글순최신순

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