562 lines
38 KiB
YAML
562 lines
38 KiB
YAML
- generic [ref=f127e3]:
|
|
- link "본문으로 건너뛰기" [ref=f127e4] [cursor=pointer]:
|
|
- /url: "#main-content"
|
|
- banner [ref=f127e5]:
|
|
- generic [ref=f127e6]:
|
|
- link "TechLog Studio" [ref=f127e7] [cursor=pointer]:
|
|
- /url: /studio
|
|
- text: TechLog
|
|
- generic [ref=f127e8]: Studio
|
|
- navigation "Studio 주 탐색" [ref=f127e10]:
|
|
- link "작업본" [ref=f127e11] [cursor=pointer]:
|
|
- /url: /studio/documents
|
|
- link "게시 기록" [ref=f127e12] [cursor=pointer]:
|
|
- /url: /studio/publications
|
|
- link "새 문서" [ref=f127e13] [cursor=pointer]:
|
|
- /url: /studio/documents/new
|
|
- link "주제·프로젝트" [ref=f127e14] [cursor=pointer]:
|
|
- /url: /studio/taxonomy
|
|
- link "릴리즈" [ref=f127e15] [cursor=pointer]:
|
|
- /url: /studio/releases
|
|
- link "공개 사이트 보기" [ref=f127e16] [cursor=pointer]:
|
|
- /url: /
|
|
- button "로그아웃" [ref=f127e17]
|
|
- main [ref=f127e18]:
|
|
- generic [ref=f127e19]:
|
|
- generic [ref=f127e20]:
|
|
- region [ref=f127e21]:
|
|
- generic [ref=f127e22]:
|
|
- paragraph [ref=f127e23]: REFERENCE · VERSION 5
|
|
- heading "문서 편집" [level=1] [ref=f127e24]
|
|
- paragraph [ref=f127e25]: Top-N-per-group 선택 기준
|
|
- region [ref=f127e26]:
|
|
- generic [ref=f127e27]:
|
|
- paragraph [ref=f127e28]: DOCUMENT
|
|
- heading "기본 정보" [level=2] [ref=f127e29]
|
|
- generic [ref=f127e30]:
|
|
- generic [ref=f127e31]:
|
|
- generic [ref=f127e32]: 제목
|
|
- textbox "제목" [ref=f127e33]: Top-N-per-group 선택 기준
|
|
- generic [ref=f127e34]:
|
|
- generic [ref=f127e35]: slug
|
|
- textbox "slug" [ref=f127e36]:
|
|
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
|
|
- text: top-n-per-group-selection
|
|
- generic [ref=f127e37]:
|
|
- generic [ref=f127e38]: 요약
|
|
- textbox "요약" [ref=f127e39]: 부모마다 상위 N개를 뽑는 일은 LIMIT으로 표현되지 않는다. 윈도우 함수, LATERAL, 애플리케이션 그룹핑 세 가지가 같은 결과를 만들지만 읽는 행수가 다르다.
|
|
- generic [ref=f127e40]: 목록 카드에는 약 90자까지 보입니다 · 93 / 2000
|
|
- generic [ref=f127e41]:
|
|
- generic [ref=f127e42]: Topic
|
|
- combobox "Topic" [ref=f127e43]:
|
|
- option "선택하지 않음"
|
|
- option "JPA 피드 조회 성능" [selected]
|
|
- option "OAuth/OIDC 인증 경계"
|
|
- generic [ref=f127e44]:
|
|
- generic [ref=f127e45]: Project
|
|
- combobox "Project" [ref=f127e46]:
|
|
- option "미지정"
|
|
- option "Backend Clean Architecture"
|
|
- option "KeyCloak Patterns"
|
|
- option "Liner N + 1문제" [selected]
|
|
- status [ref=f127e47]
|
|
- group "관계" [ref=f127e48]:
|
|
- generic [ref=f127e50]:
|
|
- generic [ref=f127e51]:
|
|
- generic [ref=f127e52]: 관계 1 대상
|
|
- combobox "관계 1 대상" [ref=f127e53]:
|
|
- option "대상 선택"
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option "실제 동시 트래픽에서도 이 구조가 안정적인가"
|
|
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
|
|
- option "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가"
|
|
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
- option "Authorization Code와 PKCE가 보호하는 구간"
|
|
- option "Bearer JWT가 인증된 principal이 되기까지"
|
|
- option "BFF 인증 구조 설계 기준"
|
|
- option "BFF가 OAuth Token을 관리하는 조건"
|
|
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
|
|
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
|
|
- option "Collection Fetch Join Pagination의 In-memory Paging"
|
|
- option "Cookie로 인증하는 요청에서 CSRF token이 하는 일"
|
|
- option "브라우저가 credential을 보관하는 위치와 그 성질"
|
|
- option "DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1"
|
|
- option "필드 접근 없이 발생한 EAGER ToOne N+1"
|
|
- option "Feed Visibility Query Pattern"
|
|
- option "feed_visible을 Production CQRS로 승격할 것인가"
|
|
- option "Fetch Join · Batch · Projection 선택 기준" [disabled]
|
|
- option "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증"
|
|
- option "Fetch Type과 Fetch Strategy 구분"
|
|
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
|
|
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
|
|
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
|
|
- option "Forward-Auth와 Nginx auth_request의 동작"
|
|
- option "Highlight 없는 FeedItem을 허용할 것인가"
|
|
- option "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다."
|
|
- option "외부 IdP 연동과 Application 인증 구조의 경계"
|
|
- option "외부 IdP Brokering의 동작"
|
|
- option "JPA N+1 정량 진단 기준"
|
|
- option "Keyset Pagination 설계 기준"
|
|
- option "OAuth/OIDC 인증 패턴 선택 기준"
|
|
- option "OAuth Token과 Application Session을 구분하는 기준"
|
|
- option "PostgreSQL Query Plan 측정 기준" [disabled]
|
|
- option "Projection 이후에도 1,509행을 읽은 Row Over-fetch" [selected]
|
|
- option "Public Client와 Confidential Client 구분 기준"
|
|
- option "Query Plan은 실제 PostgreSQL에서 측정한다"
|
|
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
|
|
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
|
|
- option "Round Trip과 Row Volume을 독립 측정할 것인가"
|
|
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
|
|
- option "Top-N-per-group 선택 기준"
|
|
- option "Visibility OR이 Keyset Index를 깨뜨린 문제"
|
|
- generic [ref=f127e54]:
|
|
- generic [ref=f127e55]: 관계 1 이유
|
|
- textbox "관계 1 이유" [ref=f127e56]: 이 기준이 풀려던 문제다.
|
|
- generic [ref=f127e57]:
|
|
- button "위로" [disabled] [ref=f127e58]
|
|
- button "아래로" [ref=f127e59]
|
|
- button "삭제" [ref=f127e60]
|
|
- generic [ref=f127e61]:
|
|
- generic [ref=f127e62]:
|
|
- generic [ref=f127e63]: 관계 2 대상
|
|
- combobox "관계 2 대상" [ref=f127e64]:
|
|
- option "대상 선택"
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option "실제 동시 트래픽에서도 이 구조가 안정적인가"
|
|
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
|
|
- option "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가"
|
|
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
- option "Authorization Code와 PKCE가 보호하는 구간"
|
|
- option "Bearer JWT가 인증된 principal이 되기까지"
|
|
- option "BFF 인증 구조 설계 기준"
|
|
- option "BFF가 OAuth Token을 관리하는 조건"
|
|
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
|
|
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
|
|
- option "Collection Fetch Join Pagination의 In-memory Paging"
|
|
- option "Cookie로 인증하는 요청에서 CSRF token이 하는 일"
|
|
- option "브라우저가 credential을 보관하는 위치와 그 성질"
|
|
- option "DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1"
|
|
- option "필드 접근 없이 발생한 EAGER ToOne N+1"
|
|
- option "Feed Visibility Query Pattern"
|
|
- option "feed_visible을 Production CQRS로 승격할 것인가"
|
|
- option "Fetch Join · Batch · Projection 선택 기준" [disabled]
|
|
- option "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증"
|
|
- option "Fetch Type과 Fetch Strategy 구분"
|
|
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
|
|
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
|
|
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
|
|
- option "Forward-Auth와 Nginx auth_request의 동작"
|
|
- option "Highlight 없는 FeedItem을 허용할 것인가"
|
|
- option "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다."
|
|
- option "외부 IdP 연동과 Application 인증 구조의 경계"
|
|
- option "외부 IdP Brokering의 동작"
|
|
- option "JPA N+1 정량 진단 기준"
|
|
- option "Keyset Pagination 설계 기준"
|
|
- option "OAuth/OIDC 인증 패턴 선택 기준"
|
|
- option "OAuth Token과 Application Session을 구분하는 기준"
|
|
- option "PostgreSQL Query Plan 측정 기준" [selected]
|
|
- option "Projection 이후에도 1,509행을 읽은 Row Over-fetch" [disabled]
|
|
- option "Public Client와 Confidential Client 구분 기준"
|
|
- option "Query Plan은 실제 PostgreSQL에서 측정한다"
|
|
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
|
|
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
|
|
- option "Round Trip과 Row Volume을 독립 측정할 것인가"
|
|
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
|
|
- option "Top-N-per-group 선택 기준"
|
|
- option "Visibility OR이 Keyset Index를 깨뜨린 문제"
|
|
- generic [ref=f127e65]:
|
|
- generic [ref=f127e66]: 관계 2 이유
|
|
- textbox "관계 2 이유" [ref=f127e67]: 세 방식을 실행계획으로 비교한 기준이다.
|
|
- generic [ref=f127e68]:
|
|
- button "위로" [ref=f127e69]
|
|
- button "아래로" [ref=f127e70]
|
|
- button "삭제" [ref=f127e71]
|
|
- generic [ref=f127e72]:
|
|
- generic [ref=f127e73]:
|
|
- generic [ref=f127e74]: 관계 3 대상
|
|
- combobox "관계 3 대상" [ref=f127e75]:
|
|
- option "대상 선택"
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option
|
|
- option "실제 동시 트래픽에서도 이 구조가 안정적인가"
|
|
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
|
|
- option "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가"
|
|
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
- option "Authorization Code와 PKCE가 보호하는 구간"
|
|
- option "Bearer JWT가 인증된 principal이 되기까지"
|
|
- option "BFF 인증 구조 설계 기준"
|
|
- option "BFF가 OAuth Token을 관리하는 조건"
|
|
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
|
|
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
|
|
- option "Collection Fetch Join Pagination의 In-memory Paging"
|
|
- option "Cookie로 인증하는 요청에서 CSRF token이 하는 일"
|
|
- option "브라우저가 credential을 보관하는 위치와 그 성질"
|
|
- option "DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1"
|
|
- option "필드 접근 없이 발생한 EAGER ToOne N+1"
|
|
- option "Feed Visibility Query Pattern"
|
|
- option "feed_visible을 Production CQRS로 승격할 것인가"
|
|
- option "Fetch Join · Batch · Projection 선택 기준" [selected]
|
|
- option "Fetch Join으로 N+1을 해결하다 만난 MultiBag과 행 폭증"
|
|
- option "Fetch Type과 Fetch Strategy 구분"
|
|
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
|
|
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
|
|
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
|
|
- option "Forward-Auth와 Nginx auth_request의 동작"
|
|
- option "Highlight 없는 FeedItem을 허용할 것인가"
|
|
- option "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다."
|
|
- option "외부 IdP 연동과 Application 인증 구조의 경계"
|
|
- option "외부 IdP Brokering의 동작"
|
|
- option "JPA N+1 정량 진단 기준"
|
|
- option "Keyset Pagination 설계 기준"
|
|
- option "OAuth/OIDC 인증 패턴 선택 기준"
|
|
- option "OAuth Token과 Application Session을 구분하는 기준"
|
|
- option "PostgreSQL Query Plan 측정 기준" [disabled]
|
|
- option "Projection 이후에도 1,509행을 읽은 Row Over-fetch" [disabled]
|
|
- option "Public Client와 Confidential Client 구분 기준"
|
|
- option "Query Plan은 실제 PostgreSQL에서 측정한다"
|
|
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
|
|
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
|
|
- option "Round Trip과 Row Volume을 독립 측정할 것인가"
|
|
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
|
|
- option "Top-N-per-group 선택 기준"
|
|
- option "Visibility OR이 Keyset Index를 깨뜨린 문제"
|
|
- generic [ref=f127e76]:
|
|
- generic [ref=f127e77]: 관계 3 이유
|
|
- textbox "관계 3 이유" [ref=f127e78]: 앞 단계에서 왕복과 적재를 푼 기준이다.
|
|
- generic [ref=f127e79]:
|
|
- button "위로" [ref=f127e80]
|
|
- button "아래로" [disabled] [ref=f127e81]
|
|
- button "삭제" [ref=f127e82]
|
|
- button "관계 추가" [ref=f127e83]
|
|
- region [ref=f127e84]:
|
|
- generic [ref=f127e85]:
|
|
- paragraph [ref=f127e86]: REFERENCE
|
|
- heading "재사용할 기준" [level=2] [ref=f127e87]
|
|
- generic [ref=f127e88]:
|
|
- generic [ref=f127e89]: 이 기준을 쓰는 이유
|
|
- textbox "이 기준을 쓰는 이유" [ref=f127e90]
|
|
- group "판단 기준" [ref=f127e91]:
|
|
- generic [ref=f127e93]:
|
|
- generic [ref=f127e94]:
|
|
- generic [ref=f127e95]: 판단 기준 1 제목
|
|
- textbox "판단 기준 1 제목" [ref=f127e96]: 단순 LIMIT은 그룹당 상한이 아니다
|
|
- generic [ref=f127e97]:
|
|
- generic [ref=f127e98]: 판단 기준 1 본문
|
|
- textbox "판단 기준 1 본문" [ref=f127e99]: LIMIT은 최종 결과 집합에 적용된다. 부모 20개를 조회하면서 LIMIT 3을 붙이면 3행만 남아 부모 하나만 채워진다. 이 오작동은 결과 행수가 적어 정상처럼 보일 수 있다. 커버한 부모 수를 함께 확인한다.
|
|
- generic [ref=f127e100]:
|
|
- button "위로" [disabled] [ref=f127e101]
|
|
- button "아래로" [ref=f127e102]
|
|
- button "삭제" [ref=f127e103]
|
|
- generic [ref=f127e104]:
|
|
- generic [ref=f127e105]:
|
|
- generic [ref=f127e106]: 판단 기준 2 제목
|
|
- textbox "판단 기준 2 제목" [ref=f127e107]: 세 가지 표현을 구분한다
|
|
- generic [ref=f127e108]:
|
|
- generic [ref=f127e109]: 판단 기준 2 본문
|
|
- textbox "판단 기준 2 본문" [ref=f127e110]: 윈도우 함수는 부모별로 순번을 매기고 상위 몇 개를 남긴다. 순번을 만들려고 파티션 전체를 읽는다. LATERAL은 부모마다 상관 서브쿼리를 실행하고 인덱스에서 필요한 개수만 읽고 멈춘다. 애플리케이션 그룹핑은 자식을 한 번에 가져온 뒤 코드에서 자른다. 자르기 전에 전량이 전송된다.
|
|
- generic [ref=f127e111]:
|
|
- button "위로" [ref=f127e112]
|
|
- button "아래로" [ref=f127e113]
|
|
- button "삭제" [ref=f127e114]
|
|
- generic [ref=f127e115]:
|
|
- generic [ref=f127e116]:
|
|
- generic [ref=f127e117]: 판단 기준 3 제목
|
|
- textbox "판단 기준 3 제목" [ref=f127e118]: 작은 K에는 LATERAL이 유리하다
|
|
- generic [ref=f127e119]:
|
|
- generic [ref=f127e120]: 판단 기준 3 본문
|
|
- textbox "판단 기준 3 본문" [ref=f127e121]: 부모별 정렬 인덱스가 있으면 LATERAL은 부모마다 K개만 읽고 멈춘다. 그룹이 크고 K가 작을수록 읽지 않는 행이 많아진다.
|
|
- generic [ref=f127e122]:
|
|
- button "위로" [ref=f127e123]
|
|
- button "아래로" [ref=f127e124]
|
|
- button "삭제" [ref=f127e125]
|
|
- generic [ref=f127e126]:
|
|
- generic [ref=f127e127]:
|
|
- generic [ref=f127e128]: 판단 기준 4 제목
|
|
- textbox "판단 기준 4 제목" [ref=f127e129]: K가 그룹 크기에 가까우면 윈도우로 수렴한다
|
|
- generic [ref=f127e130]:
|
|
- generic [ref=f127e131]: 판단 기준 4 본문
|
|
- textbox "판단 기준 4 본문" [ref=f127e132]: K가 그룹 크기에 가까워지면 LATERAL도 대부분을 읽는다. 이때는 더 단순한 윈도우 함수를 고를 수 있다. K를 바꿔 가며 buffers를 재면 어느 지점에서 뒤집히는지 볼 수 있다.
|
|
- generic [ref=f127e133]:
|
|
- button "위로" [ref=f127e134]
|
|
- button "아래로" [ref=f127e135]
|
|
- button "삭제" [ref=f127e136]
|
|
- generic [ref=f127e137]:
|
|
- generic [ref=f127e138]:
|
|
- generic [ref=f127e139]: 판단 기준 5 제목
|
|
- textbox "판단 기준 5 제목" [ref=f127e140]: LATERAL의 이점은 인덱스에서 나온다
|
|
- generic [ref=f127e141]:
|
|
- generic [ref=f127e142]: 판단 기준 5 본문
|
|
- textbox "판단 기준 5 본문" [ref=f127e143]: LATERAL 문법 자체가 빠른 것이 아니다. 부모별 정렬 인덱스가 있어야 상위 K개를 바로 찾는다. 인덱스가 없으면 부모마다 자식 테이블을 스캔하고 대부분을 필터로 버린다. 인덱스 유무를 토글해 확인한다.
|
|
- generic [ref=f127e144]:
|
|
- button "위로" [ref=f127e145]
|
|
- button "아래로" [ref=f127e146]
|
|
- button "삭제" [ref=f127e147]
|
|
- generic [ref=f127e148]:
|
|
- generic [ref=f127e149]:
|
|
- generic [ref=f127e150]: 판단 기준 6 제목
|
|
- textbox "판단 기준 6 제목" [ref=f127e151]: 애플리케이션 그룹핑은 전송량을 줄이지 않는다
|
|
- generic [ref=f127e152]:
|
|
- generic [ref=f127e153]: 판단 기준 6 본문
|
|
- textbox "판단 기준 6 본문" [ref=f127e154]: 코드에서 자르면 결과는 맞지만 DB가 전달한 행은 전량이다. 전송량이 문제인 상황에서는 해법이 아니다.
|
|
- generic [ref=f127e155]:
|
|
- button "위로" [ref=f127e156]
|
|
- button "아래로" [ref=f127e157]
|
|
- button "삭제" [ref=f127e158]
|
|
- generic [ref=f127e159]:
|
|
- generic [ref=f127e160]:
|
|
- generic [ref=f127e161]: 판단 기준 7 제목
|
|
- textbox "판단 기준 7 제목" [ref=f127e162]: 표준 JPQL로 표현되지 않는다
|
|
- generic [ref=f127e163]:
|
|
- generic [ref=f127e164]: 판단 기준 7 본문
|
|
- textbox "판단 기준 7 본문" [ref=f127e165]: 윈도우 함수와 LATERAL은 표준 JPQL에 없다. native SQL로 내려가야 한다. 이 결정을 기록에 남긴다.
|
|
- generic [ref=f127e166]:
|
|
- button "위로" [ref=f127e167]
|
|
- button "아래로" [ref=f127e168]
|
|
- button "삭제" [ref=f127e169]
|
|
- generic [ref=f127e170]:
|
|
- generic [ref=f127e171]:
|
|
- generic [ref=f127e172]: 판단 기준 8 제목
|
|
- textbox "판단 기준 8 제목" [ref=f127e173]: 반환 행수와 커버한 부모를 함께 검증한다
|
|
- generic [ref=f127e174]:
|
|
- generic [ref=f127e175]: 판단 기준 8 본문
|
|
- textbox "판단 기준 8 본문" [ref=f127e176]: 세 방식이 같은 결과를 만드는지 먼저 확인한 뒤 실행계획을 비교한다. 반환 행수, 커버한 부모 수, 부모당 최대 개수를 함께 본다.
|
|
- generic [ref=f127e177]:
|
|
- button "위로" [ref=f127e178]
|
|
- button "아래로" [disabled] [ref=f127e179]
|
|
- button "삭제" [ref=f127e180]
|
|
- button "판단 기준 추가" [ref=f127e181]
|
|
- group "적용할 때" [ref=f127e182]:
|
|
- generic [ref=f127e184]:
|
|
- generic [ref=f127e185]:
|
|
- generic [ref=f127e186]: 적용할 때 1
|
|
- textbox "적용할 때 1" [ref=f127e187]: 목록 응답에 부모별 자식 상위 몇 개를 포함해야 할 때
|
|
- generic [ref=f127e188]:
|
|
- button "위로" [disabled] [ref=f127e189]
|
|
- button "아래로" [ref=f127e190]
|
|
- button "삭제" [ref=f127e191]
|
|
- generic [ref=f127e192]:
|
|
- generic [ref=f127e193]:
|
|
- generic [ref=f127e194]: 적용할 때 2
|
|
- textbox "적용할 때 2" [ref=f127e195]: 자식 전량 조회가 전송량 문제를 만들 때
|
|
- generic [ref=f127e196]:
|
|
- button "위로" [ref=f127e197]
|
|
- button "아래로" [ref=f127e198]
|
|
- button "삭제" [ref=f127e199]
|
|
- generic [ref=f127e200]:
|
|
- generic [ref=f127e201]:
|
|
- generic [ref=f127e202]: 적용할 때 3
|
|
- textbox "적용할 때 3" [ref=f127e203]: 그룹 크기가 크고 필요한 개수가 작을 때
|
|
- generic [ref=f127e204]:
|
|
- button "위로" [ref=f127e205]
|
|
- button "아래로" [disabled] [ref=f127e206]
|
|
- button "삭제" [ref=f127e207]
|
|
- button "적용할 때 추가" [ref=f127e208]
|
|
- group "예외와 주의" [ref=f127e209]:
|
|
- generic [ref=f127e211]:
|
|
- generic [ref=f127e212]:
|
|
- generic [ref=f127e213]: 예외와 주의 1
|
|
- textbox "예외와 주의 1" [ref=f127e214]: 그룹 크기가 작아 전량을 읽어도 부담이 없으면 애플리케이션 그룹핑이 단순하다.
|
|
- generic [ref=f127e215]:
|
|
- button "위로" [disabled] [ref=f127e216]
|
|
- button "아래로" [ref=f127e217]
|
|
- button "삭제" [ref=f127e218]
|
|
- generic [ref=f127e219]:
|
|
- generic [ref=f127e220]:
|
|
- generic [ref=f127e221]: 예외와 주의 2
|
|
- textbox "예외와 주의 2" [ref=f127e222]: 부모별 정렬 인덱스를 만들 수 없으면 LATERAL의 이점이 사라진다. 이때는 윈도우 함수와 buffers를 비교해 고른다.
|
|
- generic [ref=f127e223]:
|
|
- button "위로" [ref=f127e224]
|
|
- button "아래로" [disabled] [ref=f127e225]
|
|
- button "삭제" [ref=f127e226]
|
|
- button "예외와 주의 추가" [ref=f127e227]
|
|
- group "예시" [ref=f127e228]:
|
|
- generic [ref=f127e230]:
|
|
- generic [ref=f127e231]:
|
|
- generic [ref=f127e232]: 예시 1
|
|
- textbox "예시 1" [ref=f127e233]: "순진 LIMIT 3 : 전체에 적용, 부모 1개만 채워짐"
|
|
- generic [ref=f127e234]:
|
|
- button "위로" [disabled] [ref=f127e235]
|
|
- button "아래로" [ref=f127e236]
|
|
- button "삭제" [ref=f127e237]
|
|
- generic [ref=f127e238]:
|
|
- generic [ref=f127e239]:
|
|
- generic [ref=f127e240]: 예시 2
|
|
- textbox "예시 2" [ref=f127e241]: "윈도우 : 부모별 순번 뒤 상위 K, 파티션 전량 읽음"
|
|
- generic [ref=f127e242]:
|
|
- button "위로" [ref=f127e243]
|
|
- button "아래로" [ref=f127e244]
|
|
- button "삭제" [ref=f127e245]
|
|
- generic [ref=f127e246]:
|
|
- generic [ref=f127e247]:
|
|
- generic [ref=f127e248]: 예시 3
|
|
- textbox "예시 3" [ref=f127e249]: "LATERAL : 부모마다 인덱스에서 K개 읽고 멈춤"
|
|
- generic [ref=f127e250]:
|
|
- button "위로" [ref=f127e251]
|
|
- button "아래로" [ref=f127e252]
|
|
- button "삭제" [ref=f127e253]
|
|
- generic [ref=f127e254]:
|
|
- generic [ref=f127e255]:
|
|
- generic [ref=f127e256]: 예시 4
|
|
- textbox "예시 4" [ref=f127e257]: "2단계 : 자식 전량 전송 뒤 코드에서 그룹핑"
|
|
- generic [ref=f127e258]:
|
|
- button "위로" [ref=f127e259]
|
|
- button "아래로" [ref=f127e260]
|
|
- button "삭제" [ref=f127e261]
|
|
- generic [ref=f127e262]:
|
|
- generic [ref=f127e263]:
|
|
- generic [ref=f127e264]: 예시 5
|
|
- textbox "예시 5" [ref=f127e265]: "인덱스 없는 LATERAL : 부모마다 Seq Scan, buffers 급증"
|
|
- generic [ref=f127e266]:
|
|
- button "위로" [ref=f127e267]
|
|
- button "아래로" [ref=f127e268]
|
|
- button "삭제" [ref=f127e269]
|
|
- generic [ref=f127e270]:
|
|
- generic [ref=f127e271]:
|
|
- generic [ref=f127e272]: 예시 6
|
|
- textbox "예시 6" [ref=f127e273]: "선택 : 작은 K는 LATERAL, K가 그룹 크기에 근접하면 윈도우"
|
|
- generic [ref=f127e274]:
|
|
- button "위로" [ref=f127e275]
|
|
- button "아래로" [disabled] [ref=f127e276]
|
|
- button "삭제" [ref=f127e277]
|
|
- button "예시 추가" [ref=f127e278]
|
|
- generic [ref=f127e279]:
|
|
- generic [ref=f127e280]: 마지막 검증일
|
|
- textbox "마지막 검증일" [ref=f127e281]
|
|
- region [ref=f127e282]:
|
|
- generic [ref=f127e283]:
|
|
- paragraph [ref=f127e284]: LIVE
|
|
- heading "즉시 미리보기" [level=2] [ref=f127e285]
|
|
- generic [ref=f127e288]:
|
|
- generic [ref=f127e289]:
|
|
- navigation "문서 경로" [ref=f127e290]:
|
|
- link "Reference" [ref=f127e291] [cursor=pointer]:
|
|
- /url: /explore/references
|
|
- generic [ref=f127e292]: /
|
|
- generic [ref=f127e293]: JPA 피드 조회 성능
|
|
- generic [ref=f127e294]: /
|
|
- generic [ref=f127e295]: Liner N + 1문제
|
|
- heading "Top-N-per-group 선택 기준" [level=1] [ref=f127e296]
|
|
- paragraph [ref=f127e297]: 부모마다 상위 N개를 뽑는 일은 LIMIT으로 표현되지 않는다. 윈도우 함수, LATERAL, 애플리케이션 그룹핑 세 가지가 같은 결과를 만들지만 읽는 행수가 다르다.
|
|
- generic [ref=f127e298]:
|
|
- generic [ref=f127e299]:
|
|
- term [ref=f127e300]: 유형
|
|
- definition [ref=f127e301]: Reference
|
|
- generic [ref=f127e302]:
|
|
- term [ref=f127e303]: 프로젝트
|
|
- definition [ref=f127e304]: Liner N + 1문제
|
|
- generic [ref=f127e305]:
|
|
- term [ref=f127e306]: 게시
|
|
- definition [ref=f127e307]: 게시 전
|
|
- region [ref=f127e308]:
|
|
- paragraph [ref=f127e309]: Purpose
|
|
- heading "이 기준을 쓰는 이유" [level=2] [ref=f127e310]
|
|
- article [ref=f127e311]:
|
|
- region [ref=f127e312]:
|
|
- heading "판단 기준" [level=2] [ref=f127e313]
|
|
- list [ref=f127e314]:
|
|
- listitem [ref=f127e315]:
|
|
- generic [ref=f127e316]: "01"
|
|
- generic [ref=f127e317]:
|
|
- heading "단순 LIMIT은 그룹당 상한이 아니다" [level=3] [ref=f127e318]
|
|
- paragraph [ref=f127e319]: LIMIT은 최종 결과 집합에 적용된다. 부모 20개를 조회하면서 LIMIT 3을 붙이면 3행만 남아 부모 하나만 채워진다.
|
|
- paragraph [ref=f127e320]: 이 오작동은 결과 행수가 적어 정상처럼 보일 수 있다. 커버한 부모 수를 함께 확인한다.
|
|
- listitem [ref=f127e321]:
|
|
- generic [ref=f127e322]: "02"
|
|
- generic [ref=f127e323]:
|
|
- heading "세 가지 표현을 구분한다" [level=3] [ref=f127e324]
|
|
- paragraph [ref=f127e325]: 윈도우 함수는 부모별로 순번을 매기고 상위 몇 개를 남긴다. 순번을 만들려고 파티션 전체를 읽는다.
|
|
- paragraph [ref=f127e326]: LATERAL은 부모마다 상관 서브쿼리를 실행하고 인덱스에서 필요한 개수만 읽고 멈춘다.
|
|
- paragraph [ref=f127e327]: 애플리케이션 그룹핑은 자식을 한 번에 가져온 뒤 코드에서 자른다. 자르기 전에 전량이 전송된다.
|
|
- listitem [ref=f127e328]:
|
|
- generic [ref=f127e329]: "03"
|
|
- generic [ref=f127e330]:
|
|
- heading "작은 K에는 LATERAL이 유리하다" [level=3] [ref=f127e331]
|
|
- paragraph [ref=f127e332]: 부모별 정렬 인덱스가 있으면 LATERAL은 부모마다 K개만 읽고 멈춘다. 그룹이 크고 K가 작을수록 읽지 않는 행이 많아진다.
|
|
- listitem [ref=f127e333]:
|
|
- generic [ref=f127e334]: "04"
|
|
- generic [ref=f127e335]:
|
|
- heading "K가 그룹 크기에 가까우면 윈도우로 수렴한다" [level=3] [ref=f127e336]
|
|
- paragraph [ref=f127e337]: K가 그룹 크기에 가까워지면 LATERAL도 대부분을 읽는다. 이때는 더 단순한 윈도우 함수를 고를 수 있다.
|
|
- paragraph [ref=f127e338]: K를 바꿔 가며 buffers를 재면 어느 지점에서 뒤집히는지 볼 수 있다.
|
|
- listitem [ref=f127e339]:
|
|
- generic [ref=f127e340]: "05"
|
|
- generic [ref=f127e341]:
|
|
- heading "LATERAL의 이점은 인덱스에서 나온다" [level=3] [ref=f127e342]
|
|
- paragraph [ref=f127e343]: LATERAL 문법 자체가 빠른 것이 아니다. 부모별 정렬 인덱스가 있어야 상위 K개를 바로 찾는다.
|
|
- paragraph [ref=f127e344]: 인덱스가 없으면 부모마다 자식 테이블을 스캔하고 대부분을 필터로 버린다. 인덱스 유무를 토글해 확인한다.
|
|
- listitem [ref=f127e345]:
|
|
- generic [ref=f127e346]: "06"
|
|
- generic [ref=f127e347]:
|
|
- heading "애플리케이션 그룹핑은 전송량을 줄이지 않는다" [level=3] [ref=f127e348]
|
|
- paragraph [ref=f127e349]: 코드에서 자르면 결과는 맞지만 DB가 전달한 행은 전량이다. 전송량이 문제인 상황에서는 해법이 아니다.
|
|
- listitem [ref=f127e350]:
|
|
- generic [ref=f127e351]: "07"
|
|
- generic [ref=f127e352]:
|
|
- heading "표준 JPQL로 표현되지 않는다" [level=3] [ref=f127e353]
|
|
- paragraph [ref=f127e354]: 윈도우 함수와 LATERAL은 표준 JPQL에 없다. native SQL로 내려가야 한다. 이 결정을 기록에 남긴다.
|
|
- listitem [ref=f127e355]:
|
|
- generic [ref=f127e356]: "08"
|
|
- generic [ref=f127e357]:
|
|
- heading "반환 행수와 커버한 부모를 함께 검증한다" [level=3] [ref=f127e358]
|
|
- paragraph [ref=f127e359]: 세 방식이 같은 결과를 만드는지 먼저 확인한 뒤 실행계획을 비교한다. 반환 행수, 커버한 부모 수, 부모당 최대 개수를 함께 본다.
|
|
- region [ref=f127e360]:
|
|
- heading "적용할 때" [level=2] [ref=f127e361]
|
|
- list [ref=f127e362]:
|
|
- listitem [ref=f127e363]:
|
|
- paragraph [ref=f127e364]: 목록 응답에 부모별 자식 상위 몇 개를 포함해야 할 때
|
|
- listitem [ref=f127e365]:
|
|
- paragraph [ref=f127e366]: 자식 전량 조회가 전송량 문제를 만들 때
|
|
- listitem [ref=f127e367]:
|
|
- paragraph [ref=f127e368]: 그룹 크기가 크고 필요한 개수가 작을 때
|
|
- region [ref=f127e369]:
|
|
- heading "예외와 주의" [level=2] [ref=f127e370]
|
|
- list [ref=f127e371]:
|
|
- listitem [ref=f127e372]:
|
|
- paragraph [ref=f127e373]: 그룹 크기가 작아 전량을 읽어도 부담이 없으면 애플리케이션 그룹핑이 단순하다.
|
|
- listitem [ref=f127e374]:
|
|
- paragraph [ref=f127e375]: 부모별 정렬 인덱스를 만들 수 없으면 LATERAL의 이점이 사라진다. 이때는 윈도우 함수와 buffers를 비교해 고른다.
|
|
- region [ref=f127e376]:
|
|
- heading "예시" [level=2] [ref=f127e377]
|
|
- list [ref=f127e378]:
|
|
- listitem [ref=f127e379]:
|
|
- paragraph [ref=f127e380]: "순진 LIMIT 3 : 전체에 적용, 부모 1개만 채워짐"
|
|
- listitem [ref=f127e381]:
|
|
- paragraph [ref=f127e382]: "윈도우 : 부모별 순번 뒤 상위 K, 파티션 전량 읽음"
|
|
- listitem [ref=f127e383]:
|
|
- paragraph [ref=f127e384]: "LATERAL : 부모마다 인덱스에서 K개 읽고 멈춤"
|
|
- listitem [ref=f127e385]:
|
|
- paragraph [ref=f127e386]: "2단계 : 자식 전량 전송 뒤 코드에서 그룹핑"
|
|
- listitem [ref=f127e387]:
|
|
- paragraph [ref=f127e388]: "인덱스 없는 LATERAL : 부모마다 Seq Scan, buffers 급증"
|
|
- listitem [ref=f127e389]:
|
|
- paragraph [ref=f127e390]: "선택 : 작은 K는 LATERAL, K가 그룹 크기에 근접하면 윈도우"
|
|
- paragraph [ref=f127e391]: 마지막 검증
|
|
- complementary [ref=f127e392]:
|
|
- heading "작업 상태" [level=2] [ref=f127e393]
|
|
- status "편집 상태" [ref=f127e394]: 저장됨
|
|
- generic [ref=f127e395]:
|
|
- generic [ref=f127e396]:
|
|
- term [ref=f127e397]: 저장 버전
|
|
- definition [ref=f127e398]: "5"
|
|
- generic [ref=f127e399]:
|
|
- term [ref=f127e400]: 종류
|
|
- definition [ref=f127e401]: Reference
|
|
- paragraph [ref=f127e402]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
|
|
- generic [ref=f127e403]:
|
|
- button "저장" [disabled] [ref=f127e404]
|
|
- button "게시" [ref=f127e405]
|
|
- paragraph [ref=f127e406]: 버전 5으로 저장했습니다. |