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

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

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

구현

가상 노드를 갖춘 해시 링을 Python bisect로 만들고 C++·Java·TypeScript로 옮깁니다. 네 언어의 해시가 같아서 같은 키는 어디서나 같은 노드로 갑니다.

Python

Python 내장 hash()는 문자열 해시가 실행마다 바뀌므로(PYTHONHASHSEED) 쓰면 안 됩니다. 여기서는 64비트 FNV-1a 뒤에 MurmurHash3의 마무리 단계 fmix64를 붙여 node#1, node#2처럼 비슷한 이름도 고르게 퍼지게 했습니다.

import bisect

MASK = (1 << 64) - 1


def hash64(text: str) -> int:
    x = 0xCBF29CE484222325  # FNV-1a offset basis
    for b in text.encode("utf-8"):
        x = ((x ^ b) * 0x100000001B3) & MASK
    x ^= x >> 33  # fmix64 finalizer
    x = (x * 0xFF51AFD7ED558CCD) & MASK
    x ^= x >> 33
    x = (x * 0xC4CEB9FE1A85EC53) & MASK
    return x ^ (x >> 33)


class HashRing:
    def __init__(self, vnodes: int = 100):
        self.vnodes = vnodes
        self.points: list[int] = []  # sorted positions
        self.owner: dict[int, str] = {}  # position -> node

    def add(self, node: str) -> None:
        for i in range(self.vnodes):
            p = hash64(f"{node}#{i}")
            bisect.insort(self.points, p)
            self.owner[p] = node

    def remove(self, node: str) -> None:
        for i in range(self.vnodes):
            p = hash64(f"{node}#{i}")
            del self.owner[p]
            self.points.pop(bisect.bisect_left(self.points, p))

    def get(self, key: str) -> str:
        if not self.points:
            raise LookupError("ring is empty")
        i = bisect.bisect_left(self.points, hash64(key))
        if i == len(self.points):  # wrap around
            i = 0
        return self.owner[self.points[i]]
  • hash64: 바이트마다 XOR하고 FNV 소수를 곱한 뒤 & MASK로 64비트만 남깁니다. C++·Java의 정수 넘침과 같은 효과입니다.
  • points, owner: 정렬된 위치 목록과 위치에서 노드로 가는 사전입니다.
  • add: 이름 뒤에 #0, #1...을 붙여 가상 노드 위치를 만들고 insort로 정렬을 지키며 넣습니다.
  • remove: 같은 이름으로 위치를 다시 계산하므로 노드별 점 목록을 따로 저장하지 않아도 됩니다.
  • get: bisect_left가 키 위치 이상인 첫 점을 찾고, 끝을 넘으면 0번으로 감쌉니다.

64비트 위치 충돌은 점이 백만 개여도 수천만분의 1 수준이라 생략했습니다.

C++

std::map은 정렬된 트리라서 lower_bound가 곧 시계 방향 탐색입니다.

#include <cstdint>
#include <map>
#include <stdexcept>
#include <string>

std::uint64_t hash64(const std::string& text) {
    std::uint64_t x = 0xCBF29CE484222325ULL;
    for (unsigned char b : text) x = (x ^ b) * 0x100000001B3ULL;
    x ^= x >> 33; x *= 0xFF51AFD7ED558CCDULL;
    x ^= x >> 33; x *= 0xC4CEB9FE1A85EC53ULL;
    return x ^ (x >> 33);
}

class HashRing {
public:
    explicit HashRing(int vnodes = 100) : vnodes_(vnodes) {}
    void add(const std::string& node) {
        for (int i = 0; i < vnodes_; ++i) ring_[hash64(node + "#" + std::to_string(i))] = node;
    }
    void remove(const std::string& node) {
        for (int i = 0; i < vnodes_; ++i) ring_.erase(hash64(node + "#" + std::to_string(i)));
    }
    const std::string& get(const std::string& key) const {
        if (ring_.empty()) throw std::runtime_error("ring is empty");
        auto it = ring_.lower_bound(hash64(key));
        return (it == ring_.end() ? ring_.begin() : it)->second;
    }
private:
    int vnodes_;
    std::map<std::uint64_t, std::string> ring_;
};

