본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

분산 환경의 고유 ID 생성

작성 2026. 11. 1.추가
목차7개 항목
출처와 참고

ByteByteGo의 System Design 101 가이드 「Explaining 5 Unique ID Generators」, 「Unique ID Generator」를 바탕으로, UUID 버전은 RFC 9562로 확인해 재구성한 노트다. Snowflake의 설계 배경은 Twitter의 「Announcing Snowflake」(2010)와 「twitter/snowflake」 저장소로 보강했다. Educative의 「The complete guide to the System Design Interview」의 Instagram 풀이로 티켓 서버의 홀짝 이중화를 더했다.

왜 어려운가

DB 한 대라면 자동 증가 컬럼이 ID를 알아서 만든다. 그런데 DB를 샤딩하거나 여러 서버가 동시에 레코드를 만들면 각자 1부터 세서 ID가 겹친다. 분산 ID 생성기는 중앙 조율을 최소로 하면서 겹치지 않는 ID를 만드는 방법이다.

흔한 요구 사항

요구 사항이유
전역 고유샤드·서버를 넘어 겹치면 안 된다
대략 시간순 정렬최근 글 목록처럼 ID 순서가 곧 생성 순서면 정렬 컬럼이 따로 필요 없다
숫자, 64비트bigint 하나에 들어가 인덱스가 작고 비교가 빠르다
높은 확장성, 낮은 지연ID 생성이 쓰기 경로의 병목이 되면 안 된다

다섯 가지 방식

방식만드는 법장점단점
UUID128비트 값을 각 서버가 혼자 만든다네트워크 호출 없이 어디서나 만든다무작위 UUID(v4)는 순서가 없어 B-트리 인덱스에 흩어져 들어가고 페이지 분할이 잦다. 128비트라 크다
Snowflake타임스탬프 + 머신 ID + 일련번호를 비트로 이어 붙인 64비트호출 없이 빠르고, 시간순으로 정렬된다머신 ID를 겹치지 않게 배정해야 한다. 서버 시계가 뒤로 가면 ID가 겹칠 수 있다
DB 자동 증가DB의 identity·sequence 컬럼DB 트랜잭션이 동시성을 처리해 테이블 안에서 확실히 고유하다ID마다 DB를 거친다. DB가 단일 장애점이다. 연속 번호가 밖에 보이면 가입자 수 같은 사업 정보가 드러난다
DB 세그먼트ID 서버가 DB에서 번호 구간(예: 1~1000)을 한 번에 받아 메모리에서 하나씩 나눠 준다DB 호출이 구간당 한 번으로 줄어든다서버가 재시작하면 남은 구간이 버려져 번호에 구멍이 생긴다. 서버 간에는 엄격한 순서가 없다
RedisINCR로 원자적으로 증가시킨다메모리에서 처리해 DB보다 빠르다Redis가 장애점이다. 영속성 설정에 따라 재시작 뒤 번호가 되돌아갈 수 있다

Snowflake의 비트 배치

flowchart LR
  Sign["0<br/>부호 비트는 쓰지 않아 항상 양수"] ~~~ Ts["타임스탬프 41비트 (기준 시각부터 ms)<br/>41비트 ms ≈ 약 69년치"] ~~~ Machine["머신 ID 10비트<br/>10비트 머신 ID = 1024대<br/>데이터센터 ID 5비트 + 워커 ID 5비트로 나누기도 한다"] ~~~ Seq["일련번호 12비트<br/>12비트 일련번호 = 머신당 ms마다 4096개"]

구현마다 비트 배분은 다르지만, 상위 비트에 시간을 둬서 정렬되게 하고 중간에 머신 ID를 둬서 서로 겹치지 않게 하는 점은 같다. 시계가 뒤로 가면(NTP 보정 등) 생성을 잠시 멈추거나 오류를 내도록 만든다.

Twitter가 Snowflake를 만든 요구 사항이 이 방식의 성격을 그대로 보여 준다. MySQL에서 Cassandra·샤딩 MySQL로 옮기면서 DB의 자동 증가 ID를 잃었고, 초당 수만 개를 고가용으로, 대략 시간순으로, 64비트 안에 만들어야 했다. 트윗 ID를 한 번 넓혀 본 경험상, 코드베이스 수만 개가 쓰는 ID의 비트 수는 다시 바꾸기 어렵다.

