feat: 가상화 문서들 추가

This commit is contained in:
DongHyeonka
2026-09-10 08:54:05 +09:00
parent e9f6a93327
commit 43e1aadef0
695 changed files with 153404 additions and 12754 deletions
@@ -9,12 +9,12 @@ topicName: JPA 피드 조회 성능
project: Liner N + 1문제
status: 게시 전
studio: "https://hyeonworks.com/studio/documents/c1158754-e3d2-47b8-bb41-81787c0ca84b/edit"
assets:
- key: in-memory-paging
file: ../../../final/assets/tech-log-studio/in-memory-paging.svg
evidence:
- ../../../final/evidence/raw/explain/l4-entity-paging-limit.txt
- ../../../final/evidence/raw/explain/l5-entity-paging-limit.txt
assets:
- key: batch-fetch-in-clause
file: ../../../final/assets/diagrams/batch-fetch-in-clause/batch-fetch-in-clause.svg
sourceRevision: n+1liner-lab@2026-08
source:
- final/document.md#10-2
@@ -129,9 +129,6 @@ used heap 델타가 아니라 스레드 누적 할당을 쓴 이유는 두 가
## 발행 SQL에 LIMIT이 없다
:::evidence key="in-memory-paging" alt="위쪽은 컬렉션 fetch join에 페이징을 건 경로로 조인 결과 전량이 애플리케이션으로 넘어와 메모리에서 잘리고, 아래쪽은 엔티티만 페이징한 경로로 정렬에 Limit이 붙어 DB가 페이지만 돌려주는 두 경로를 위아래로 대조한 그림." caption=" " zoom="true"
:::
```text label="seed(100) — (a) 컬렉션 fetch join / (b) 엔티티만 페이징"
-- (a) 컬렉션 fetch join의 조인 — Limit 노드 없음
Sort (... rows=1782 ...) (actual ... rows=1961 loops=1)
@@ -152,64 +149,10 @@ Limit (... rows=20 ...) (actual ... rows=20 loops=1)
## 엔티티만 페이징하고 배치로 하이라이트를 채우기
![조회 코드, Hibernate 세션, feed_items, highlights 네 참여자 사이에서 부모 페이징이 먼저 일어나고 그다음 자식 IN 배치 조회가 일어나는 순서도.](../../../final/assets/diagrams/batch-fetch-in-clause/batch-fetch-in-clause.svg)
fetch join을 버리고 엔티티만 페이징하면 LIMIT이 정상 발행되지만, highlights가 다시 지연 로딩이 되어 컬렉션 N+1이 돌아온다. 그래서 페이지 부모 키를 모아 IN으로 조회하는 Batch Fetch를 함께 적용했다.
이 실패도 프로덕션 코드에 섞지 않고 통합 테스트에 격리했다. 다음 단계의 전후 차이를 같은 기준으로 비교하기 위해서다.
<!-- body:end -->
<!-- local-preview:start — Studio로 전송되지 않는다. 본문은 body:start~body:end 사이만이다. -->
## 로컬 미리보기
본문 「무대 — 페이징 한 줄만 추가」 절의 코드
```java label="통합 테스트 안에서 세운 무대 (프로덕션 아님)"
"select f from FeedItemJpaEntity f join fetch f.highlights " // ← 한 bag fetch join
+ "order by f.firstHighlightedAt desc, f.id asc"
// + .setFirstResult(0).setMaxResults(20) // ← 방아쇠: 페이징
```
앞 단계의 데이터와 매핑을 그대로 두고 페이징 한 줄만 더했다. 새 엔티티·마이그레이션·시더·프로덕션 코드는 만들지 않았다.
## 응답은 한 페이지인데 부모는 전부 로드한다
| N | returned(페이지) | feedItemLoaded | over-fetch 배수 | 시드 하이라이트 |
|---:|---:|---:|---:|---:|
| 10 | 10 | 10 | 1.0× (안 보임) | 1,285 |
| 100 | 20 | 100 | 5.0× | 1,961 |
| 1,000 | 20 | 1,000 | 50.0× | 2,917 |
데이터가 커진 뒤에야 반환 크기와 실제 로드 수의 차이가 나타났다.
fetch join 쿼리는 FeedItem을 루트로 하이드레이트하므로 로드된 부모 수가 EntityStatistics.getLoadCount()에 잡힌다. 인메모리 페이징은 전체를 하이드레이트한 뒤 부모 목록을 자르기 때문에 returned가 20이어도 getLoadCount()는 N이다. getCollectionFetchCount()에는 join으로 로드된 컬렉션이 잡히지 않을 수 있어 이 단계의 지표로 쓰지 않았다.
## 경고 코드가 알려진 것과 달랐다
```text label="Hibernate ORM 7.1.8이 기록한 경고"
HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
```
널리 알려진 코드는 HHH000104지만 이 랩에서는 HHH90003004였다. 메시지 본문은 같았으므로 회귀 가드는 코드 번호만 비교하지 않고 contains("HHH000104") || contains("collection fetch")처럼 문구도 함께 확인하도록 만들었다.
## 비용은 페이지가 아니라 데이터셋에 비례한다
| N | 지연 중앙값(5회) | 지연 최댓값(5회) | 스레드 누적 할당 |
|---:|---:|---:|---:|
| 10 | 6.184 ms | 6.566 ms | 약 1.5 MB |
| 100 | 13.890 ms | 16.062 ms | 약 3.0 MB |
| 1,000 | 79.452 ms | 83.526 ms | 약 10.0 MB |
returned는 페이지 크기로 고정인데도 지연과 할당은 N이 커질수록 함께 올랐다. 페이징이 데이터를 줄이지 못했다는 시간·메모리 증거다.
지연은 예상과 달랐다. 컬렉션을 부모마다 따로 조회하던 loadFeed는 N=1,000에서 지연 최댓값이 238.4 ms였는데 이 fetch join은 83.526 ms였다. 컬렉션 N번 왕복이 조인 하나로 줄었기 때문이다. 그래도 이 값은 페이지에 필요하지 않은 부모 N개와 그 하이라이트를 전부 하이드레이트하면서 나온 것이다. 할당은 loadFeed 쪽을 재지 않아 두 구조의 메모리를 직접 비교한 값이 없다.
used heap 델타가 아니라 스레드 누적 할당을 쓴 이유는 두 가지다. used heap 델타는 측정 구간 사이의 GC 시점에 좌우되어 실행마다 흔들리고, JVM 전체 값이라 다른 스레드의 활동도 섞인다. getThreadAllocatedBytes는 GC와 무관하게 이 스레드가 만든 총량을 누적하므로 중간에 사라지는 객체까지 센다.
로드된 엔티티가 곧바로 GC 대상이 되는 것은 아니다. 반환 리스트만 페이지 크기로 잘릴 뿐 영속성 컨텍스트가 나머지를 붙들고 있어서 em.clear나 트랜잭션 종료 전까지 남는다. 시드 1,000에 페이지 20으로 확인하니 반환은 20건인데 영속성 컨텍스트 엔티티는 4,937개였다. 같은 실행의 used heap 델타 22,016 KB는 스레드 누적 할당 19,995 KB보다 컸다.
## 발행 SQL에 LIMIT이 없다」 아래 `:::evidence key="in-memory-paging"` 자리에 들어갈 그림이다.
![위쪽은 컬렉션 fetch join에 페이징을 건 경로로 조인 결과 전량이 애플리케이션으로 넘어와 메모리에서 잘리고, 아래쪽은 엔티티만 페이징한 경로로 정렬에 Limit이 붙어 DB가 페이지만 돌려주는 두 경로를 위아래로 대조한 그림.](../../../final/assets/tech-log-studio/in-memory-paging.svg)
<!-- local-preview:end -->
@@ -11,7 +11,7 @@ status: 게시 전
studio: "https://hyeonworks.com/studio/documents/32d0be7d-d88e-4760-8d91-35d3a233a99a/edit"
assets:
- key: eager-lazy-query-sequence
file: ../../../final/assets/tech-log-studio/eager-lazy-query-sequence.svg
file: ../../../final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg
evidence:
- ../../../final/evidence/raw/explain/highlights-child-plan-A.txt
- ../../../final/evidence/raw/explain/toone-pages-plan.txt
@@ -110,8 +110,7 @@ public List<FeedSummary> loadFeed(int page, int size) {
## EAGER 연관 관계에서 나간 추가 조회
:::evidence key="eager-lazy-query-sequence" alt="loadFeed 매핑, Hibernate, PostgreSQL 세 참가자 사이에서 루트 SELECT가 먼저 실행되고, fetch join되지 않은 EAGER user와 page가 별도의 2차 SELECT로 채워진 뒤, 매핑이 getHighlights에 접근하는 순간 지연 로딩 컬렉션 SELECT가 실행되는 순서를 보여 주는 시퀀스." caption=" " zoom="true"
:::
![loadFeed 매핑, Hibernate, PostgreSQL 세 참가자 사이에서 루트 SELECT가 먼저 실행되고, fetch join되지 않은 EAGER user와 page가 별도의 2차 SELECT로 채워진 뒤, 매핑이 getHighlights에 접근하는 순간 지연 로딩 컬렉션 SELECT가 실행되는 순서를 보여 주는 시퀀스.](../../../final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg)
`getEntityFetchCount()`는 실행된 SELECT SQL 수가 아니라 2차 fetch로 초기화된 엔티티 수를 센다. 아래 표의 ToOne 합이 이 값이고, Page fetch와 User fetch는 `getEntityStatistics(...).getFetchCount()`로 엔티티마다 따로 읽었다.
@@ -197,13 +196,3 @@ Execution Time: 0.173 ms
추정 rows=1과 실제 rows=500은 500배 차이가 난다. 대량 시드 직후 ANALYZE를 실행하지 않아 통계가 feed_item_id별 편중을 담지 못했다는 가설을 세웠고, 아직 검증하지 않았다.
<!-- body:end -->
<!-- local-preview:start — Studio로 전송되지 않는다. 본문은 body:start~body:end 사이만이다. -->
## 로컬 미리보기
본문 「EAGER 연관 관계에서 나간 추가 조회」의 `:::evidence key="eager-lazy-query-sequence"` 구획에 들어갈 그림이다.
![loadFeed 매핑, Hibernate, PostgreSQL 세 참가자 사이에서 루트 SELECT가 먼저 실행되고, fetch join되지 않은 EAGER user와 page가 별도의 2차 SELECT로 채워진 뒤, 매핑이 getHighlights에 접근하는 순간 지연 로딩 컬렉션 SELECT가 실행되는 순서를 보여 주는 시퀀스.](../../../final/assets/tech-log-studio/eager-lazy-query-sequence.svg)
<!-- local-preview:end -->
@@ -11,7 +11,7 @@ status: 게시 전
studio: "https://hyeonworks.com/studio/documents/7ed75172-fd56-42bf-956a-8f9fc1cca235/edit"
assets:
- key: cartesian-row-multiplication
file: ../../../final/assets/tech-log-studio/cartesian-row-multiplication.svg
file: ../../../final/assets/diagrams/cartesian-row-multiplication/cartesian-row-multiplication.svg
evidence:
- ../../../final/evidence/raw/explain/l3-cartesian-join-plan.txt
sourceRevision: n+1liner-lab@2026-08
@@ -108,8 +108,7 @@ java.lang.IllegalArgumentException <- org.hibernate.loader.MultipleBagFetchExcep
컬렉션을 `highlights` 하나만 fetch join하면 예외는 나지 않는다. 대신 `feed_items`와 `highlights`의 조인이 부모 한 행을 자식 수만큼 반복해 내보내므로, 쿼리 수가 아니라 DB가 애플리케이션에 전달한 조인 행수를 쟀다.
:::evidence key="cartesian-row-multiplication" alt="왼쪽 부모 테이블에서 출발한 조인이 부모 한 행을 자식 수만큼 반복한 행 묶음으로 만들어 오른쪽 전송 단계로 내보내고, 아래쪽에서 Hibernate 6 이상이 루트 엔티티 중복 제거해 결과 리스트를 부모 수로 되돌리지만 늘어난 행은 SQL과 전송 단계에 남는다는 것을 보여 주는 그림." caption=" " zoom="true"
:::
![feed_items와 highlights가 fetch join으로 합쳐져 전송 조인 행이 되고, 그 행이 결과 리스트로 갈 때만 루트 엔티티 중복 제거되는 흐름.](../../../final/assets/diagrams/cartesian-row-multiplication/cartesian-row-multiplication.svg)
| N | 전송 행수(조인 카디널리티) | 리스트 크기(Hib6 dedup) | distinct 아이템 | 시드 하이라이트 | 폭발 배수 | 총 PreparedStatement |
|---:|---:|---:|---:|---:|---:|---:|
@@ -163,14 +162,3 @@ Execution Time: 0.959 ms
`.distinct()`를 붙이거나 `List`를 `Set`으로 바꾸거나 `@BatchSize`로 바로 우회하지 않고, 실패한 쿼리를 별도 통합 테스트에 남겼다. 그래야 fetch join이 만든 페이징 문제와 그다음 Batch Fetch 선택까지 이어서 확인할 수 있기 때문이다.
<!-- body:end -->
<!-- local-preview:start — Studio로 전송되지 않는다. 본문은 body:start~body:end 사이만이다. -->
## 로컬 미리보기
본문 「실패 둘 — 컬렉션 하나만 합치면 전송 행수가 늘어난다」 절의 첫 문단 다음
`:::evidence key="cartesian-row-multiplication"`에 들어갈 그림이다.
![왼쪽 부모 테이블에서 출발한 조인이 부모 한 행을 자식 수만큼 반복한 행 묶음으로 만들어 오른쪽 전송 단계로 내보내고, 아래쪽에서 Hibernate 6 이상이 루트 엔티티를 중복 제거해 결과 리스트를 부모 수로 되돌리지만 늘어난 행은 SQL과 전송 단계에 남는다는 것을 보여 주는 그림.](../../../final/assets/tech-log-studio/cartesian-row-multiplication.svg)
<!-- local-preview:end -->
@@ -11,7 +11,7 @@ status: 게시 전
studio: "https://hyeonworks.com/studio/documents/4c9c3b90-bc89-4300-9334-088ea95d37d8/edit"
assets:
- key: projection-row-over-fetch
file: ../../../final/assets/tech-log-studio/projection-row-over-fetch.svg
file: ../../../final/assets/diagrams/projection-row-over-fetch/projection-row-over-fetch.svg
evidence:
- ../../../final/evidence/raw/explain/l6-parent-projection.txt
sourceRevision: n+1liner-lab@2026-08
@@ -120,8 +120,7 @@ FeedSummary의 마지막 인자가 리스트라 생성자 표현식 한 번으
## 남은 비용 — 페이지당 전량
:::evidence key="projection-row-over-fetch" alt="왼쪽 엔티티 적재에서 가운데 스칼라 프로젝션으로 넘어가면서 엔티티 생성이 사라지지만, 오른쪽 자식 조회는 페이지 부모의 자식을 전부 가져와 행수는 줄지 않는 것을 보여 주는 그림." caption=" " zoom="true"
:::
![페이지 부모에서 자식 IN 조회를 거쳐 자식 행 전량이 나오고, 화면이 그중 부모별 최신 몇 개만 쓰는 흐름. IN 조회 상자에는 엔티티 적재가 없다는 표시가 붙어 있다.](../../../final/assets/diagrams/projection-row-over-fetch/projection-row-over-fetch.svg)
| 항목 | 값 |
|---|---:|
@@ -155,47 +154,3 @@ Hash Semi Join (... rows=1509 loops=1)
기존 loadFeed를 바로 교체하면 앞 단계의 값을 다시 측정할 수 없어서, loadFeedProjection을 별도 메서드로 추가하고 같은 데이터로 비교했다. 처음 구현부터 배치까지의 테스트도 다시 실행해 기존 결과가 유지되는지 확인했다.
<!-- body:end -->
<!-- local-preview:start — Studio로 전송되지 않는다. 본문은 body:start~body:end 사이만이다. -->
## 로컬 미리보기
본문 「두 개의 스칼라 프로젝션
```java label="loadFeedProjection — 부모(A)와 자식(B)을 각각 스칼라로"
// (A) 부모 스칼라 프로젝션 — 조인은 컬럼 접근용, 페이징은 엔티티에
select new FeedItemProjectionRow(f.id, u.name, u.username, p.url, p.title, f.firstHighlightedAt)
from FeedItemJpaEntity f join f.user u join f.page p
order by f.firstHighlightedAt desc, f.id asc // + setMaxResults(20) → LIMIT
// (B) 그 20개 부모의 하이라이트를 필요 컬럼만 IN 한 방으로 → feedItemId 로 그룹핑
select new HighlightProjectionRow(h.feedItem.id, h.color, h.text, h.createdAt)
from HighlightJpaEntity h where h.feedItem.id in (:pageIds)
```
부모 쿼리에는 컬렉션 조인을 넣지 않았다. user와 page는 컬럼을 읽기 위한 조인이라 행이 곱해지지 않고, setMaxResults가 만드는 LIMIT도 DB에서 그대로 적용된다.
FeedSummary의 마지막 인자가 리스트라 생성자 표현식 한 번으로 만들 수 없었다. 부모와 자식을 각각 스칼라 캐리어로 조회한 뒤 메모리에서 조립했다.
## 엔티티 로드가 0으로 줄어든다
| 지표 | 배치 | 프로젝션 |
|---|---:|---:|
| entitiesLoaded (seed 1,000) | 1,569 | 0 |
| prepared (N=1,000) | 23 | 2 |
| collectionFetch (N=1,000) | 10 | 0 |
## N이 늘어도 쿼리는 2개다
| N | 순진(1+N) | 배치(1+ceil(N/batch)·연관) | 프로젝션(상수) |
|---:|---:|---:|---:|
| 10 | 25 | 5 | 2 |
| 100 | 222 | 5 | 2 |
| 1,000 | 2,022 | 23 | 2 |
처음 구현한 loadFeed의 쿼리 수는 N에 비례해 늘었고 배치는 배치 크기 단위로 늘었다. 프로젝션은 2개로 유지되었다.
## 남은 비용 — 페이지당 전량」 아래 `:::evidence key="projection-row-over-fetch"` 자리에 들어갈 그림이다.
![왼쪽 엔티티 적재에서 가운데 스칼라 프로젝션으로 넘어가면서 엔티티 생성이 사라지지만, 오른쪽 자식 조회는 페이지 부모의 자식을 전부 가져와 행수는 줄지 않는 것을 보여 주는 그림.](../../../final/assets/tech-log-studio/projection-row-over-fetch.svg)
<!-- local-preview:end -->
@@ -9,9 +9,6 @@ topicName: JPA 피드 조회 성능
project: Liner N + 1문제
status: 게시 전
studio: "https://hyeonworks.com/studio/documents/e6715e81-6dbd-4287-8e19-946c334f38fb/edit"
assets:
- key: keyset-vs-offset
file: ../../../final/assets/tech-log-studio/keyset-vs-offset.svg
evidence:
- ../../../final/evidence/raw/explain/l15-keyset-no-index.txt
- ../../../final/evidence/raw/explain/l15-offset-deep-page.txt
@@ -82,9 +79,6 @@ ix_feed_items_visibility_sort : (visibility, first_highlighted_at DESC, id)
## 가시성을 얹기 전 — 커서 이후 20행만 읽기
:::evidence key="keyset-vs-offset" alt="위쪽 OFFSET 막대는 정렬 순서상 앞에 있어 만들어졌다가 버려지는 빗금 구간과 실제 반환되는 진한 구간으로 나뉜다. 아래쪽 keyset 막대는 아예 읽지 않는 빈 구간과 커서 표시 뒤의 페이지 구간으로 나뉜다. 페이지가 깊어질수록 위쪽 빗금 구간만 길어진다." caption=" " zoom="true"
:::
seed 2,000에서 100번째 페이지(offset 1980)를 요청하고 두 방식의 실행계획을 대조했다.
```text label="keyset + 정렬키 인덱스, 깊은 페이지"
@@ -146,13 +140,3 @@ keyset이 `Index Only Scan`으로 성립하려면 정렬키·커서·인덱스
실제 단일 OR은 `Seq Scan`이 아니라 `BitmapOr`와 top-N `Sort`, hashed SubPlan을 썼다. `UNION` 분해는 요청할 때마다 세 분기를 각각 스캔했고, 그래서 읽은 블록은 단일 OR보다 많았다. 읽은 블록을 가장 줄인 방식은 `UNION` 분해가 아니라 사전계산이었다. 사전계산은 조회를 단일 `Index Only Scan`으로 바꾸는 대신, 피드·멘션·가시성이 바뀔 때마다 읽기 모델을 갱신해야 하고 조회 사용자 수만큼 저장 공간도 늘어난다.
<!-- body:end -->
<!-- local-preview:start — Studio로 전송되지 않는다. 본문은 body:start~body:end 사이만이다. -->
## 로컬 미리보기
본문 「가시성을 얹기 전 — 커서 이후 20행만 읽기」 아래 `:::evidence key="keyset-vs-offset"` 자리에 들어갈 그림이다.
![위쪽 OFFSET 막대는 정렬 순서상 앞에 있어 만들어졌다가 버려지는 빗금 구간과 실제 반환되는 진한 구간으로 나뉜다. 아래쪽 keyset 막대는 아예 읽지 않는 빈 구간과 커서 표시 뒤의 페이지 구간으로 나뉜다. 페이지가 깊어질수록 위쪽 빗금 구간만 길어진다.](../../../final/assets/tech-log-studio/keyset-vs-offset.svg)
<!-- local-preview:end -->
@@ -7,7 +7,7 @@
"revision": "761384d7b317469f8b4bb9c243a73419c00ea3a1",
"verified": "이 커밋에 문서가 인용한 IT 7종(FeedPersistenceIT·FeedBatchFetchIT·FeedProjectionIT·FeedTopNIT·FeedKeysetIT·FeedVisibilityIT·FeedCrownIT)이 모두 있다. 저장소 HEAD 는 nplus1-replay-l3 브랜치라 이 커밋이 아니다"
},
"ssotSha256": "a32d9e8ba07129fc26deeef2ce07e3624ce6f34888d6cf503d0b7f226bbb4b6b",
"ssotSha256": "bb33f443aaffe90be9e848fa49864b299a2ab628c300e2662ef32ac1c4d6964a",
"sourceRevision": "n+1liner-lab@2026-08",
"generatedAt": "2026-09-07",
"candidateScope": {
@@ -62,6 +62,48 @@
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
}
},
"assetLedger": {
"note": "final/assets/diagrams/ 의 그림 10장 가운데 글감에 배정한 것과, 배정하지 않은 것의 이유를 적는다",
"assigned": [
"cartesian-row-multiplication",
"projection-row-over-fetch",
"eager-lazy-query-sequence",
"batch-fetch-in-clause"
],
"unassigned": [
{
"asset": [
"nplus1-query-fanout"
],
"reason": "컬렉션(Highlight) N+1 의 팬아웃을 그린 그림인데, 그 주장을 담은 Case 가 없다. case:eager-toone-nplus1-without-access 는 ToOne(user·page) 을 다루고 case:collection-fetch-join-in-memory-paging 은 fetch join 과 페이징을 다룬다"
},
{
"asset": [
"baseline-schema",
"target-schema",
"query-port-boundary",
"strategy-journey"
],
"reason": "데이터 모델·포트 경계·전체 여정을 그린 그림이라 글감 하나에 속하지 않는다. final/document.md 가 제자리에서 싣는다"
},
{
"asset": [
"skew-profile"
],
"reason": "표로 되는 그림이다 — 분포 셋을 네 속성으로 늘어놓았을 뿐 관계선이 없다. §4.3 에 순위별 하이라이트 수 표가 이미 있다"
}
],
"removed": [
{
"asset": [
"in-memory-paging",
"keyset-vs-offset",
"top-n-per-group"
],
"reason": "표로 되는 그림이라 지웠다. 같은 비교가 기록의 표에 더 자세히 있었다"
}
]
},
"topics": {
"jpa-feed-query-performance": {
"topic": "jpa-feed-query-performance",
@@ -93,6 +135,9 @@
"reference:fetch-type-vs-fetch-strategy",
"decision:measure-plan-on-real-postgresql"
],
"ssot-assets": [
"eager-lazy-query-sequence"
],
"kind": "case",
"publication": "게시됨",
"file": "jpa-feed-query-performance/case/case-eager-toone-nplus1-without-access.md",
@@ -101,6 +146,9 @@
"assets": [
"eager-lazy-query-sequence"
],
"assetFiles": [
"eager-lazy-query-sequence"
],
"evidenceFiles": [
"../../../final/evidence/raw/explain/highlights-child-plan-A.txt",
"../../../final/evidence/raw/explain/toone-pages-plan.txt",
@@ -131,6 +179,9 @@
"reference:fetch-strategy-selection",
"decision:no-collection-fetch-join-with-pagination"
],
"ssot-assets": [
"cartesian-row-multiplication"
],
"kind": "case",
"publication": "게시됨",
"file": "jpa-feed-query-performance/case/case-fetch-join-multibag-and-row-explosion.md",
@@ -139,6 +190,9 @@
"assets": [
"cartesian-row-multiplication"
],
"assetFiles": [
"cartesian-row-multiplication"
],
"evidenceFiles": [
"../../../final/evidence/raw/explain/l3-cartesian-join-plan.txt"
]
@@ -166,12 +220,18 @@
"decision:no-collection-fetch-join-with-pagination"
],
"kind": "case",
"ssot-assets": [
"batch-fetch-in-clause"
],
"publication": "게시됨",
"file": "jpa-feed-query-performance/case/case-collection-fetch-join-in-memory-paging.md",
"status": "게시 전",
"studioId": "c1158754-e3d2-47b8-bb41-81787c0ca84b",
"assets": [
"in-memory-paging"
"batch-fetch-in-clause"
],
"assetFiles": [
"batch-fetch-in-clause"
],
"evidenceFiles": [
"../../../final/evidence/raw/explain/l4-entity-paging-limit.txt",
@@ -201,6 +261,9 @@
"relations": [
"decision:read-projection-for-screen-query"
],
"ssot-assets": [
"projection-row-over-fetch"
],
"kind": "case",
"publication": "게시됨",
"file": "jpa-feed-query-performance/case/case-projection-row-over-fetch.md",
@@ -209,6 +272,9 @@
"assets": [
"projection-row-over-fetch"
],
"assetFiles": [
"projection-row-over-fetch"
],
"evidenceFiles": [
"../../../final/evidence/raw/explain/l6-parent-projection.txt"
]
@@ -242,9 +308,8 @@
"file": "jpa-feed-query-performance/case/case-visibility-or-breaks-keyset-index.md",
"status": "게시 전",
"studioId": "e6715e81-6dbd-4287-8e19-946c334f38fb",
"assets": [
"keyset-vs-offset"
],
"assets": [],
"assetFiles": [],
"evidenceFiles": [
"../../../final/evidence/raw/explain/l15-keyset-no-index.txt",
"../../../final/evidence/raw/explain/l15-offset-deep-page.txt"
@@ -274,6 +339,7 @@
"status": "게시 전",
"studioId": "b0b55ac9-c0a3-4c01-ba84-0aa478923ace",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -296,6 +362,7 @@
"status": "게시 전",
"studioId": "51095f6e-2cc8-439c-8648-065033614215",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -320,6 +387,7 @@
"status": "게시 전",
"studioId": "db99cbc5-9123-4599-b368-39ff3170e81d",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -342,6 +410,7 @@
"status": "게시 전",
"studioId": "bf5f2462-0e94-4723-bdc8-f7dd709b2dbb",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -365,6 +434,7 @@
"status": "게시 전",
"studioId": "06788903-3dfa-4f70-b159-f1224384fd0b",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -387,6 +457,7 @@
"status": "게시 전",
"studioId": "635fcedd-d402-4297-bcf3-9fcdf4200d28",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -411,6 +482,7 @@
"status": "게시 전",
"studioId": "e8c2e9ea-cd87-46f8-9469-849dbd433d86",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
}
],
@@ -436,6 +508,7 @@
"status": "게시 전",
"studioId": "e1e0e2a0-c6b6-45bf-be42-f697ba5e2fff",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -459,6 +532,7 @@
"status": "게시 전",
"studioId": "5159c415-232d-424a-970a-b0db52746767",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -483,6 +557,7 @@
"status": "게시 전",
"studioId": "b099ca65-bf9f-4d61-814c-74722453fa3c",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -508,6 +583,7 @@
"status": "게시 전",
"studioId": "5088ce14-b096-41d3-abba-64b7afb48bb9",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -531,6 +607,7 @@
"status": "게시 전",
"studioId": "6cbe963f-86f8-4df6-be5a-900712970d01",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
}
],
@@ -559,6 +636,7 @@
"status": "게시 전",
"studioId": "ae6c9bea-d3a3-46e1-bbd4-8d580d336394",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -584,6 +662,7 @@
"status": "게시 전",
"studioId": "4e3200c8-eff5-4442-ae84-ae7b7fa92c8b",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -611,6 +690,7 @@
"status": "게시 전",
"studioId": "5e4d033c-d6fe-4257-a4dc-1ade44473c72",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -636,6 +716,7 @@
"status": "게시 전",
"studioId": "08a74b35-10c3-4874-8fbc-209b0b6e942e",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -663,6 +744,7 @@
"status": "게시 전",
"studioId": "30a37f34-b406-4061-b924-e22e0be0c3bf",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -690,6 +772,7 @@
"status": "게시 전",
"studioId": "1dbce381-f0dc-4d49-ad68-bd31d205677e",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
},
{
@@ -718,6 +801,7 @@
"status": "게시 전",
"studioId": "7f248f68-ce2b-43ec-94ce-82324d0bd1a7",
"assets": [],
"assetFiles": [],
"evidenceFiles": []
}
]