패션 플랫폼 ‘바로’ 개발을 시작하면서 도메인을 구현하던 중 ID 생성 방식에 대해 고민하게 되었다. 기존에는 Auto Increment로 만들고 있었지만 이는 단일 서버의 환경에서만 안정적이지 않을까? 라는 생각이 들었다. 하지만 ‘바로’ 서비스의 목표에 의하면 분산 시스템으로서의 확장 가능한 개발이 필요하다
https://chobo-backend.tistory.com/49
[바로] 스와이프로 찾는 내 스타일, ‘바로’를 기획하며..
Intro. 왜 ‘스와이프’로 옷을 팔기로 결정했나 나는 항상 느낀다. 패션은 늘 어렵다... 특히 옷에 많은 시간과 노력을 쏟기 어려운 사람들에게는 더욱 그렇다. 매일 아침 옷장 앞에서 ‘오늘 뭐
chobo-backend.tistory.com
‘바로’의 서비스와 목표에 대해 간단히 설명하면, 스와이프라는 직관적인 UX를 통해 사용자의 스타일링 고민을 해결하고, 장기적으로 MAU 30만, DAU 4만 5천 이상의 트래픽을 안정적으로 처리하는 것이다. 이는 시리즈 A, B 라운드를 거치며 성장하는 서비스의 트래픽 규모와 비슷하다. 이 정도 규모의 시스템에서는 모든 데이터, 즉 모든 회원, 상품, 주문, 그리고 스와이프(좋아요) 기록 하나하나에 전역적으로 유일하며 정렬 가능한 ID를 부여하는 것이 시스템의 확장성과 안정성을 결정짓는데 중요한 요소라고 생각한다. 아래에서 어떤 방식들을 고려하고 어떠한 근거로 결정했는지 고민했던 내용을 기록해보겠다
Part1. 어떤 문제를 해결해야 하는가
가장 먼저, ID 생성기가 만족해야 할 요구사항을 명확히 정의해야 했다. 막연히 ‘유일한 ID’가 아니라, ‘바로’ 서비스의 비즈니스와 기술적 특성에 맞는 구체적인 제약 조건 설정이 필요했다
1. ID는 반드시 전역적으로 유일해야 한다
이것은 기본 전제로, 두 명의 사용자가 같은 ID를 갖거나 두 개의 주문이 같은 주문 번호를 공유하는 순간 데이터 무결성이 깨진다
2. ID는 시간 순으로 정렬 가능해야 한다
이것이 매우 중요한 요구사항이라 생각한다. 사용자의 ‘좋아요’한 룩 목록, 신상품 목록, 주문 내역 등 대부분의 데이터는 시간 순으로 정렬하여 보여줘야 한다. ID 자체가 시간 정보를 담고 있다면, 별도의 created_at
같은 컬럼에 의존하지 않고도 ID만으로 효율적인 정렬이 가능하다. 이는 데이터베이스 인덱싱 성능에 영향을 미칠 수 있다. B-Tree 인덱스에서 순차적인 ID는 새로운 데이터를 리프 노드의 끝에 추가하므로 쓰기 성능이 뛰어나지만, 무작위 ID는 인덱스 트리의 여러 곳을 수정해야 하므로 성능 저하를 유발한다
3. ID는 64비트(8바이트)로 표현 가능해야 한다
ID가 너무 길면 저장 공간 낭비가 심하고, 네트워크 전송 비용과 인덱스 크기도 커진다. 64비트는 MySQL의 BIGINT
타입에 해당하며, 충분히 큰 숫자를 표현하면서도 효율적인 관리가 가능한 합리적인 크기라고 생각한다
4. 초당 10,000개 이상의 ID를 생성할 수 있어야 한다
‘바로’의 목표 트래픽을 고려할 때, 특정 이벤트(예: 유명 인플루언서의 룩 공개)가 발생하면 순간적으로 ‘좋아요’나 주문 요청이 폭주할 수 있다. 시스템은 이러한 피크 타임에도 지연 없이 ID를 발급할 수 있어야 한다
Part2. 선택 가능한 방법들 고려
방법 1 - Multi Master Replication 환경에서 AUTO_INCREMENT
AUTO_INCREMENT 방식은 write를 처리하는 Master DB가 하나 인 경우에서는 문제가 발생하지 않는다. 다만 master DB를 여러 개 사용하는 분산 환경에서 문제가 될 수 있다. 이 상황에서 AUTO_INCREMENT
를 활용하는 방식을 생각해보자. N개의 DB 서버를 두고, 각 서버의 AUTO_INCREMENT
증가 값을 N으로 설정하는 것이다. 예를 들어 3대의 서버가 있다면, 1번 서버는 1, 4, 7..., 2번 서버는 2, 5, 8..., 3번 서버는 3, 6, 9... 와 같은 ID를 생성한다
장점
구현이 비교적 간단하고, 기존 DB 기능을 활용할 수 있다
단점
- 시간순 정렬 불가 : ID가 생성된 시간과 무관하게 뒤죽박죽 섞인다 (요구사항 2 위배)
- 유연성 부족 : DB 서버를 추가하거나 제거할 때마다 모든 서버의
auto_increment_increment
값을 변경하고 재설정해야 하는 운영상의 리소스가 추가적으로 필요하다 - 스케일링 한계 : 데이터 센터가 여러 개로 나뉘는 대규모 환경에서는 적용하기 거의 불가능하다
결론
‘바로’처럼 유연한 확장이 필수적이고 데이터의 시간순 정렬이 중요한 서비스에는 부적합하다
방법 2 - UUID (Universally Unique Identifier)
UUID는 총 36개의 문자로 16진수 문자 32개와 하이픈 -
문자 4개로 구성되어 있다. 16진수를 나타내기 위해서는 4bit가 필요하고, 32개의 16진수 이기 때문에 128비트 길이를 가지고 있다. 충돌 가능성이 천문학적으로 낮아 사실상 유일성이 보장된다
장점
각 서버가 알아서 ID를 생성하므로 중앙 조정이 필요 없고, 동기화 이슈가 없다. 규모 확장이 매우 쉽다
단점
- 길이 : 128비트는 우리의 요구사항인 64비트를 초과한다 (요구사항 3 위배)
- 시간순 정렬 불가 : UUID 버전 1, 6 등 일부는 타임스탬프를 포함하지만, 대부분의 UUID는 완전히 무작위 값이라 시간순 정렬이 힘들다 (요구사항 2 위배)
- 인덱스 성능 저하 : 무작위 문자열 형태라 DB 인덱싱 시 성능이 매우 비효율적이다
결론
확장성은 좋은 특징이지만, 길이와 정렬 문제 그리고 DB 성능 이슈 때문에 ‘바로’의 요구사항을 만족시키지 못한다
방법 3 - 티켓 서버 (Ticket Server)
중앙에 ID 발급 전용 서버(티켓 서버)를 두는 방식이다. 모든 애플리케이션 서버는 ID가 필요할 때마다 이 서버에 요청해서 순차적인 번호(티켓)를 받아온다
장점
순차적인 숫자 ID를 얻을 수 있어 시간순 정렬이 가능하고, 구현이 비교적 간단하다
단점
- SPOF : 티켓 서버가 죽으면 시스템 전체의 ID 발급이 중단된다. ‘바로’와 같이 고가용성이 중요한 서비스에서 리스크가 크다
- 병목 현상 : 모든 ID 발급 요청이 티켓 서버로 몰리므로, 트래픽이 증가하면 서버 자체가 병목이 되어 성능을 저하시킨다
결론
구조는 단순하지만, 시스템의 중요한 부분을 단 하나의 서버에 맡기는 위험을 감수하기는 힘들다
방법 4 - 트위터 스노우플레이크 (Twitter Snowflake)
Snowflake는 ID를 생성하기 위해 중앙 서버에 의존하지 않으면서도, 분산된 각 서버가 알아서 유일하고 시간순으로 정렬 가능한 ID를 만들어내는 알고리즘이다
핵심 아이디어: 64비트라는 공간을 여러 의미 있는 부분으로 잘게 쪼개어 조합하는 것이다
- 사인 비트 (1비트) : 항상 0으로 고정. 양수를 의미한다
- 타임스탬프 (41비트) : ID 생성 시점의 시간 정보
- 데이터센터 ID (5비트) : ID를 생성한 서버가 속한 데이터센터
- 서버 ID (5비트) : 데이터센터 내에서 서버를 식별하는 번호
- 일련번호 (12비트) : 같은 밀리초(millisecond) 내에 여러 ID가 생성될 경우, 이를 구별하기 위한 카운터
이 구조는 모든 요구사항을 완벽하게 충족했다. 유일하고(서버 ID와 일련번호), 64비트이며, 시간순 정렬이 가능하고(타임스탬프), 초당 수천 개의 ID 생성이 가능하다(일련번호). 아래에서 조금 더 설명하겠다
Part3. Snowflake 설계는 어떻게?
Snowflake 개념에 맞게 각 필드의 의미와 길이를 조정하는 과정이 필요하다
1. 타임스탬프 (41비트)
41비트는 약 69년의 시간을 표현할 수 있다. 하지만 이 시간을 최대한 활용하려면 기준점, 즉 에포크(epoch)를 잘 설정해야 한다. 유닉스 타임스탬프의 기준인 1970년 1월 1일을 그대로 사용하면 이미 많은 시간을 소진한 셈이다
우리는 ‘바로’ 서비스가 시작되는 시점을 커스텀 에포크로 설정했다. 예를 들어, 서비스 런칭일이 2025년 1월 1일이라면, 그 순간을 0으로 삼는 것이다. 이렇게 하면 앞으로 69년 동안은 타임스탬프 비트가 고갈될 걱정 없이 ID를 생성할 수 있다. 이 덕분에, ORDER BY id DESC
쿼리만으로 사용자의 활동 피드, 상품 목록 등을 최신순으로 매우 빠르게 가져올 수 있다
2. 데이터센터 ID와 서버 ID (총 10비트)
스노플레이크는 데이터센터 5비트(32개), 서버 5비트(32개)로 총 1024개의 서버를 식별할 수 있다
ID 할당 전략
각 애플리케이션 서버가 시작될 때, AWS EC2 인스턴스 태그나 쿠버네티스 환경 변수를 통해 고유한 서버 ID(0~1023)를 주입받도록 설계할 수 있을 것 같다. 중요한 것은 어떤 서버도 같은 ID를 중복해서 사용하지 않도록 중앙에서 관리하는 정책이다
이 10비트로 인해 최대 1024대의 서버로 서비스를 확장해도 ID 충돌에 대한 걱정 없이 운영할 수 있다. 각 애플리케이션 서버가 시작될 때, Spring의 @Value
어노테이션을 통해 application.yml
에 설정된 고유 ID를 주입받도록 설계해보자
3. 일련번호 (12비트)
12비트는 2^12
, 즉 4096개의 값을 표현할 수 있다. 이는 단일 서버에서 1밀리초 동안 4096개의 ID를 생성할 수 있다는 의미다. 초당으로는 4096 * 1000 = 4,096,000
개, 즉 약 400만 개의 ID 생성이 가능하다. 이는 설정한 요구사항인 ‘초당 10,000개’를 넘는 수치로, 트래픽이 폭발적으로 증가하는 상황에서도 시스템이 안정적으로 ID를 발급할 수 있을 것이다
Part4. Snowflake ID Generator 직접 구현(Kotlin)
Snowflake 설계 원칙에 맞게 SnowflakeIdGenerator
를 Kotlin으로 구현해보았다
전체 구현 코드
@Component
class SnowflakeIdGenerator(
@Value("\${snowflake.server-id}") private val serverId: Long,
@Value("\${snowflake.datacenter-id}") private val datacenterId: Long
) {
// ID 생성의 기준이 되는 에포크 시간 (UTC 2025-07-24 00:00:00)
private val epoch = Instant.parse("2025-07-24T00:00:00Z").toEpochMilli()
// 마지막으로 ID를 생성한 타임스탬프 (밀리초)
private var lastTimestamp = -1L
// 동일 밀리초 내에서 사용될 시퀀스 번호
private var sequence = 0L
init {
// 서버 ID 및 데이터센터 ID의 유효성 검사
if (serverId < 0 || serverId > MAX_SERVER_ID) {
throw IllegalArgumentException("Server ID must be between 0 and $MAX_SERVER_ID")
}
if (datacenterId < 0 || datacenterId > MAX_DATACENTER_ID) {
throw IllegalArgumentException("Datacenter ID must be between 0 and $MAX_DATACENTER_ID")
}
}
@Synchronized
fun nextId(): Long {
var currentTimestamp = System.currentTimeMillis()
// 시스템 시계 역행 감지 (ID 충돌 방지)
if (currentTimestamp < lastTimestamp) {
throw IllegalStateException("Clock moved backwards. Refusing to generate id for ${lastTimestamp - currentTimestamp} milliseconds")
}
// 동일 밀리초 내 ID 생성 처리 및 시퀀스 오버플로우 시 다음 밀리초 대기
if (lastTimestamp == currentTimestamp) {
sequence = (sequence + 1) and SEQUENCE_MASK
if (sequence == 0L) {
currentTimestamp = tilNextMillis(lastTimestamp)
}
} else {
// 시간이 변경되면 시퀀스 초기화
sequence = 0L
}
lastTimestamp = currentTimestamp
// 각 컴포넌트를 비트 쉬프트 연산으로 결합하여 최종 ID 생성
return ((currentTimestamp - epoch) shl TIMESTAMP_LEFT_SHIFT.toInt()) or
(datacenterId shl DATACENTER_ID_SHIFT.toInt()) or
(serverId shl SERVER_ID_SHIFT.toInt()) or
sequence
}
// 다음 밀리초까지 대기하는 헬퍼 함수
private fun tilNextMillis(lastTimestamp: Long): Long {
var timestamp = System.currentTimeMillis()
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis()
}
return timestamp
}
// Snowflake ID 비트 길이 및 쉬프트 상수 정의
companion object {
private const val SERVER_ID_BITS = 5L // 서버 ID (5비트 = 32개 서버)
private const val DATACENTER_ID_BITS = 5L // 데이터센터 ID (5비트 = 32개 데이터센터)
private const val SEQUENCE_BITS = 12L // 밀리초당 시퀀스 (12비트 = 4096개 ID)
const val MAX_SERVER_ID = (1L shl SERVER_ID_BITS.toInt()) - 1
const val MAX_DATACENTER_ID = (1L shl DATACENTER_ID_BITS.toInt()) - 1
private const val SERVER_ID_SHIFT = SEQUENCE_BITS
private const val DATACENTER_ID_SHIFT = SEQUENCE_BITS + SERVER_ID_BITS
private const val TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + SERVER_ID_BITS + DATACENTER_ID_BITS
const val SEQUENCE_MASK = (1L shl SEQUENCE_BITS.toInt()) - 1
}
}
스레드 안전성 : lastTimestamp
와 sequence
변수의 일관성을 유지하기 위해 메서드 전체에 synchronized
키워드를 적용하여 한 번에 하나의 스레드만 접근하도록 한다
시간 역행 감지 : currentTimestamp
가 lastTimestamp
보다 이전인 경우, 시스템 시계가 뒤로 돌아갔다고 판단하고 IllegalStateException
을 발생시킨다
동일 밀리초 처리
- 만약 현재 시간(
currentTimestamp
)이 이전 ID를 생성했던 시간(lastTimestamp
)과 동일하다면,sequence
번호를 1 증가시키고SEQUENCE_MASK
로 마스킹한다 sequence
가 0으로 오버플로우되면 (sequence == 0L
), 이는 해당 밀리초에 생성할 수 있는 모든 ID(4096개)를 소진했다는 의미입니다. 이 경우tilNextMillis()
을 호출하여 다음 밀리초가 될 때까지 강제로 대기한다- 현재 시간이 이전 시간과 다르다면 (
lastTimestamp != currentTimestamp
),sequence
를 0으로 초기화하고 새로운 밀리초의 시작으로 간주한다
Part5. TSID란?
관련 정보들을 더 찾아보던 중 TSID라는 것을 알게 되었다. TSID는 Snowflake와 마찬가지로 시간순 정렬이 가능한 64비트 유일 ID를 생성하지만, 그 내부 구조적으로 차이가 있다. 자세한 설명은 아래 깃허브에서 확인가능하다
https://github.com/f4b6a3/tsid-creator
GitHub - f4b6a3/tsid-creator: A Java library for generating Time-Sorted Unique Identifiers (TSID).
A Java library for generating Time-Sorted Unique Identifiers (TSID). - f4b6a3/tsid-creator
github.com
구조적 차이점
- 타임스탬프 (42비트) : Snowflake(41비트)보다 1비트 더 많은 타임스탬프 비트를 사용하여 더 긴 시간(약 139년)을 표현할 수 있다
- 노드 ID (Node ID) : 데이터센터와 서버 ID를 구분하지 않고, 이를 통합한 ‘노드’의 개념을 사용한다. 기본 10비트를 사용하며, 필요에 따라 유연하게 조정할 수 있다
- 엔트로피 (Entropy) : 가장 큰 차이점이다. Snowflake의 마지막 12비트가 1밀리초 내에서 순차적으로 증가하는 시퀀스(Sequence)인 반면, TSID의 마지막 22비트는 무작위 값으로 채워진다
‘무작위성’은 중요한 특징이다. 20250724105758001
, 20250724105758002
처럼 예측 가능한 순차 ID와 다르게, TSID는 시간 정보는 유지하되 뒷부분이 무작위로 생성되어 예측이 어렵다. 이는 보안적으로 이점을 가질 수 있으며, 특정 데이터베이스 환경에서는 순차적인 키로 인한 ‘핫스팟’ 문제를 완화하는 데 도움이 되기도 한다
Part5. Snowflake VS TSID 성능 테스트 코드 작성
테스트의 목표는 다음과 같다
- 처리율 (Throughput) : 멀티코어, 멀티스레드 환경에서 초당 몇 개의 ID를 생성할 수 있는가? (TPS: Transactions Per Second)
- 충돌 안전성 : 동시성 요청 속에서 단 하나의 ID 충돌도 발생하지 않는가?
테스트 환경
- 컴퓨터 사양 : Intel i7-8700(6코어, 12 논리 프로세서), Ram 16GB
- 동시성 수준 :
16개
의 병렬 스레드(코루틴)를 사용했다 - 총 요청량 : 각 스레드가
100만
개의 ID, 총1,600만
개의 ID를 생성하도록 하여 통계적으로 유의미한 데이터를 확보하고자 했다 - JIT 컴파일러 워밍업 : JVM의 성능을 최대로 끌어내기 위해, 실제 측정 전
10만
개의 ID를 미리 생성하는 워밍업 단계를 추가했다. 이는 JIT 컴파일러가 핫스팟 코드를 네이티브 코드로 최적화할 시간을 보장하여, 순수한 알고리즘의 실행 속도를 측정하기 위함이다 - 충돌 감지 : 생성된 모든 ID는 스레드에 안전한
ConcurrentHashMap.newKeySet<Long>()
에 저장하여, 최종적으로 생성된 유니크 ID의 개수가 요청된 총 ID 개수와 일치하는지 검증했다
테스트 코드
class IdGeneratorBenchmarkTest {
companion object {
private const val THREAD_COUNT = 16 // 병렬 실행할 스레드 수
private const val IDS_PER_THREAD = 100_000 // 각 스레드당 생성할 ID 개수
private const val TOTAL_IDS = THREAD_COUNT * IDS_PER_THREAD
private const val WARMUP_COUNT = 100_000 // JIT 워밍업을 위한 ID 생성 횟수
}
// 직접 구현한 Snowflake 생성기
private val customSnowflake = SnowflakeIdGenerator(serverId = 1, datacenterId = 1)
// TSID 생성기
private val tsidFactory = TsidFactory.builder()
.withNode(1)
.build()
@Test
fun `Snowflake 성능 측정`() {
repeat(WARMUP_COUNT) { customSnowflake.nextId() } // 워밍업
val durationMs = measureTimeMillis {
val latch = CountDownLatch(THREAD_COUNT)
val pool = Executors.newFixedThreadPool(THREAD_COUNT)
repeat(THREAD_COUNT) {
pool.submit {
repeat(IDS_PER_THREAD) {
customSnowflake.nextId() // ID 생성
}
latch.countDown() // 스레드 작업 완료
}
}
latch.await() // 모든 스레드 종료 대기
pool.shutdown()
}
val tps = TOTAL_IDS / (durationMs / 1000.0)
println("Snowflake 생성: 총 ${durationMs}ms, TPS = ${"%,.2f".format(tps)}")
}
@Test
fun `Snowflake 충돌 테스트`() {
val generatedIds = ConcurrentHashMap.newKeySet<Long>()
val latch = CountDownLatch(THREAD_COUNT)
val pool = Executors.newFixedThreadPool(THREAD_COUNT)
repeat(THREAD_COUNT) {
pool.submit {
repeat(IDS_PER_THREAD) {
generatedIds.add(customSnowflake.nextId()) // ID 충돌 체크용 저장
}
latch.countDown()
}
}
latch.await()
pool.shutdown()
if (generatedIds.size == TOTAL_IDS) {
println("✅ Snowflake 충돌 없음: 기대=$TOTAL_IDS, 실제=${generatedIds.size}")
} else {
throw AssertionError("❌ Snowflake ID 충돌! 기대=$TOTAL_IDS, 실제=${generatedIds.size}")
}
}
@Test
fun `TSID 성능 측정`() {
repeat(WARMUP_COUNT) { tsidFactory.create().toLong() } // 워밍업
val durationMs = measureTimeMillis {
val latch = CountDownLatch(THREAD_COUNT)
val pool = Executors.newFixedThreadPool(THREAD_COUNT)
repeat(THREAD_COUNT) {
pool.submit {
repeat(IDS_PER_THREAD) {
tsidFactory.create().toLong() // ID 생성
}
latch.countDown()
}
}
latch.await()
pool.shutdown()
}
val tps = TOTAL_IDS / (durationMs / 1000.0)
println("TSID 생성: 총 ${durationMs}ms, TPS = ${"%,.2f".format(tps)}")
}
@Test
fun `TSID 충돌 테스트 `() {
val generatedIds = ConcurrentHashMap.newKeySet<Long>()
val latch = CountDownLatch(THREAD_COUNT)
val pool = Executors.newFixedThreadPool(THREAD_COUNT)
repeat(THREAD_COUNT) {
pool.submit {
repeat(IDS_PER_THREAD) {
generatedIds.add(tsidFactory.create().toLong()) // ID 충돌 체크용 저장
}
latch.countDown()
}
}
latch.await()
pool.shutdown()
if (generatedIds.size == TOTAL_IDS) {
println("✅ TSID 충돌 없음: 기대=$TOTAL_IDS, 실제=${generatedIds.size}")
} else {
throw AssertionError("❌ TSID ID 충돌! 기대=$TOTAL_IDS, 실제=${generatedIds.size}")
}
}
}
Part6. 성능 테스트 결과
비교를 위해 각 테스트를 10회씩 반복 실행했다. 결과는 다음과 같다
Snowflake 테스트 결과
실행 횟수 | 소요 시간(ms) | TPS (초당 처리량) |
---|---|---|
1 | 4,026 | 3,974,167 |
2 | 3,996 | 4,004,004 |
3 | 4,142 | 3,862,868 |
4 | 4,081 | 3,920,607 |
5 | 4,015 | 3,985,056 |
6 | 4,142 | 3,862,868 |
7 | 4,026 | 3,974,167 |
8 | 4,059 | 3,941,857 |
9 | 4,088 | 3,913,894 |
10 | 4,108 | 3,894,839 |
평균 | 4,068.3ms | 약 3,933,183 |
✅ Snowflake 충돌 없음: 기대=16,000,000, 실제=16,000,000
TSID 테스트 결과
실행 횟수 | 소요 시간(ms) | TPS (초당 처리량) |
---|---|---|
1 | 328 | 48,780,487 |
2 | 338 | 47,337,278 |
3 | 353 | 45,325,779 |
4 | 335 | 47,761,194 |
5 | 375 | 42,666,666 |
6 | 365 | 43,835,616 |
7 | 334 | 47,904,191 |
8 | 366 | 43,715,846 |
9 | 332 | 48,192,771 |
10 | 386 | 41,450,777 |
평균 | 351.2ms | 약 45,696,845 |
✅ TSID 충돌 없음: 기대=16,000,000, 실제=16,000,000
결과 분석
- 충돌 안전성 : 가장 먼저, 두 방식 모두 1,600만 건의 ID를 생성하는 동안 단 한 건의 충돌도 일으키지 않았다. 이는 두 구현 모두 분산 환경의 유일성 보장이라는 기본 요건을 완벽하게 만족함을 의미한다
- 처리율 (Throughput) : 그러나 성능 차이는 근소한 수준이 아니었다. TSID는 직접 구현한 Snowflake 생성기보다 평균적으로 11.5배 이상 빨랐다. 우리 코드가 초당 약 390만 개의 ID를 생성하는 동안, TSID는 초당 약 4,500만 개를 생성했다
Part7. 최종 선택
결과적으로는 직접 만든 코드를 버리고, TSID를 채택하기로 했다. 이유는 다음과 같다
1. 준수한 성능과 미래 확장성
직접 설계해본 ID 생성기도 초기 목표치를 상회했지만, 11배가 넘는 TSID의 성능은 ‘바로’ 서비스가 이후 훨씬 더 성장했을 때에도 문제 없이 작동할 수 있을 것이다. 따라서 미래의 어느 시점에 ID 생성기 성능 튜닝에 시간을 쏟을 필요 없이, 핵심 비즈니스 로직에만 집중할 수 있다
2. 코드 관리 및 유지보수 비용 절감
직접 만든 코드는 관리를 위한 리소스가 들어간다. 잠재적인 버그, 새로운 엣지 케이스, 성능 최적화 등이 필요할 것이다. 반면, 많은 개발자가 사용하고 기여하는 오픈소스는 이미 수많은 환경에서 검증되었고, 지속적으로 개선된다. 따라서 이를 사용하면 유지보수하는 비용을 줄일 수 있다
'프로젝트' 카테고리의 다른 글
[바로] 단일 주문 성능 개선 삽질기 (Ft. JPA save, FK) (0) | 2025.08.20 |
---|---|
[바로] 반복되는 인증,인가 처리 없애버리기(Ft. AOP & ArgumentResolver) (0) | 2025.08.08 |
[바로] 확장성과 성능을 고려한 ERD 설계하기 (3) | 2025.07.25 |
[바로] JWT는 정말 괜찮은 방법일까? (Ft. 세션저장소 선택 이유) (2) | 2025.07.22 |
[바로] 스와이프로 찾는 내 스타일, ‘바로’를 기획하며..(Ft. 기술적 목표) (3) | 2025.07.22 |