Java

부호 없는 long이 없으므로 TreeMap에 Long::compareUnsigned 비교기를 주고, ceilingEntry로 이상인 첫 항목을 찾습니다.

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

public class HashRing {
    private final int vnodes;
    private final TreeMap<Long, String> ring = new TreeMap<>(Long::compareUnsigned);

    public HashRing(int vnodes) { this.vnodes = vnodes; }

    static long hash64(String text) {
        long x = 0xCBF29CE484222325L;
        for (byte b : text.getBytes(StandardCharsets.UTF_8)) x = (x ^ (b & 0xFF)) * 0x100000001B3L;
        x ^= x >>> 33; x *= 0xFF51AFD7ED558CCDL;
        x ^= x >>> 33; x *= 0xC4CEB9FE1A85EC53L;
        return x ^ (x >>> 33);
    }

    public void add(String node) {
        for (int i = 0; i < vnodes; i++) ring.put(hash64(node + "#" + i), node);
    }

    public void remove(String node) {
        for (int i = 0; i < vnodes; i++) ring.remove(hash64(node + "#" + i));
    }

    public String get(String key) {
        if (ring.isEmpty()) throw new IllegalStateException("ring is empty");
        Map.Entry<Long, String> e = ring.ceilingEntry(hash64(key));
        return (e != null ? e : ring.firstEntry()).getValue();
    }
}

TypeScript

64비트 곱셈에 bigint를 쓰고 이진 탐색은 직접 씁니다.

const MASK = (1n << 64n) - 1n;

function hash64(text: string): bigint {
  let x = 0xcbf29ce484222325n;
  for (const b of new TextEncoder().encode(text)) x = ((x ^ BigInt(b)) * 0x100000001b3n) & MASK;
  x ^= x >> 33n; x = (x * 0xff51afd7ed558ccdn) & MASK;
  x ^= x >> 33n; x = (x * 0xc4ceb9fe1a85ec53n) & MASK;
  return x ^ (x >> 33n);
}

class HashRing {
  private points: bigint[] = [];
  private owner = new Map<bigint, string>();
  constructor(private readonly vnodes = 100) {}

  private lowerBound(h: bigint): number {
    let lo = 0, hi = this.points.length;
    while (lo < hi) {
      const mid = (lo + hi) >> 1;
      if (this.points[mid] < h) lo = mid + 1; else hi = mid;
    }
    return lo;
  }
  add(node: string): void {
    for (let i = 0; i < this.vnodes; i++) {
      const p = hash64(`${node}#${i}`);
      this.points.splice(this.lowerBound(p), 0, p);
      this.owner.set(p, node);
    }
  }
  remove(node: string): void {
    for (let i = 0; i < this.vnodes; i++) {
      const p = hash64(`${node}#${i}`);
      if (this.owner.delete(p)) this.points.splice(this.lowerBound(p), 1);
    }
  }
  get(key: string): string {
    if (this.points.length === 0) throw new Error("ring is empty");
    const i = this.lowerBound(hash64(key));
    return this.owner.get(this.points[i === this.points.length ? 0 : i])!;
  }
}

정리

  • 링은 정렬된 위치와 위치별 주인, 두 자료로 충분합니다.
  • 조회는 이상인 첫 위치를 찾고 끝이면 처음으로 감싸는 것입니다.
  • 해시는 실행과 언어가 달라도 같은 값을 내야 합니다.
  • 정렬 트리(map, TreeMap)를 쓰면 점 추가·삭제가 O(log n)입니다.
이 장에 대해 질문이 있나요?
이전 장동작 원리다음 장복잡도

댓글 0개

인기 댓글순최신순

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