0. 들어가기 전
이전까지 저는 모든 프로젝트에서 관성적으로 다음과 같이 Auto_Increment 전략을 사용해서 DB PK를 생성했습니다.
public class XxxEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
이번에 프로젝트 코드 분석을 하는 도중에 JPA Entity에 다음과 같이 PK 생성을 하는 것을 보게 되었습니다.
public class XxxEntity {
@Id @Tsid
private String id;
...
}
관련해서 검색해보니, TSID라는 생성 전략이 있는 것을 확인했습니다.
해당 TSID 외에도 다른 PK 생성 전략에 대해 무지했기 때문에 다른 여러 PK 생성 전략도 알아보려고 합니다.
(현재 포스팅에서는 여러 방법들을 PK에 국한되어 설명하지만, PK 외에 고유 ID를 생성할 때도 사용할 수 있습니다.)
1. PK(고유 ID) 생성 전략 종류
DB의 PK를 생성하는 방법들에는 어떤 것들이 있을까요?
여러 방법들이 있지만, 일반적으로 사용하는 방법은 크게 다음과 같이 5가지 방법들이 있습니다.
- Auto_Increment
- UUID
- ULID
- Snowflake ID
- TSID
결론적으로 말하면, 프로젝트에서는 해당 5가지 방법 중 TSID를 적용했습니다.
앞으로 5가지 방법들을 하나씩 살펴보고, 왜 TSID를 적용했는지도 알아보겠습니다.
2. Auto_Increment
Auto_Increment 전략은 저도 그랬고, 일반적으로 개발을 처음 시작하고 나서 PK를 생성할 때 가장 많이 적용하는 방법일 것입니다.
Auto_Increment는 일반적으로 사용하는 PK 생성 방법이기 때문에 생성 결과만 간략하게 소개하고 넘어가도록 하겠습니다.
위와 같은 방식으로 DB 자체에서 1부터 자동증가로 채번하여 PK를 생성하는 방법입니다.
2-1. Auto_Increment에 어떤 문제가 있을까?
Auto_Increment는 위처럼 간단하게 PK를 생성하는 방식임을 알 수 있습니다.
그래서 많은 사람들이 Auto_Increment 전략을 통해 PK를 생성하고 있습니다.
하지만, 레퍼런스들을 찾아보면 뒤에서 다룰 UUID, ULID, Snowflake, TSID를 사용하는 프로젝트도 있는 것을 알 수 있습니다.
그렇다면, 왜 Auto_Increment는 사용하지 않을까요?
바로 Auto_Increment를 선택했을 때 생기는 문제들이 존재하기 때문입니다.
어떤 문제들이 있는지 Auto_Increment의 문제들을 살펴보도록 하겠습니다.
Problem 1 - 다른 데이터들을 쉽게 추적할 수 있다.
개발자뿐만 아니라, 일반 사용자들도 페이지의 URL을 들여다보면 Auto_Increment된 식별자가 있는 페이지들을 자주 볼 수 있습니다.
실제로, 제가 사용하는 블로그 플랫폼인 Tistory의 예시를 살펴보겠습니다.
빨간 박스가 쳐진 URL을 살펴보면, tistory 도메인 뒤에 '135'인 글의 식별자를 발견할 수 있습니다.
이러한 식별자를 보고 다른 데이터들을 어떻게 추적할 수 있을까요?
135니까 136을 넣어서 접근해볼까?
실제로 위와 같이 접근하면 다음과 같이 다른 데이터(포스트)로 접근되는 것을 알 수 있습니다.
이러한 추적은 티스토리와 같이 정보에 민감하지 않은 도메인들은 상관이 없겠지만, 정보가 민감한 도메인에는 문제가 발생할 수 있습니다.
해당 데이터들을 추적하는 것이 쉽기 때문에 크롤링, 해킹에 아주 취약할 수 있습니다.
- 크롤링 : 자사의 정보들을 크롤링하기 쉬워지므로 타사 및 경쟁사에서 정보들을 가져갈 수 있다.
- 해킹 : 자사의 고객 개인 정보들과 같은 민감한 정보들을 해커가 해킹하기 쉬워진다.
Problem 2 - 분산 시스템에서 ID가 고유하지 않을 수 있다.
프로젝트의 규모가 커지면 다양한 요소를 고려해서 대용량 트래픽을 처리하게 됩니다.
그 중에서 비즈니스를 고려하여 DB를 다음과 같이 분산할 수 있습니다.
- 글로벌 서비스에서 서비스가 성장하여 다양한 국가에서 데이터들이 하나의 DB로 접근하는 상황
- 이 상황에서 국가별로 DB를 분산시켜서 DB 부하를 줄일 수 있습니다.
이렇게 국가별로 DB가 나뉜 상황에서 '회원' 도메인을 예시로 들어보겠습니다.
위와 같이 모두 다른 DB를 사용하기 때문에 DB 별로 1부터 증가하는 Auto_Increment는 중복이 발생할 것입니다.
이때 일반적으로 서비스를 진행한다면, 데이터 상으로 별다른 문제가 발생하지 않을 것입니다.
하지만 이때, 다음과 같은 요구사항이 존재한다고 해봅시다.
- 통계를 위해 국가별 회원 데이터를 하나의 통계 테이블로 구성해야 한다.
이때, 하나의 테이블에 데이터를 모으려고 할 때 각각의 PK가 중복되므로 고유한 데이터를 식별하는 PK의 의미가 퇴색된다.
Problem 3 - 정렬 컬럼에 INDEX를 생성해야 하는 경우, 혼란을 야기할 수 있다.
이번 문제는 앞선 2개의 문제와는 다르게, Auto_Increment 전략을 사용해서 문제가 발생하는 케이스는 아닙니다.
결론적으로 말하면, 다른 전략인 ULID, Snowflake, TSID를 사용하면 해당 문제를 해결할 수 있기 때문에
상대적으로 Auto_Increment를 사용하면 해당 문제를 해결할 수 없기 때문에 문제로 정의했습니다.
(말이 어렵네요 🥲)
무슨말인지 풀어서 설명해보겠습니다.
해당 문제는 조건적으로 발생하는 문제입니다.
해당 문제가 발생하려면, 다음과 같은 조건이 필요합니다.
- 테이블에 created_at 같은 정렬 조건으로 사용하는 컬럼이 존재한다.
- 정렬 속도 향상을 위해 해당 컬럼에 인덱스를 생성했다.
- 또한, 해당 상황에서 조건으로 많이 사용하는 컬럼에 인덱스를 생성했다.
- 쿼리 시 where과 order by에서 index로 설정한 컬럼을 사용한다.
위의 조건이 갖춰졌을 때, Index Scan의 성능이 나빠질 수 있습니다.
※ Member 테이블 예시
말로만 쓰면 상황이 잘 와닿지 않는데, PostgreSQL을 사용한 구체적인 테이블로 예시를 들어보도록 하겠습니다.
* postgreSQL
CREATE TABLE member (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
* name 인덱스 생성
CREATE INDEX idx_member_name ON member(name);
위와 같은 DDL을 가지는 member 테이블이 있다고 해봅시다.
테이블 생성 이후, 조건에 자주 사용되는 name에 먼저 INDEX를 생성했습니다.
또한 초기 데이터로 'seongha1', 'seongha2', 'seongha3' name을 가진 Member 데이터를 각각 백만 건 INSERT 했습니다.
그 후, 다음과 같은 쿼리를 EXPLAIN하면 다음과 같은 결과가 나옵니다.
EXPLAIN
select *
from member
where name = 'seongha3'
- Bitmap Index Scan
- name의 INDEX를 사용하여 Index Scan
- 사진에는 없지만, 약 0.67s 소요
이때 정렬이 많이 사용되어 정렬 속도 향상을 위해 created_at에도 INDEX를 생성했습니다.
* created_at 인덱스 생성
CREATE INDEX idx_member_created_at ON member(created_at);
그리고 다음과 같이 조건 + 정렬의 쿼리를 EXPLAIN 했을 때 쿼리는 어떤 INDEX를 통해 Scan 할까요?
EXPLAIN
select *
from member
where name = 'seongha3'
order by created_at desc
- Index Scan
- cretaed_at의 INDEX를 사용하여 Index Scan
- 사진에는 없지만, 약 1.3s 소요
결과적으로 'name' 조건 컬럼의 INDEX가 아닌 'created_at' 정렬 컬럼의 INDEX를 통해 Scan을 하는 것을 알 수 있습니다.
Index가 여러 개일 때 어떤 Index를 사용해서 Scan할 것인지는 Optimizer의 역할입니다.
아마도, 'name'의 Cardinality가 'created_at'의 Cardinality보다 낮아서 'created_at' INDEX를 선택한 것으로 보입니다.
아무튼, 이렇게 되면 조건 컬럼인 'name'에 INDEX를 생성한 의미가 없어지고 개발자의 의도와 다르게 동작할 수 있습니다.
조금 설명이 길었는데, 이러한 부분을 문제로 설정하게 되었습니다.
Problem 4 - DB 리소스를 소모해서 PK를 생성한다.
Auto_Increment는 PK 생성 책임을 DB에 위임해서
DB가 자동 증가되는 숫자를 저장하고 있다가, 데이터가 들어오면 1씩 증가시켜서 PK로 저장하는 방식입니다.
이때, 동시에 많은 데이터 저장 쿼리가 DB에 요청되면 해당 숫자들은 어떻게 정합성을 지키면서 증가할 수 있을까요?
이러한 구현은 DB 벤더사별로 다르게 동작합니다.
- MySQL : Auto_Increment Lock이 존재하여, 데이터가 INSERT 될 때마다 PK의 정합성을 유지하기 위해 Lock을 걸고 내부 Increment 숫자를 증가시킨 후에 Lock을 해제합니다.
- PostgreSQL : 트랜잭션과 별도로 동작하는 'Sequence'를 만들어서 원자성을 보장합니다.
- ...
이런 식으로 DB별로 별도의 자원을 소모하여 Auto Increment의 데이터 정합성을 지키게 됩니다.
따라서, 다른 PK 생성 전략을 사용하는 것 대비 DB 리소스를 사용하기 때문에 DB에 부하가 간다는 점이 문제라고 할 수 있습니다.
이렇게 Auto_Increment를 선택했을 때 발생하는 문제 4가지를 알아봤습니다.
Auto_Increment가 아닌 UUID, ULID, Snowflake, TSID를 사용하면 이러한 문제들을 부분적으로 해결하거나 전부 해결할 수 있습니다.
앞으로 설명할 UUID, ULID, Snowflake, TSID는 해당 문제들을 어떻게 해결하는지, 어디까지 해결하는지 살펴보도록 하겠습니다.
2. UUID
UUID는 Universally Unique Identifier의 약자로, 말그대로 고유한 식별자를 의미합니다.
UUID는 128비트의 고유한 식별자 36자 문자열을 만들어내는 표준입니다.
UUID는 여러 버전이 있고, 버전에 따라 고유 식별자를 만드는 방법들이 다릅니다.
버전 중에서 일반적으로 버전 4가 완전한 랜덤값을 통해 고유 식별자를 사용하기 때문에 버전 4를 많이 사용합니다.
UUID는 사실 고유한 식별자를 생성하는 표준이지만, 중복 가능성이 0%는 아닙니다.
RFC 4122 (https://datatracker.ietf.org/doc/html/rfc4122) 문서에 정의된 바에 따르면,
1조개의 UUID 중에 중복이 일어날 확률은 10억 분의 1이라고 서술하고 있습니다.
그럼에도 불구하고, 간단하게 거의 고유한 식별자를 생성할 수 있고 작은 크기를 가지기 때문에 고유 ID를 생성할 때 많이 사용합니다.
UUID로 생성되는 문자열의 예시는 다음과 같습니다.
b00f1718-540e-4ac0-aaa2-68cc65e41d5d
UUID로 PK를 생성하게 되면 다음과 같이 저장됩니다.
이렇게 저장되면 Auto_Increment 사용 시 발생했던 크롤링, 해킹 문제를 부분적으로 해결할 수 있습니다.
- 크롤링, 해킹 위험이 Auto_Increment보다 적다. (다른 데이터 추적 어려움)
- PK 생성에 DB 리소스를 소모하지 않는다.
그러나, 여전히 다음 2가지 문제는 해결할 수 없습니다.
- 분산 시스템에서 ID가 고유하지 않을 수 있다. (사실 분산 시스템이 아니더라도 UUID는 중복 가능성이 0%가 아니기 때문에 UUID 자체의 100% 고유 식별자가 아닌 문제가 있다.)
- 정렬 Index 사용으로 인해 Index Scan 성능이 나빠질 수 있다.
- UUID 버전 1을 사용하면 시간을 기반으로 생성되기 때문에 PK를 통해 정렬해서 정렬 Index를 사용하지 않아도 된다.
- 하지만, 일반적으로 자바에서도 UUID 버전 3, 4를 제공하고 버전 1은 외부 라이브러리를 사용해야 한다.
- 이러한 이유때문에 일반적인 경우(버전 4)를 고려해서 정렬 Index를 사용해야 한다.
이렇게 문제가 존재하기 때문에 사실 UUID도 PK 생성 전략으로 많이 사용하지는 않습니다.
3. ULID
ULID는 Universally Unique Lexicographically Sortable Identifier의 약자입니다.
(근데 UULSI가 아니라 ULID네요,,, ㅎㅎ)
UUID와 비슷해보이지만 다음과 같은 키워드가 추가되었습니다.
- Lexicographically : 사전적으로
- Sortable : 정렬 가능한
기본적으로 UUID에서 '정렬' 기능이 추가되었다고 생각하면 됩니다.
따라서, ULID는 '128비트의 고유한 식별자 문자열을 만드는 표준'이라는 점은 UUID와 같습니다.
UUID와 다른 점은 다음과 같습니다.
- Timestamp 기반으로 생성하기 때문에 ULID로 시간순 정렬이 가능하다.
- 36자가 아닌 26자로 생성되기 때문에 문자열 크기가 더 작다.
ULID를 나타내면 다음과 같습니다.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48bits 80bits
- 앞 10자 : Timestamp 기반으로 대소문자를 구분하지 않고 시간을 나타냄
- 뒤 16자 : 랜덤 값으로 구성
ULID로 PK를 생성하게 되면 다음과 같이 저장됩니다.
(아주 약간의 일정한 간격을 두고 5개를 생성했습니다.)
아주 약간의 일정한 간격을 두고 생성했기 때문에 맨 앞 10자가 '01J8AA75M0'이나 '01J8AA75M0'으로 비슷한 것을 알 수 있습니다.
해당 ULID는 기존 문제 중 다음과 같은 문제를 해결합니다.
- 크롤링, 해킹 위험이 Auto_Increment보다 적다. (다른 데이터 추적 어려움)
- PK 생성에 DB 리소스를 소모하지 않는다.
그러나, 여전히 다음 2가지 문제는 해결할 수 없습니다.
- 분산 시스템에서 ID가 고유하지 않을 수 있다. (사실 분산 시스템이 아니더라도 ULID는 중복 가능성이 0%가 아니기 때문에 ULID 자체의 100% 고유 식별자가 아닌 문제가 있다.)
- 정렬 Index 사용으로 인해 Index Scan 성능이 나빠질 수 있다.
여기서, 고유하지 않은 ID 문제는 UUID와 같기 때문에 이해가 될 것입니다.
그런데 UUID에서 Timestamp 기반으로 시간 정렬 기능이 추가된 ULID인데 왜 정렬 문제가 그대로 남아있는지 궁금할 것입니다.
다시 ULID PK 예시를 봐봅시다.
3, 4, 5번을 살펴보면 거의 동일한 시간 간격으로 생성했기 때문에 Timestamp 기반의 앞 10자리가 '01J8AA75M1'로 동일합니다.
이 상황에서 PK로 정렬을 수행하게 되면 맨 앞 10자리는 동일하기 때문에 뒤의 랜덤 16자리에 의해 정렬되게 됩니다.
따라서 위의 경우 11번째 자리가 각각 '7 / 3 / C'이므로 정렬은 '3 -> 7 -> C' 순으로 정렬됩니다.
결국 생성 순서 3, 4, 5를 정렬했는데 4, 3, 5로 정렬되게 되는 것입니다.
이러한 이유때문에 결국 ULID도 생성한 ID로 완벽하게 정렬할 수는 없기 때문에 해당 문제를 해결할 수 없습니다.
그래서 ULID로 생성한 ID도 PK로 사용하기 적절하지 않아보입니다.
4. Snowflake ID
Twitter Snowflake ID는 2010년에 Twitter에서 만든 분산환경에서 사용할 수 있는 고유 ID 생성 방법입니다.
Twitter Snowflake 공식 문서(현재는 X 공식문서) 링크는 다음과 같습니다.
https://blog.x.com/engineering/en_us/a/2010/announcing-snowflake
공식문서에서는 Snowflake ID 방법을 도입한 이유를 다음과 같이 설명하고 있습니다.
- 하나의 MySQL 인스턴스에 데이터를 저장했다가, NoSQL인 Cassandra와 MySQL을 샤딩하여 나눠서 데이터 저장
- 이 상황에서, 고유한 ID를 만드는 적절한 방법이 없었다.
- 고가용성 방식으로 분산 시스템에서 초당 수만개의 ID를 생성해야한다.
- ID로 정렬이 가능해야 한다.
- 리팩토링을 위해 새로운 ID는 기존 ID 비트 수인 64비트를 유지해야한다.
따라서, 해당 문제들을 해결하기 위해 Snowflake ID는 고유한 ID를 다음과 같이 구현했습니다.
- 총 64비트의 ID
- 1 bit sign : 1비트를 할당해서 양수 / 음수를 결정, 일반적으로 양수를 나타내기위해 항상 0으로 사용
- 41 bit timestamp : 특정 기준 시간인 Epoch를 정해놓고 그 시점부터 경과한 시간을 ms 단위로 저장
- 41비트는 약 2의 41승 개의 다른 값을 가질 수 있는데, 이를 기간으로 환산하면 약 69년까지 고유한 값을 저장할 수 있다.
- 해당 timestamp bit를 통해 ID로 시간순 정렬이 가능하고, 언제 생성되었는지 추적할 수 있다.
- 10 bit worker ID : Snowflake ID를 생성하는 인스턴스마다 고유한 ID를 생성
- 총 10비트로, 2의 10승 개인 1024개의 분산 인스턴스까지 사용 가능하다.
- 해당 10 bit worker ID를 통해 분산 시스템 간의 Snowflake ID 중복 충돌이 발생하지 않는다.
- 12 bit sequence : 동일한 ms 내에서 여러 ID 생성 시에 사용된다.
- 기본은 0으로, 만약 동일한 ms 내에 여러 ID를 생성해야 할 때 1씩 증가하면서 2의 12승인 4096까지 나타낼 수 있다.
- 즉, 동일한 ms 내에서 최대 4096개의 고유한 ID를 생성할 수 있다.
- 만약 4096개 이상의 동일한 요청이 들어오면 대기한다.
Snowflake ID로 PK를 생성하게 되면 다음과 같이 저장됩니다.
(아주 약간의 일정한 간격을 두고 5개를 생성했습니다.)
해당 Snowflake는 기존 문제 중 다음과 같은 문제를 해결합니다.
- 크롤링, 해킹 위험이 Auto_Increment보다 적다. (다른 데이터 추적 어려움)
- PK 생성에 DB 리소스를 소모하지 않는다.
- 분산 시스템에서 ID가 고유하다. (약 69년까지만.)
- Snowflake ID로 정렬이 가능하기 때문에 정렬 컬럼을 따로 만들지 않고 PK로 정렬이 가능하다.
따라서, Snowflake를 사용하면 기존 4가지 문제를 모두 해결할 수 있습니다.
물론 고유 ID를 만들 수 있는 기간이 최대 69년이라는 사실이 조금 걸리긴 하지만, 이전 방법들 중에서는 가장 좋은 방법 같습니다.
그래서 Discord나 Instagram 같은 유명한 서비스에서도 고유 ID를 Snowflake ID를 사용하여 생성한다고 합니다.
5. TSID
TSID는 Time-Sorted Unique Identifier의 약자로, UUID를 대체할 목적으로 나온 방법이라고 합니다.
TSID는 앞서 살펴본 ULID와 Snowflake ID를 합쳐서 ID를 구성한 오픈소스 라이브러리입니다.
https://github.com/f4b6a3/tsid-creator?tab=readme-ov-file
TSID는 간단하게 다음과 같은 2가지 요소로 구성됩니다.
- Time 요소 (42 bits)
- Random 요소 (22 bits)
- node (0 ~ 20 bits)
- counter (2 ~ 22 bits)
- 여기서 node, counter는 bit 수가 정해져있지 않고 유연하게 node, counter 수를 조정할 수 있습니다.
- ex) 현재 인스턴스 수에 맞게 적절하게 node, counter를 조절하여 동시성을 고려할 수 있습니다.
구조를 그림으로 도식화하면 다음과 같습니다. (공식문서의 구조 코드블럭)
adjustable
<---------->
|------------------------------------------|----------|------------|
time (msecs since 2020-01-01) node counter
42 bits 10 bits 12 bits
- time: 2^42 = ~69 years or ~139 years (with adjustable epoch)
- node: 2^10 = 1,024 (with adjustable bits)
- counter: 2^12 = 4,096 (initially random)
기본적으로 구조는 앞서 살펴봤던 Snowflake ID와 거의 동일합니다.
다른 점은 ULID에 있었던 '랜덤성'이 추가되었다는 것입니다.
node가 생성될 때 이전의 Snowflake ID에서는 나머지 22 bit를 다음과 같이 처리했습니다.
- 10bit의 worker ID : 각 worker(node)의 ID를 지정하여 worker 마다 동일한 ID 지정
- 12 bit의 seqence : 순차적으로 증가하는 방식
TSID는 해당 방식에서 랜덤성을 추가하여 다음과 같이 구현합니다.
- node : node마다 ID를 랜덤으로 지정 (동일한 node라도 매 요청마다 ID 달라짐, 하지만 설정으로 고유하게 지정도 가능)
- counter : 무조건 랜덤으로 설정
랜덤성과 정렬의 Trade-Off
TSID에도 ULID의 랜덤성이 추가되면서, ULID의 한계도 같이 가져오게 됩니다.
랜덤성을 추가하다보니, 동일한 Timestamp일 경우에 뒤의 22 bit 요소가 중복될 확률이 0%가 아니게 되었습니다.
하지만, 반대로 인스턴스의 ID가 동일할 때도 랜덤하게 생성하고 counter가 랜덤하게 바뀌면서 보안성은 훨씬 좋아진 것을 알 수 있습니다.
따라서, TSID와 Snowflake ID를 결정하는 기준 중 하나는 '보안성'과 '중복성'일 것 같습니다.
(물론 중복성에서 중복 확률이 0%가 아니지만, 거의 0%에 수렴하므로 무시해도 되지 않을까 싶긴 합니다.)
TSID as long / TSID as String
그렇다면, TSID를 사용해서 ID를 생성하면 결과가 어떻게 나올까요?
TSID는 'long', 'String' 2가지 타입으로 상황에 맞게 사용할 수 있습니다.
이때, String 타입을 사용한다면 Crockford's base32 인코딩을 사용해서 인코딩한 값을 ID로 사용하게 됩니다.
해당 TSID는 기존 문제 중 다음과 같이 모든 문제를 해결하는 것을 알 수 있습니다.
- 크롤링, 해킹 위험이 Auto_Increment보다 적다. (다른 데이터 추적 어려움)
- PK 생성에 DB 리소스를 소모하지 않는다.
- 분산 시스템에서 ID가 고유하다. (TSID도 Snowflake와 timestamp 부분 bit 수가 같기 때문에 약 69년까지만.)
- TSID 자체로 시간 순 정렬이 가능하기 때문에 정렬 컬럼을 따로 만들지 않고 PK로 정렬이 가능하다.
※ Snowflake ID vs TSID
그렇다면, 구조만 본다면 거의 Snowflake와 다른 점이 없어보이는데 Snowflake ID와 비교해서 TSID는 어떤 점이 다를까요?
- 유연한 확장성 : Snowflake ID는 확장 시마다 고유 ID를 부여해줘야하지만, TSID는 랜덤 방식을 선택하면 고유 ID를 새롭게 지정할 필요없이 랜덤하게 생성됩니다. 따라서, 고유 ID를 관리해야 할 필요가 없습니다.
- 데이터 저장 공간 절약 : String으로 저장하게 되면 13자로 더 짧은 문자열을 생성하기 때문에, 저장되는 크기가 더 적습니다.
- 좋은 가독성 : 상대적으로 19자의 숫자인 Snowflake 보다 Base32로 인코딩된 TSID가 개발자 입장에서 가독성이 좋습니다.
결론
지금까지 다음과 같이 다양한 PK(고유 ID) 생성 전략들을 알아봤습니다.
- Auto_Increment
- UUID
- ULID
- Snowflake ID
- TSID
그렇다면, 어떤 상황에 어떤 전략들을 사용할까요?
결론부터 말하자면, 저는 Auto_Increment / Snowflake ID / TSID 중 상황에 맞게 사용할 것 같습니다.
- 규모가 적고, 확장 가능성이 적고 정보 노출에 민감하지 않은 서비스 : Auto Increment
- DB에서 PK를 생성하므 Application 단계에서 UUID, ULID에 비해 간단한 구현
- UUID, ULID에 비해 상대적으로 좋은 코드 가독성
- 상황상 발생하지 않는 Side Effect
- 반대로 규모가 크거나 확장 가능성이 있거나 정보 노출에 민감한 서비스 : Snowflake ID / TSID 중 선택
- 상황상 Auto Increment, UUID, ULID는 Side Effect가 발생할 수 있기 때문에 제외
- 또, 시간순 정렬이 가능하게 설계하면 대규모 데이터 정렬 시 더 빠른 성능으로 조회가 가능하므로 Snowflake ID / TSID 중 선택
- 둘 중에서는 일반적으로 TSID를 사용하고, 랜덤성으로 인한 데이터 중복이 1건이라도 있으면 안되는 경우 Snowflake ID 선택
- TSID가 Snowflake ID 대비 확장에 유리하고, 데이터 공간 절약, 좋은 가독성을 가지기 때문에 일반적으로 TSID 선택
- 단, 엄청 희박한 확률이지만 TSID의 랜덤성으로 인해서 데이터 중복이 되었을 때 손해가 큰 도메인(돈 관련 도메인)에서는 중복이 없는 Snowflake ID를 선택
Reference
'DB' 카테고리의 다른 글
[PostgreSQL] 2. PostgreSQL Index 알아보기 (Index Type, Index Scan) (1) | 2024.11.16 |
---|---|
[PostgreSQL] 1. PostgreSQL 내부 구조 알아보기 (feat. 쿼리 처리 과정) (0) | 2024.10.19 |
[DB] AWS DynamoDB 알아보기 (0) | 2024.09.13 |
[DB] MySQL 및 InnoDB의 DB Lock 알아보기 (1) | 2023.11.19 |
[DB] MySQL InnoDB의 인덱스(feat. 클러스터링 인덱스, 세컨더리 인덱스, 인덱스 스캔 종류, 다중 컬럼 인덱스) (1) | 2023.11.13 |