목차
문제 상황
E-Commerce 프로젝트 요구사항 중 User를 제외한 모든 엔티티는 UUID를 PK로 가져야 한다는 조건이 있었고, 그에 따라 프로젝트 초기에는 모든 주요 엔티티의 식별자를 UUIDv4로 설정했습니다.
UUIDv4는 완전 랜덤 기반의 생성방식을 가져 생성에 대한 충돌 위험이 적다는 장점을 가지기 에 도입했습니다.
그러나 이후 QueryDSL 기반 커서 페이지네이션(cursor-based pagination) 구현 과정에서 정렬 불가능성과 인덱스 비효율로 인해 성능 문제가 발생했습니다.
목표: created_at 기준으로 정렬하면서 UUID를 커서로 사용
문제: QueryDSL에서 UUID와 created_at을 함께 커서 조건으로 사용할 때 복잡도 및 성능 저하 발생
→ UUIDv4의 랜덤성으로 인한 정렬 불가능, 인덱싱 비효율, 쿼리 복잡성 증가 등의 문제 발생
where(
(order.createdAt < :cursorCreatedAt)
.or(order.createdAt.eq(:cursorCreatedAt)
.and(order.id.lt(:cursorId)))
)
Java
복사
원인 분석
1. UUIDv4의 구조적 한계
항목 | 설명 |
생성 방식 | 완전 랜덤(RANDOM) 기반 |
정렬 가능성 | 시간 기반 정렬 불가 |
인덱스 효율성 | 랜덤 분포로 인한 B-Tree 단편화 |
성능 영향 | 삽입 순서 불규칙 → 페이징 성능 저하 |
•
UUIDv4는 완전히 랜덤하게 생성되어, 값이 중복되지 않고 유니크하다는 장점이 있지만, 생성 시간과 데이터 정렬 순서가 전혀 일치하지 않음.
•
따라서 최근 생성된 데이터가 테이블 어디에 위치할지 예측 불가능하며, B-Tree 인덱스가 순차 정렬을 유지하기 어렵다는 단점 존재.
2. QueryDSL 커서 기반 조회 복잡성
•
생성 시간으로 조회를 하기 위해 created_at과 uuid를 함께 사용하는 복합 조건 및 인덱스 필요
•
그에 따른 DB 부하 증가 및 최적화 실패로 연결
해결 과정
UUID 버전 변경 (v4 → v7)
•
완전 랜덤 기반의 UUIDv4에서 시간 기반(Time-Ordered)의 생성 방식을 따르는 UUIDv7으로 변경
•
UUIDv7은 Unix timestamp를 포함하여 생성 시점 순서대로 증가
•
created_at 없이도 UUID 자체로 시간 순 정렬 및 커서 기반 조회 가능
•
정렬 가능하며 PostgreSQL 인덱스 효율이 향상됨
PostgreSQL의 B-Tree 인덱스는 순차 입력에 최적화되어 있어, UUIDv7과 같이 시간 순으로 증가하는 키를 가질 경우 인덱스 단편화가 거의 발생하지 않음.
UUID Generator 교체
// 기존
@UuidGenerator(style = UuidGenerator.Style.RANDOM)
// 변경
@UuidGenerator(style = UuidGenerator.Style.TIME)
Java
복사
인덱스 및 쿼리 구조 개선
•
쿼리 단순화
// 기존
where(
(order.createdAt < :cursorCreatedAt)
.or(order.createdAt.eq(:cursorCreatedAt)
.and(order.id.lt(:cursorId)))
)
// 변경
where(order.id.lt(cursor));
Java
복사
•
created_at 없이 UUID 값을 통해 시간순 정렬 가능
•
복합 인덱스 불필요
개선 효과
•
QueryDSL 커서 조회 쿼리 단순화
•
인덱스 부하 및 DB I/O 감소
•
created_at 의존 제거
•
시간 기반 정렬 및 추적성 향상
인사이트
1.
UUID는 중복 방지만이 아니라 정렬성과 인덱싱 효율까지 고려해야 한다.
2.
UUIDv7은 시간 순서를 반영해 커서 기반 조회 및 로그 분석에 유리하다.
3.
새로운 도메인 모델이나 서비스에서도, 정렬·페이지네이션이 필요한 경우에는 UUIDv7을 기본 선택지로 고려할 수 있다.
