에디블로그
Engineer's Field Notes

AI 자동화로 매일
한 편씩 쓰는
엔지니어 운영 노트

Claude Code · 자동화 파이프라인 · 사고 회고까지. 잘 굴러간 기록 + 깨진 흔적도 같이 남깁니다.

사람이 할 수 있는 일은,
AI도 할 수 있어야 합니다.
매일 한 편 쓰면서 검증 중.
— 이번 주 가장 많이 읽힌 글 TOP 3
백엔드/데이터 & DB

[JPA] ID 생성 전략 비교: IDENTITY vs SEQUENCE, allocationSize, UUID 기본키

반응형
[JPA] ID 생성 전략 비교: IDENTITY vs SEQUENCE, allocationSize, UUID 기본키

[JPA] ID 생성 전략 비교: IDENTITY vs SEQUENCE, allocationSize, UUID 기본키

@GeneratedValue 전략은 별 생각 없이 IDENTITY로 두는 경우가 많아요. 대부분은 문제없는데, 이 선택이 배치 insert를 구조적으로 막고 분산 환경의 ID 설계와도 얽혀요. 한 번은 정리해둘 가치가 있는 주제예요.

IDENTITY와 SEQUENCE가 ID를 언제 아느냐의 차이, allocationSize의 동작과 ID 구멍, MySQL의 현실적인 선택지, UUID 기본키의 함정까지 하나씩 짚어요.

01. 핵심은 "ID를 언제 아느냐"예요

영속성 컨텍스트는 엔티티를 관리하려면 ID가 필요해요. 1차 캐시가 ID를 키로 쓰니까요. 그래서 persist 시점에 ID가 있어야 하는데, 전략마다 ID를 아는 시점이 달라요.

JPA IDENTITY와 SEQUENCE 전략 비교. IDENTITY는 DB가 INSERT 후에야 ID를 발급해 persist 즉시 INSERT가 나가 배치가 불가하고, SEQUENCE는 시퀀스에서 ID만 먼저 받아 쓰기 지연과 배치가 유지된다
  • IDENTITY — DB의 auto_increment가 ID를 만들어요. INSERT를 실행해야만 ID를 알 수 있어요. 그래서 persist 즉시 INSERT가 나가요. 쓰기 지연이 무력화되고 JDBC 배치가 안 돼요.
  • SEQUENCE — DB 시퀀스에서 ID만 먼저 받아와요. INSERT 없이 ID를 확보하니, 쓰기 지연이 유지되고 배치도 돼요.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "item_seq")
@SequenceGenerator(name = "item_seq", sequenceName = "item_seq", allocationSize = 50)
private Long id;

02. allocationSize, 시퀀스 호출도 아낀다

"persist마다 시퀀스를 부르면 그것도 왕복 아닌가?"라는 의문이 들죠. 맞아요. 그래서 allocationSize가 있어요.

JPA allocationSize 동작. 시퀀스를 50씩 증가시켜 한 번의 호출로 50개 구간을 확보하고 그 사이 번호는 메모리에서 할당한다. 서버 재시작 시 안 쓴 구간이 버려져 ID에 구멍이 생기는 건 정상이다

기본값 50이면, 시퀀스를 한 번 불러 1~50 구간을 통째로 확보하고 그 사이 번호는 메모리에서 나눠줘요. persist 50번에 시퀀스 왕복 1번이에요. 대량 insert에서 시퀀스가 병목이 되는 걸 막아줘요.

여기서 자주 받는 질문 — "ID가 중간중간 비어요(구멍). 버그인가요?" 정상이에요. 서버가 재시작되면 메모리에 확보해둔 미사용 구간이 버려지거든요. 트랜잭션 롤백 때도 ID는 안 돌아오고요(IDENTITY도 마찬가지예요). 기본키는 유일하면 되지 연속일 필요가 없어요. "결번 없는 번호"가 업무 요구라면 그건 기본키가 아니라 별도 채번 로직으로 풀어야 해요.

03. MySQL에는 시퀀스가 없어요

그럼 MySQL은요? MySQL은 시퀀스 객체를 지원하지 않아요. 그래서 선택지가 이렇게 돼요.

참고로 Hibernate 6에서 MySQL에 SEQUENCE(또는 AUTO)를 지정하면 에러가 나는 게 아니라, 채번 테이블({엔티티}_seq)을 조용히 만들어 폴백해요. Boot 3 마이그레이션 후 낯선 _seq 테이블이 생겼다면 이거예요.

MySQL의 ID 전략 선택지. 대부분은 IDENTITY를 유지하고 대량 적재 구간만 JdbcTemplate로 처리한다. TABLE 전략은 채번 테이블 락 경합으로 비추천이고, 앱에서 UUID v7이나 TSID를 채번하면 배치가 가능하지만 랜덤 UUID v4는 인덱스를 흩어 금물이다

