5.0 KiB
5.0 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label
| title | source_type | status | related_branches | related_projects | tags | created | status_label | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / hibernate7-hhh90003004-collection-fetch-paging-2026-07-13 | error-note | raw |
|
|
|
2026-07-13 | resolved |
error: hibernate7-hhh90003004-collection-fetch-paging-2026-07-13
Layer:
raw/errors/— 작업 중 마주친 단일 실패·놀라움 기록. 원본은 raw에 영구 보관한다.
Parent / 부모
- raw/branch-notes/experiment-nplus1-highlight-feed — N+1 랩 L4(컬렉션 fetch join + 페이징 → 인메모리 페이징) 실행 중 경고 캡처 테스트에서 발견.
증상 / Symptom
- 기대: 컬렉션 fetch join에 페이징(
setMaxResults)을 걸면 널리 알려진 경고 코드HHH000104(firstResult/maxResults specified with collection fetch; applying in memory)가 WARN으로 찍힌다. - 실제(Logback
ListAppender로org.hibernateWARN 캡처, 원문 그대로):HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory - 코드 번호가 다르다: 문서·다수 블로그가 말하는
HHH000104가 아니라HHH90003004. 메시지 본문 문구는 동일. - 파급: 회귀가드를
assertThat(warnings).anyMatch(m -> m.contains("HHH000104"))처럼 코드 번호만으로 매칭했다면 이 테스트는 거짓 실패했을 것이다. 실제로는|| m.contains("collection fetch")분기가 어서션을 통과시켰다. - 발생 환경: Java 21 · Spring Boot 4.0.0 · Hibernate ORM 7.1.8.Final · PostgreSQL 16(Testcontainers).
- 재현 가능 여부:
always.
재현 절차 / Reproduction
ca-tmpl에서FeedPersistenceIT(app-bootstrap)의l4EmitsHhh000104InMemoryPagingWarning테스트를 둔다:org.hibernate로거에ListAppender를 붙이고,select f from FeedItemJpaEntity f join fetch f.highlights order by ...에setFirstResult(0).setMaxResults(20)를 걸어getResultList()실행.cd src && ./gradlew :app-bootstrap:test --tests '*FeedPersistenceIT'(Docker 필요 — Testcontainers).LabReport.observe("L4 HHH000104 warning (verbatim)", ...)가 남긴 원문 확인 →HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory.- 결과: 테스트 GREEN(0 fail) — 단, 코드 번호로만 매칭했다면 red였을 것.
근본 원인 / Root cause
- 직접 원인: Hibernate ORM이 이 경고의 메시지 코드를 6→7 사이에 재부여했다.
HHH000104(구) →HHH90003004(현). 메시지 본문(firstResult/maxResults specified with collection fetch; applying in memory)과 의미(컬렉션 fetch join + 페이징 = DBLIMIT없이 결과셋 전체를 메모리로 올려 인메모리 페이징)는 그대로다. - 근본 원인: 로그 메시지 코드는 버전 간 안정 계약이 아니다. 널리 인용되는 코드 번호(
HHH000104)를 버전 불변 상수로 취급하면 상위 버전에서 매칭이 깨진다. - 트리거 조건: Hibernate 7.x에서 컬렉션 fetch join +
setMaxResults/setFirstResult(기본hibernate.query.fail_on_pagination_over_collection_fetch=false).
Sources / 근거
- 로컬 실측:
ca-tmpl:app-bootstrapFeedPersistenceIT.l4EmitsHhh000104InMemoryPagingWarning—ListAppender가 캡처한 WARN 원문이HHH90003004: ....build/lab-results/feed-nplus1.md의 "L4 HHH000104 warning (verbatim)" 관찰 블록에 원문 적재.:app-bootstrap:test --tests '*FeedPersistenceIT'GREEN(0 fail). - 런타임:
docs/notes/L4.md§"실측 정정" + 발표 문서topic-arrange/n+1liner/n+1liner.md§10 "⚠ 측정 정정" 콜아웃에 동일 원문 기록.
권고 해결 / Recommended resolution (적용됨)
- 적용: 경고 매칭을 코드 번호가 아니라 메시지 문구로도 하도록
m.contains("HHH000104") || m.contains("collection fetch")OR 매칭. Hibernate 버전이 코드를 다시 바꿔도(또는 카테고리/문구가 흔들려도) 견고. - 대안: 특정 버전에 고정하려면 실행 시 캡처한 원문을 먼저 확인해 정확한 현재 코드(
HHH90003004)로 좁힐 수 있으나, 상위 버전 이식성을 잃는다 → 랩에서는 문구 매칭을 채택.
교훈 / Lesson
- Hibernate 로그 메시지 코드는 버전 불변 계약이 아니다.
HHH######번호로 로그를 assert하면 상위 버전에서 조용히 깨진다 — 메시지 문구(의미를 담은 부분)로 매칭하는 편이 견고하다. - 로그 기반 테스트는 첫 실행에서 캡처한 원문을 반드시 확인하고(여기선
LabReport.observe), 매칭 조건을 그 원문에 맞춰 좁히거나(문구) 넓게(OR) 둔다. "널리 알려진 코드"를 상수로 하드코딩하지 않는다. - 같은 결의 정정이 이 랩에 하나 더 있다: L3의 "Hibernate 6+ 루트 자동 dedup"(fetch join 결과 리스트 크기 = Σ가 아니라 N) — ORM 버전이 관측 지표를 바꾸므로 실측으로 재확인해야 한다.