검토한 대안탈락 이유
MySQL 티켓 서버(Flickr 방식)여러 대를 두면 순서 보장을 위해 재동기화 장치가 필요하다
UUID128비트라 64비트에 들어가지 않는다
ZooKeeper 순차 노드필요한 처리량이 나오지 않는다

티켓 서버 방식은 ID만 내주는 전용 DB 인스턴스를 두는 것이다. 64비트 ID 열 하나만 있는 테이블에 행을 넣고 그 자동 증가 값을 새 객체의 ID로 쓴다. 이 DB가 단일 장애 지점이 되는 문제는 두 대를 두고 한 대는 홀수, 한 대는 짝수만 내게 해서 푼다.

KeyGeneratingServer1:  auto-increment-increment = 2, auto-increment-offset = 1   → 1, 3, 5, …
KeyGeneratingServer2:  auto-increment-increment = 2, auto-increment-offset = 2   → 2, 4, 6, …

앞에 로드 밸런서를 두고 라운드 로빈으로 번갈아 쓰면 한 대가 죽어도 ID가 계속 나온다. 두 서버가 내준 개수가 어긋나도 겹치지만 않으면 되므로 문제가 없고, 대신 전체 ID가 시간순으로 정렬된다는 보장은 잃는다. 사용자, 댓글처럼 객체마다 ID 테이블을 따로 둘 수도 있다.

그래서 조율 없이 각 서버가 「타임스탬프 + 워커 번호 + 순번」을 조합한다. 워커 번호만 시작할 때 ZooKeeper로 받는다(설정 파일로도 줄 수 있다). 결과는 완전 정렬이 아니라 k-정렬이다. 1초 안에 만든 트윗끼리는 ID도 1초 범위 안에 있다는 정도의 보장이다. 2010년 공개판은 은퇴했고, Twitter 내부판은 자체 인프라에 묶인 재작성 버전이다.

시간순 UUID

UUID의 인덱스 문제는 UUIDv7로 대부분 풀린다. RFC 9562(2024)가 정의한 UUIDv7은 상위 48비트에 Unix 밀리초 타임스탬프를, 나머지에 무작위 값을 넣는다. 그래서 조율 없이 만들면서도 대략 시간순으로 정렬되어 인덱스 끝에 차곡차곡 쌓인다. 비슷한 목적의 ULID도 있다.

비교UUIDv4UUIDv7Snowflake
크기128비트128비트64비트
정렬없음시간순시간순
조율필요 없음필요 없음머신 ID 배정 필요
생성 시각 노출없음있음있음

고르는 기준

  • 저장 공간과 조인 성능이 중요하고 64비트 숫자가 필요하면 Snowflake 계열.
  • 조율 없이 어디서나 만들어야 하고 128비트를 감당할 수 있으면 UUIDv7.
  • 테이블 하나에 쓰기가 많지 않으면 DB 자동 증가로 충분하다. 외부에 노출할 ID는 따로 둔다.
  • ID에서 생성 시각이 드러나면 안 되는 곳(추측 가능한 공개 URL 등)에는 무작위 ID를 쓴다.

정리

  • 분산 환경에서 각자 1부터 세면 ID가 겹친다. 생성기는 조율을 줄이면서 고유성을 지킨다.
  • Snowflake는 시간·머신·일련번호를 64비트에 담아 빠르고 정렬되지만 머신 ID와 시계를 관리해야 한다.
  • 무작위 UUID는 인덱스에 불리하다. 시간순이 필요하면 UUIDv7을 쓴다.
  • 연속 번호는 사업 정보를 드러낸다. 외부에 보이는 ID는 따로 설계한다.

관련 노트: 데이터베이스 샤딩, 와이드 컬럼 DB와 대규모 메시지 저장, 시스템 설계의 품질 속성과 트레이드오프

원본 기록6

댓글

GitHub 계정으로 댓글을 남길 수 있어요.