The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.2 KiB
id, kind, slug, title, topic, topicName, project, status, studio
| id | kind | slug | title | topic | topicName | project | status | studio |
|---|---|---|---|---|---|---|---|---|
| bf5f2462-0e94-4723-bdc8-f7dd709b2dbb | REFERENCE | top-n-per-group-selection | Top-N-per-group 선택 기준 | jpa-feed-query-performance | JPA 피드 조회 성능 | Liner N + 1문제 | 게시 전 | https://hyeonworks.com/studio/documents/bf5f2462-0e94-4723-bdc8-f7dd709b2dbb/edit |
Top-N-per-group 선택 기준
부모마다 상위 N개를 뽑는 일은 LIMIT으로 표현되지 않는다. 윈도우 함수, LATERAL, 애플리케이션 그룹핑 세 가지가 같은 결과를 만들지만 읽는 행수가 다르다.
관계
- Projection 이후에도 1,509행을 읽은 Row Over-fetch 이 기준이 풀려던 문제다.
- PostgreSQL Query Plan 측정 기준 세 방식을 실행계획으로 비교한 기준이다.
- Fetch Join · Batch · Projection 선택 기준 앞 단계에서 왕복과 적재를 푼 기준이다.
목적
자식 조회에 LIMIT을 붙이면 최종 결과 집합 전체에 적용되어 부모 하나만 채워진다. 그룹당 상한은 다른 표현이 필요하다.
세 방식은 결과가 같으므로 정확성만으로 고를 수 없다. 읽는 행수와 buffers로 갈린다.
규칙
1. 단순 LIMIT은 그룹당 상한이 아니다
LIMIT은 최종 결과 집합에 적용된다. 부모 20개를 조회하면서 LIMIT 3을 붙이면 3행만 남아 부모 하나만 채워진다.
이 오작동은 결과 행수가 적어 정상처럼 보일 수 있다. 커버한 부모 수를 함께 확인한다.
2. 세 가지 표현을 구분한다
윈도우 함수는 부모별로 순번을 매기고 상위 몇 개를 남긴다. 순번을 만들려고 파티션 전체를 읽는다.
LATERAL은 부모마다 상관 서브쿼리를 실행하고 인덱스에서 필요한 개수만 읽고 멈춘다.
애플리케이션 그룹핑은 자식을 한 번에 가져온 뒤 코드에서 자른다. 자르기 전에 전량이 전송된다.
3. 작은 K에는 LATERAL이 유리하다
부모별 정렬 인덱스가 있으면 LATERAL은 부모마다 K개만 읽고 멈춘다. 그룹이 크고 K가 작을수록 읽지 않는 행이 많아진다.
4. K가 그룹 크기에 가까우면 윈도우로 수렴한다
K가 그룹 크기에 가까워지면 LATERAL도 대부분을 읽는다. 이때는 더 단순한 윈도우 함수를 고를 수 있다.
K를 바꿔 가며 buffers를 재면 어느 지점에서 뒤집히는지 볼 수 있다.
5. LATERAL의 이점은 인덱스에서 나온다
LATERAL 문법 자체가 빠른 것이 아니다. 부모별 정렬 인덱스가 있어야 상위 K개를 바로 찾는다.
인덱스가 없으면 부모마다 자식 테이블을 스캔하고 대부분을 필터로 버린다. 인덱스 유무를 토글해 확인한다.
6. 애플리케이션 그룹핑은 전송량을 줄이지 않는다
코드에서 자르면 결과는 맞지만 DB가 전달한 행은 전량이다. 전송량이 문제인 상황에서는 해법이 아니다.
7. 표준 JPQL로 표현되지 않는다
윈도우 함수와 LATERAL은 표준 JPQL에 없다. native SQL로 내려가야 한다. 이 결정을 기록에 남긴다.
8. 반환 행수와 커버한 부모를 함께 검증한다
세 방식이 같은 결과를 만드는지 먼저 확인한 뒤 실행계획을 비교한다. 반환 행수, 커버한 부모 수, 부모당 최대 개수를 함께 본다.
적용 조건
- 목록 응답에 부모별 자식 상위 몇 개를 포함해야 할 때
- 자식 전량 조회가 전송량 문제를 만들 때
- 그룹 크기가 크고 필요한 개수가 작을 때
예외
- 그룹 크기가 작아 전량을 읽어도 부담이 없으면 애플리케이션 그룹핑이 단순하다.
- 부모별 정렬 인덱스를 만들 수 없으면 LATERAL의 이점이 사라진다. 이때는 윈도우 함수와 buffers를 비교해 고른다.
예시
- 순진 LIMIT 3 : 전체에 적용, 부모 1개만 채워짐
- 윈도우 : 부모별 순번 뒤 상위 K, 파티션 전량 읽음
- LATERAL : 부모마다 인덱스에서 K개 읽고 멈춤
- 2단계 : 자식 전량 전송 뒤 코드에서 그룹핑
- 인덱스 없는 LATERAL : 부모마다 Seq Scan, buffers 급증
- 선택 : 작은 K는 LATERAL, K가 그룹 크기에 근접하면 윈도우