해시 테이블 성능을 좌우하는 충돌 처리와 로드 팩터
해시 함수, 체이닝, 오픈 어드레싱, 로드 팩터와 재해싱 정책을 중심으로 해시 테이블의 성능과 운영 설계를 정리합니다.
2026-08-14 · 최초 발행 2024-04-29
키를 버킷으로 보내는 구조와 성능의 전제
해시 테이블은 키를 해시 함수 h(k)로 정수 해시로 바꾼 뒤, 버킷 배열 크기 m에 대해 index = h(k) mod m으로 위치를 정하는 자료구조다. 서로 다른 키가 같은 인덱스에 도달할 수 있으므로 충돌 처리가 필요하다.
균등한 해싱과 적절한 로드 팩터 관리가 이뤄지면 삽입·탐색·삭제는 평균 O(1)이다. 다만 충돌이 한곳에 몰리면 최악 O(n)까지 악화될 수 있다. 로드 팩터 α = n/m에서 n은 원소 수, m은 버킷 수이며, 이 값을 어떤 임계에서 리사이징·재해싱할지가 성능을 결정한다.
해시 함수와 버킷 크기를 함께 정한다
해시 함수에는 결정성, 균등한 분포, 빠른 계산이 요구된다. 문자열 키라면 인코딩과 트리밍 같은 정규화 규칙을 먼저 고정해야 한다.
일반적인 고성능 용도에는 MurmurHash3나 xxHash 같은 비암호학적 해시를 쓸 수 있다. 보안적 충돌 저항이 필요한 경우에는 SHA-2 계열을 고려할 수 있지만, 일반 테이블에는 과도한 비용이 될 수 있다.
버킷 수가 2의 거듭제곱이면 비트 마스킹 최적화가 가능하다. 입력 편향이 있다면 소수 m 또는 더블 해싱으로 이를 보정할 수 있다.
로드 팩터가 증가하면 충돌과 탐색 길이가 늘어난다. 체이닝은 α≈1.0~2.0, 오픈 어드레싱은 α≤0.5~0.75를 기준으로 관리한다. 임계를 넘으면 보통 m을 2배로 늘리고 전체를 재해싱한다. 이 작업 자체는 O(n)이지만, 가끔 발생하는 상쇄 비용으로 평균 O(1)을 유지한다.
체이닝과 오픈 어드레싱의 설계 차이
체이닝은 각 버킷에 리스트나 동적 배열로 키-값 쌍을 보관한다. 삽입은 버킷 뒤에 추가하고, 탐색은 버킷 내부에서 선형 또는 이진 탐색한다. 포인터 오버헤드는 있지만 충돌 영향이 완화되고 확장하기 쉽다.
오픈 어드레싱은 별도 버킷 구조 대신 테이블 내부에서 빈 슬롯을 찾는다. 선형 조사, 제곱 조사, 더블 해싱을 사용할 수 있다. 추가 포인터가 필요 없고 캐시 지역성이 좋지만, 로드 팩터가 높아질수록 클러스터링으로 성능이 급격히 나빠질 수 있다.
| 지표 | 체이닝(Chaining) | 오픈 어드레싱(Open Addressing) |
|---|---|---|
| 성능 | α 증가에 선형적 영향, 평균 탐색 1+α/2 | 캐시 효율 우수, α 증가 시 급격한 악화(클러스터링) |
| 확장성 | 리사이징 단순, 고밀도 저장 가능 | α≤0.7 권장, 리사이징 비용 큼(전 슬롯 스캔) |
| 일관성 | 삭제 간단, 버킷 락으로 예측 가능 | Tombstone 관리 필요, 조사 순서 유지 필수 |
| 안정성 | 편향 입력에도 상대적 강건 | 해시 편향에 취약, 더블 해싱 등 보완 필요 |
| 운영 편의 | 구조 단순, 디버깅 용이 | 메모리 효율·캐시 친화적, 파라미터 튜닝 중요 |
메모리 밀도와 캐시 친화성이 우선이면 오픈 어드레싱이 맞고, 단순한 구조와 동시성 확장성이 중요하면 체이닝이 적합하다.
삭제와 리사이징에서 지켜야 할 일관성
체이닝은 포인터를 수정해 노드를 제거할 수 있다. 중복 키가 들어올 때 버킷 내부에서 값을 갱신할지에 대한 규약도 필요하다.
오픈 어드레싱은 삭제한 위치에 Tombstone을 남겨 탐색 경로를 보존한다. Tombstone이 과도하게 쌓이면 재해싱을 트리거해야 한다. 테이블이 가득 차거나 조사 한계에 도달했을 때도 즉시 리사이징·재해싱이 필요하며, 삽입과 리사이징은 원자적으로 처리해야 한다.
동시 접근에 맞춘 테이블 운영
체이닝은 버킷 단위의 Striped locks 또는 RW 락으로 경합을 줄일 수 있다. 충돌이 집중되는 버킷은 분할 정책을 검토한다.
오픈 어드레싱은 테이블 전체 또는 구간 락을 둘 수 있다. CAS 기반의 Lock-free 설계도 가능하지만 구현 복잡성이 높아진다. 읽기와 쓰기가 경쟁하는 환경에서는 Copy-on-Write 리사이징이나, 이행 기간에 신·구 테이블을 함께 조회하는 방식으로 무중단 확장을 구성할 수 있다.
인메모리 캐시나 세션 스토어처럼 낮은 지연 탐색이 필요한 경우에는 오픈 어드레싱과 선형·제곱 조사를 조합하고 α≈0.5~0.7에서 주기적으로 재해싱하는 방식을 사용할 수 있다. 언어 런타임의 심볼 테이블과 딕셔너리는 체이닝 또는 오픈 어드레싱을 혼합해 구현한다.
스트리밍 집계와 중복 제거에서는 체이닝에 버킷 내부 소형 정렬 배열이나 미니 해시를 두고, 핫 버킷을 모니터링해 분할할 수 있다. 네트워크·로깅 라우팅 테이블은 캐시 지역성을 중시해 더블 해싱으로 편향을 완화하고, 이중 테이블 이행 방식으로 무중단 리사이징을 적용한다.
로드 팩터가 탐색 비용에 미치는 영향
이상적 가정에서 체이닝의 성공 탐색 기대 비교 연산은 ≈ 1 + α/2, 실패 탐색은 ≈ 1 + α다. 선형 조사를 쓰는 오픈 어드레싱은 성공 탐색이 ≈ 0.5 × (1 + 1/(1−α)), 실패 탐색이 ≈ 0.5 × (1 + 1/(1−α)^2)로 표현된다.
α=0.5일 때 선형 조사의 성공 탐색은 ≈ 1.5 probe이고, α=0.75일 때는 ≈ 2.5 probe다. 실패 탐색은 ≈ 8.5 probe가 된다. 로드 팩터 임계와 주기적 재해싱은 지연 분포를 안정화하는 운영 수단이다.
체이닝은 항목당 포인터 오버헤드가 있지만 충돌 편향에 강건하다. 오픈 어드레싱은 추가 포인터 없이 캐시 미스를 줄일 수 있어 고QPS 처리에 유리하다. 편향 키나 공격성 입력에는 솔트 해시와 더블 해싱으로 최악 지연을 완화할 수 있다.
관측 가능한 정책으로 굳히기
키 정규화에는 케이싱, 대소문자 규약, 트리밍을 포함한다. 비암호학적 해시로 Murmur3 또는 xxHash를 사용하되, 공격 가능 표면이 있으면 솔트를 추가한다. m은 마스킹 최적화를 위해 2의 거듭제곱으로 둘 수 있으며, 더블 해싱에서는 보폭과 m이 서로소가 되도록 보장한다.
체이닝은 α≥1.5, 오픈 어드레싱은 α≥0.65 같은 임계를 목표에 맞게 둘 수 있다. 2배 확장을 사용하고, 체이닝은 백그라운드 점진적 리해싱으로 지연 스파이크를 완화한다. 오픈 어드레싱은 Tombstone 비율이 임계를 넘으면 강제 재해싱한다.
운영 중에는 α, 충돌율, 평균·상위 p99 probe 길이, Tombstone 비율을 계측한다. 체이닝은 버킷 스트라이프 락과 버킷별 단일 작가 규칙을 적용할 수 있고, 오픈 어드레싱은 리사이징 동안 신·구 테이블을 함께 조회해 읽기를 이중화한다.