새로운 프로젝트나 서비스를 설계할 때 항상 마주치는 고민 중 하나가 바로 "테이블 PK(Primary Key)를 어떻게 생성할 것인가?"입니다.
보통 가장 흔하게 쓰는 방식은 DB의 Auto-Increment(1, 2, 3...)이지만, 분산 환경이나 보안을 고려해 UUID를 도입했다가 심각한 성능 저하를 겪기도 합니다. 그래서 최근 많은 기업과 현대적인 아키텍처에서 대안으로 TSID(Time-Sorted Unique Identifier)를 적극 채택하고 있습니다.
하지만 처음 TSID를 접하면 이런 의문들이 생깁니다:
"그냥 1, 2, 3 순서대로 커지는 Auto-Increment가 제일 빠른 거 아닌가요?"
"게시글이나 댓글 같은 곳까지 굳이 TSID를 써야 하나요?"
"서버를 여러 대 띄우면 ID가 겹치진 않나요? 티켓팅 같은 대용량 서비스에서도 쓸 수 있나요?"
이 글에서는 TSID의 동작 원리와 실제 DB 성능, 그리고 운영 시 알아두어야 할 점들을 정리해 봅니다.
1. TSID란 무엇인가?
TSID는 트위터의 Snowflake나 인스타그램의 ID 생성 방식에서 영감을 받아 표준화된 64비트 정수 기반 식별자입니다.
64비트(8바이트) 안에 다음 세 가지 정보를 담습니다:
타임스탬프 (42 bit): 밀리초(ms) 단위의 생성 시각
노드 ID (10 bit): 서버/머신 고유 번호 (최대 1,024개 노드 구분)
시퀀스 카운터 (12 bit): 같은 밀리초 동안 같은 노드에서 생성된 순번 (밀리초당 최대 4,096개)
핵심은 **앞부분에 시간이 들어가기 때문에 시간이 흐름에 따라 숫자가 항상 증가(Time-sorted)**한다는 점입니다.
2. "1, 2, 3 순서대로 커지는 것보다 느리지 않을까?"
가장 흔한 오해입니다. 결론부터 말하면 DB 저장 및 조회 성능은 Auto-Increment와 사실상 동일하며, 대량 등록 시에는 오히려 TSID가 더 빠릅니다.
1) 똑같은 8바이트 BIGINT
Auto-Increment와 TSID 모두 DB 테이블에는 똑같은 BIGINT(8bytes) 정수로 저장됩니다.
UUID v4(16바이트 또는 문자열 36바이트)는 크기가 커서 외래 키(FK)나 보조 인덱스(Secondary Index)의 메모리(Buffer Pool)를 심하게 낭비합니다.
반면 TSID는 8바이트 정수이므로 인덱스 크기와 메모리 사용량이 Auto-Increment와 완전히 같습니다.
2) B-Tree 인덱스 최적화 (순차 삽입)
RDBMS(MySQL InnoDB, PostgreSQL 등)는 기본적으로 B-Tree 인덱스를 사용합니다.
UUID(랜덤): 새 데이터가 인덱스의 무작위 위치에 끼어들면서 빈번한 페이지 분할(Page Split)과 디스크 I/O 파편화를 일으킵니다.
TSID(시간 정렬): 항상 시간 순으로 숫자가 커지므로, Auto-Increment처럼 인덱스 트리의 맨 우측 끝에 순차적으로(Right-append) 차곡차곡 쌓입니다. 페이지 분할이 거의 발생하지 않습니다.
3) JPA 환경에서 Batch Insert(벌크 삽입) 가능
JPA(Hibernate)를 쓸 때 IDENTITY 방식(Auto-Increment)을 쓰면 쓰기 지연과 배치 삽입이 작동하지 않습니다. DB에 INSERT를 직접 찔러봐야만 PK를 알 수 있기 때문입니다.
반면 TSID는 애플리케이션 메모리에서 미리 번호를 찍어 들고 가기 때문에 수백 건의 데이터를 하나의 INSERT 쿼리로 묶어 처리할 수 있어 대량 등록 속도가 압도적입니다.
3. "회원 ID 말고 일반 게시글/댓글에도 굳이 필요한가요?"
보통 보안이 중요한 결제나 회원 테이블에만 TSID를 고민하고, 게시글이나 댓글은 그냥 Auto-Increment를 써도 되지 않냐고 생각할 수 있습니다. 하지만 일반 도메인에서도 TSID는 큰 장점을 줍니다.
스크래핑 및 열거 공격(Enumeration Attack) 방지
URL이/posts/1,/posts/2형태라면 크롤러나 악의적인 사용자가 1부터 숫자를 올리며 비공개 글을 유추하거나 전체 글을 긁어가기 쉽습니다.비즈니스 지표 노출 차단
경쟁사가 오늘 올라온 글의 ID와 어제 글의 ID를 비교하면 우리 서비스의 하루 게시글 등록량, 회원 가입자 수 등 민감한 성장 지표를 즉시 추정할 수 있습니다.일관된 아키텍처
어떤 테이블은 Auto-Increment, 어떤 테이블은 TSID로 쪼개는 것보다 전사 공통 BaseEntity의 식별자 생성 전략을 일원화하는 것이 코드 복잡도와 유지보수 비용을 크게 낮춥니다.
4. 운영 시 생기는 질문들 (FAQ)
Q1. "서버를 여러 대 띄우면(다중 서버) ID가 겹칠 위험은 없나요?"
A. 서버마다 고유한 노드 번호(Node ID)를 지정해주면 절대 겹치지 않습니다.
TSID는 주민등록번호와 비슷합니다. 앞자리는 생년월일(시간), 뒷자리는 출생지(서버 번호)와 순번입니다.
서버 2대가 우연히 1000분의 1초(1ms)까지 완전히 똑같은 시점에 ID 생성을 시도하더라도, 서버 A는 Node 1, 서버 B는 Node 2로 설정되어 있다면 생성되는 숫자는 완전히 다릅니다.
# 쿠버네티스나 환경변수로 간단히 주입 가능
export TSID_NODE=1 # 1번 Pod
export TSID_NODE=2 # 2번 Pod
Q2. "티켓팅 같은 초고용량 트래픽 서비스에서도 써도 되나요?"
A. 네, 오히려 Auto-Increment보다 대규모 트래픽에 훨씬 유리합니다.
티켓팅 오픈 순간 수만 명이 동시에 몰릴 때 Auto-Increment를 쓰면 "다음 번호표(1, 2, 3...)"를 발급하기 위해 DB 내부 카운터 락(Auto-inc Lock)에 수만 명이 줄을 서다 DB가 먼저 다운됩니다.
반면 TSID는 각 API 서버가 자신의 메모리에서 ID를 즉시 만들어내므로 DB의 ID 발급 병목이 0입니다. 예매 대기 큐(Kafka/Redis)에 넣는 단계부터 고유 orderId를 부여해 비동기로 흘려보낼 수 있습니다.
5. 결론
구분 | Auto-Increment | UUID (v4) | TSID |
|---|---|---|---|
저장 크기 | 8 bytes ( | 16~36 bytes | 8 bytes ( |
인덱스 쓰기 성능 | 최상 (순차 삽입) | 최악 (페이지 분할) | 최상 (시간 순 정렬) |
분산 환경 생성 | 불가 (DB 의존) | 가능 | 가능 (메모리 생성) |
JPA Batch Insert | 불가능 | 가능 | 가능 |
비즈니스 지표 보안 | 취약 (유추 가능) | 안전 | 안전 (유추 불가) |
TSID는 Auto-Increment의 성능·용량적 장점과 UUID의 분산·보안적 장점을 모두 흡수한 현대적인 식별자 전략입니다.
특별히 DB가 번호를 자동으로 매겨줘야 하는 레거시 제약이 있는 것이 아니라면, 신규 프로젝트나 마이크로서비스 환경에서 전 도메인 엔티티의 기본 식별자로 TSID를 채택하는 것은 매우 훌륭한 선택입니다.
댓글
로그인 상태를 확인하고 있어요.