대부분: IDENTITY 유지가 답이에요

일반 CRUD에서 "persist 즉시 INSERT"는 체감 문제가 아니에요. 단순함의 가치가 커서, MySQL + JPA의 사실상 표준은 IDENTITY예요. 배치가 필요한 대량 적재 구간만 JdbcTemplate.batchUpdate로 우회하는 게 현실적인 절충이에요(배치 편에서 본 그 결론이에요).

TABLE 전략은 피해요

시퀀스를 테이블로 흉내 내는 전략인데, 채번 테이블에 락 경합이 생겨서 동시성이 높으면 오히려 병목이 돼요. 요즘은 거의 안 써요.

분산·배치가 둘 다 필요하면: 애플리케이션 채번

애플리케이션이 직접 ID를 만들면(INSERT 전에 ID를 아니까) 배치가 되고, DB에 채번을 의존하지 않으니 샤딩·분산 환경에도 잘 맞아요. 다만 무엇으로 만드느냐가 중요해요.

04. UUID 기본키의 함정

"그럼 UUID를 기본키로 쓰면 되겠네"가 자연스러운 다음 생각인데, 함정이 있어요. 랜덤 UUID(v4)는 MySQL에서 쓰기 성능을 깎아먹어요.

InnoDB의 기본키는 클러스터드 인덱스예요 — 데이터가 기본키 순서로 물리적으로 정렬돼 저장돼요. auto_increment처럼 증가하는 키는 항상 "맨 뒤에 추가"라 빠른데, 랜덤 UUID는 삽입 위치가 매번 무작위라 페이지 분할이 계속 일어나요. 데이터가 쌓일수록 insert가 느려지고 인덱스도 부풀어요. (이 구조는 MySQL 인덱스 편에서 그림으로 깊게 다뤄요.)

그래서 애플리케이션 채번을 하려면 시간순으로 증가하는 ID를 써요.

  • UUID v7 — 앞부분이 타임스탬프라 대체로 증가해요. 표준이고 생태계 지원이 좋아요.
  • TSID / Snowflake 계열 — 타임스탬프 + 노드 ID + 시퀀스 조합의 64비트 ID예요. BIGINT에 들어가 UUID(16바이트)보다 인덱스가 작아요.

정리하면 "분산 친화 + 배치 가능 + 인덱스 친화"를 다 잡으려면 v7이나 TSID 계열이고, 랜덤 v4는 기본키로는 피하는 게 좋아요.

05. 자주 만나는 문제

saveAll 배치가 안 돼요

IDENTITY 전략이에요. 구조적인 한계라 설정으로 못 풀어요. SEQUENCE(가능한 DB라면)나 애플리케이션 채번으로 바꾸거나, 그 구간만 JdbcTemplate로 처리해요.

ID에 구멍이 나요

allocationSize 미사용 구간 폐기, 롤백 등으로 생기는 정상 동작이에요. 연속 번호가 업무 요구면 기본키와 분리된 채번으로 풀어요.

allocationSize와 DB 시퀀스 increment가 안 맞아 충돌해요

allocationSize는 DB 시퀀스의 INCREMENT BY와 같아야 해요. JPA는 50으로 아는데 시퀀스는 1씩 증가하면 ID가 겹치거나 검증 에러가 나요. 둘을 꼭 맞춰요.

UUID 기본키 테이블이 갈수록 insert가 느려져요

랜덤 v4의 페이지 분할이에요. v7·TSID로 바꾸거나, 내부 기본키는 auto_increment로 두고 UUID는 유니크 보조키로 내리는 설계를 검토해요.

정리

ID 전략의 본질은 "INSERT 전에 ID를 알 수 있는가"예요. IDENTITY는 몰라서 배치가 안 되고, SEQUENCE는 미리 받아서 배치가 돼요(allocationSize로 시퀀스 왕복까지 아끼고요). MySQL은 시퀀스가 없으니 대부분 IDENTITY로 두고 대량 구간만 JdbcTemplate로 우회하는 게 현실적이고, 분산과 배치를 둘 다 잡아야 하면 UUID v7·TSID 같은 시간순 앱 채번이에요. 랜덤 UUID를 기본키로만 안 쓰면 돼요.

그리고 마지막 한 편이 남았어요. 지금까지 흩어진 증상들 — N+1, 느린 조회, 대량 처리, Lazy 예외 — 을 증상별로 묶어 진단하는 트러블슈팅 허브로 시리즈를 닫아요.

출처: Hibernate User Guide — Identifiers · MySQL — Clustered and Secondary Indexes

반응형

📚 같이 보면 좋은

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 일정액의 수수료를 제공받습니다."