JPA를 사용하다 보면 로그에 실제 전달한 리스트는 152개인데, SQL 바인딩 시점에는 255개(혹은 256개)까지 늘어나 마지막 값이 반복해서 들어가는 현상을 볼 수 있습니다. 이는 `type.descriptor.sql.BasicBinder`가 수행하는 IN Clause Parameter Padding 때문입니다.
이 현상의 원인과 해결 방법을 정리합니다.
---
1. 왜 255개까지 늘어나는 걸까? (원인)
Hibernate는 성능 최적화를 위해 IN 절에 전달된 인자의 개수를 2의 거듭제곱(2, 4, 8, 16 ... 256) 단위로 맞추는 패딩 전략을 사용합니다.
* 이유: DB의 Execution Plan(실행 계획) 재사용성 때문입니다.
* 상세: 인자 개수가 152개일 때와 153개일 때 각각 다른 SQL로 인식되면 DB는 매번 새로운 실행 계획을 짜야 합니다. 패딩을 통해 둘 다 256개짜리 쿼리로 통일하면, DB는 이미 캐싱된 실행 계획을 재사용하여 CPU 소모를 줄일 수 있습니다.
---
2. 패딩을 방지하거나 조절하는 방법
상황에 따라 아래 세 가지 방법 중 하나를 선택할 수 있습니다.
# 방법 1: 패딩 기능 완전 비활성화
파라미터 개수가 늘어나는 것 자체가 싫고, 쿼리 종류가 많지 않아 실행 계획 재사용의 이점이 적다면 설정을 통해 끌 수 있습니다.
* application.yml 설정:
```yaml
spring:
jpa:
properties:
hibernate.query.in_clause_parameter_padding: false
```
> 주의: 이 설정을 끄면 `IN (1, 2, 3)`과 `IN (1, 2, 3, 4)`가 서로 다른 쿼리로 취급되어 DB 부하가 소폭 증가할 수 있습니다.
# 방법 2: 배치 사이즈(Batch Size) 제한
특정 연관 관계 조회에서 발생하는 문제라면 `@BatchSize`를 사용하여 한 번에 넘길 파라미터의 최대치를 강제로 고정할 수 있습니다.
```java
@BatchSize(size = 100) // 152개라면 100개, 52개로 나누어 실행됨
@OneToMany(mappedBy = "parent")
private List<Child> children;
```
# 방법 3: 애플리케이션 레벨에서 리스트 분할 (Partitioning)
가장 확실하게 제어하는 방법은 대량의 리스트를 직접 일정한 크기로 나누어 쿼리를 여러 번 호출하는 것입니다.
```java
// Google Guava 라이브러리 활용 예시
List<List<Long>> partitionedIds = Lists.partition(originalIds, 100);
for (List<Long> batch : partitionedIds) {
repository.findAllByIdIn(batch);
}
```
이 방식은 DB에 전달되는 파라미터 개수를 100개 이하로 엄격하게 관리할 수 있어 성능 예측이 쉬워집니다.
---
3. 요약 및 권장 사항
* 성능 중심: 패딩 기능을 켜두는 것이 좋습니다. (DB CPU 효율 증대)
* 로그 가독성 및 정밀 제어 중심: `in_clause_parameter_padding: false`로 설정하세요.
* 대량 데이터 처리: 설정과 무관하게 `Lists.partition` 등을 이용해 애플리케이션에서 100~500 단위로 끊어서 요청하는 것이 메모리와 성능 면에서 가장 안전합니다.
---
'IT > 프로그래밍' 카테고리의 다른 글
| @Transactional의 동작 (0) | 2025.12.13 |
|---|---|
| 상태 변수 이름 state, status 차이 (0) | 2025.10.23 |
| Offset Pagination과 Cursor Pagination (0) | 2024.03.27 |
| File up & download HTML (0) | 2020.08.07 |
| JAVA] A와 B 날짜 차이 계산하기 (0) | 2020.07.09 |