Files
document-haness/.playwright-mcp/page-2026-09-04T05-08-48-651Z.yml
T

521 lines
35 KiB
YAML

- generic [ref=f26e3]:
- link "본문으로 건너뛰기" [ref=f26e4] [cursor=pointer]:
- /url: "#main-content"
- banner [ref=f26e5]:
- generic [ref=f26e6]:
- link "TechLog Studio" [ref=f26e7] [cursor=pointer]:
- /url: /studio
- text: TechLog
- generic [ref=f26e8]: Studio
- navigation "Studio 주 탐색" [ref=f26e10]:
- link "작업본" [ref=f26e11] [cursor=pointer]:
- /url: /studio/documents
- link "게시 기록" [ref=f26e12] [cursor=pointer]:
- /url: /studio/publications
- link "새 문서" [ref=f26e13] [cursor=pointer]:
- /url: /studio/documents/new
- link "주제·프로젝트" [ref=f26e14] [cursor=pointer]:
- /url: /studio/taxonomy
- link "릴리즈" [ref=f26e15] [cursor=pointer]:
- /url: /studio/releases
- link "공개 사이트 보기" [ref=f26e16] [cursor=pointer]:
- /url: /
- button "로그아웃" [ref=f26e17]
- main [ref=f26e18]:
- generic [ref=f26e19]:
- generic [ref=f26e20]:
- region [ref=f26e21]:
- generic [ref=f26e22]:
- paragraph [ref=f26e23]: QUESTION · VERSION 6
- heading "문서 편집" [level=1] [ref=f26e24]
- paragraph [ref=f26e25]: ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
- region [ref=f26e26]:
- generic [ref=f26e27]:
- paragraph [ref=f26e28]: DOCUMENT
- heading "기본 정보" [level=2] [ref=f26e29]
- generic [ref=f26e30]:
- generic [ref=f26e31]:
- generic [ref=f26e32]: 제목
- textbox "제목" [ref=f26e33]: ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
- generic [ref=f26e34]:
- generic [ref=f26e35]: slug
- textbox "slug" [ref=f26e36]:
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
- text: cardinality-estimate-after-analyze
- generic [ref=f26e37]:
- generic [ref=f26e38]: 요약
- textbox "요약" [ref=f26e39]: 반복되는 하이라이트 조회의 실행계획에서 추정 행수는 1이고 실제 행수는 500이었다. 대량 데이터를 넣은 직후 통계를 갱신하지 않아 편중을 담지 못했다는 가설을 세웠지만 아직 검증하지 않았다.
- generic [aria-hidden] [ref=f26e40]: 목록 카드에는 약 90자까지 보입니다 · 107 / 2000
- generic [ref=f26e41]:
- generic [ref=f26e42]: Topic
- combobox "Topic" [ref=f26e43]:
- option "선택하지 않음"
- option "JPA 피드 조회 성능" [selected]
- option "OAuth/OIDC 인증 경계"
- generic [ref=f26e44]:
- generic [ref=f26e45]: Project
- combobox "Project" [ref=f26e46]:
- option "미지정"
- option "Backend Clean Architecture"
- option "KeyCloak Patterns"
- option "Liner N + 1문제" [selected]
- status [ref=f26e47]
- group "축 — 고르지 않으면 이 주제의 공통 기록이 됩니다" [ref=f26e48]:
- generic [ref=f26e50] [cursor=pointer]:
- checkbox "파생 쿼리 그대로" [ref=f26e51]
- generic [ref=f26e52]: 파생 쿼리 그대로
- generic [ref=f26e53] [cursor=pointer]:
- checkbox "컬렉션 fetch join" [ref=f26e54]
- generic [ref=f26e55]: 컬렉션 fetch join
- generic [ref=f26e56] [cursor=pointer]:
- checkbox "fetch join + 페이징" [ref=f26e57]
- generic [ref=f26e58]: fetch join + 페이징
- group "관계" [ref=f26e59]:
- generic [ref=f26e61]:
- generic [ref=f26e62]:
- generic [ref=f26e63]: 관계 1 대상
- combobox "관계 1 대상" [ref=f26e64]:
- option "대상 선택"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Collection Fetch Join으로 N+1을 해결하다 만난 MultiBag예외와 행 폭증 문제"
- option "Collection Fetch Join Pagination의 In-memory Paging"
- option "DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1"
- option "Fetch 타입이 아닌 조회 방식으로 인한 N+1" [disabled]
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
- option "Projection 이후에도 1,509행을 읽은 Row Over-fetch"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
- option "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우"
- option "Visibility OR이 Keyset Index를 깨뜨린 문제"
- option "Authorization Code와 PKCE가 보호하는 구간"
- option "Bearer JWT가 인증된 principal이 되기까지"
- option "Cookie로 인증하는 요청에서 CSRF token이 하는 일"
- option "브라우저가 credential을 보관하는 위치와 그 성질"
- option "Forward-Auth와 Nginx auth_request의 동작"
- option "외부 IdP Brokering의 동작"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "Feed Visibility Query Pattern"
- option "Fetch Join · Batch · Projection 선택 기준"
- option "Fetch Type과 Fetch Strategy 구분"
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP 연동과 Application 인증 구조의 경계"
- option "JPA N+1 정량 진단 기준"
- option "Keyset Pagination 설계 기준"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "PostgreSQL Query Plan 측정 기준" [selected]
- option "Public Client와 Confidential Client 구분 기준"
- option "Top-N-per-group 선택 기준"
- option "실제 동시 트래픽에서도 이 구조가 안정적인가"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "feed_visible을 Production CQRS로 승격할 것인가"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Highlight 없는 FeedItem을 허용할 것인가"
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "Round Trip과 Row Volume을 독립 측정할 것인가"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "Collection Fetch Join과 Pagination을 같이 사용하지 않는다"
- option "Entity Graph 조회에는 Batch Fetch를 사용한다"
- option "Feed Pagination은 Keyset을 사용한다"
- option "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다."
- option "Query Plan은 실제 PostgreSQL에서 측정한다"
- option "Query Strategy는 FeedQueryPort 뒤에서 소유한다"
- option "현재 Read Model은 CQRS-lite로 유지한다"
- option "화면 조회는 Read Projection을 사용한다"
- generic [ref=f26e65]:
- generic [ref=f26e66]: 관계 1 이유
- textbox "관계 1 이유" [ref=f26e67]: 추정과 실제의 차이를 기록하는 기준이다.
- generic [aria-hidden] [ref=f26e68]: 공개 화면의 「다음에 읽을 것」에 그대로 나갑니다.
- generic [ref=f26e69]:
- button "위로" [disabled] [ref=f26e70]
- button "아래로" [ref=f26e71]
- button "삭제" [ref=f26e72]
- generic [ref=f26e73]:
- generic [ref=f26e74]:
- generic [ref=f26e75]: 관계 2 대상
- combobox "관계 2 대상" [ref=f26e76]:
- option "대상 선택"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Collection Fetch Join으로 N+1을 해결하다 만난 MultiBag예외와 행 폭증 문제"
- option "Collection Fetch Join Pagination의 In-memory Paging"
- option "DTO 변환 과정에서 발생한 Highlight 컬렉션 N+1"
- option "Fetch 타입이 아닌 조회 방식으로 인한 N+1" [selected]
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
- option "Projection 이후에도 1,509행을 읽은 Row Over-fetch"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
- option "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우"
- option "Visibility OR이 Keyset Index를 깨뜨린 문제"
- option "Authorization Code와 PKCE가 보호하는 구간"
- option "Bearer JWT가 인증된 principal이 되기까지"
- option "Cookie로 인증하는 요청에서 CSRF token이 하는 일"
- option "브라우저가 credential을 보관하는 위치와 그 성질"
- option "Forward-Auth와 Nginx auth_request의 동작"
- option "외부 IdP Brokering의 동작"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "Feed Visibility Query Pattern"
- option "Fetch Join · Batch · Projection 선택 기준"
- option "Fetch Type과 Fetch Strategy 구분"
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP 연동과 Application 인증 구조의 경계"
- option "JPA N+1 정량 진단 기준"
- option "Keyset Pagination 설계 기준"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "PostgreSQL Query Plan 측정 기준" [disabled]
- option "Public Client와 Confidential Client 구분 기준"
- option "Top-N-per-group 선택 기준"
- option "실제 동시 트래픽에서도 이 구조가 안정적인가"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "feed_visible을 Production CQRS로 승격할 것인가"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Highlight 없는 FeedItem을 허용할 것인가"
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "Round Trip과 Row Volume을 독립 측정할 것인가"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "Collection Fetch Join과 Pagination을 같이 사용하지 않는다"
- option "Entity Graph 조회에는 Batch Fetch를 사용한다"
- option "Feed Pagination은 Keyset을 사용한다"
- option "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다."
- option "Query Plan은 실제 PostgreSQL에서 측정한다"
- option "Query Strategy는 FeedQueryPort 뒤에서 소유한다"
- option "현재 Read Model은 CQRS-lite로 유지한다"
- option "화면 조회는 Read Projection을 사용한다"
- generic [ref=f26e77]:
- generic [ref=f26e78]: 관계 2 이유
- textbox [ref=f26e79]
- generic [aria-hidden] [ref=f26e80]: 공개 화면의 「다음에 읽을 것」에 그대로 나갑니다.
- generic [ref=f26e81]:
- button "위로" [ref=f26e82]
- button "아래로" [disabled] [ref=f26e83]
- button "삭제" [ref=f26e84]
- button "관계 추가" [ref=f26e85]
- region [ref=f26e86]:
- generic [ref=f26e87]:
- paragraph [ref=f26e88]: QUESTION
- heading "판단과 다음 검증" [level=2] [ref=f26e89]
- generic [ref=f26e90]:
- generic [ref=f26e91]: 질문 상태
- combobox "질문 상태" [ref=f26e92]:
- option "아직 정하지 않음"
- option "OPEN" [selected]
- option "RESOLVED"
- group "확인한 사실" [ref=f26e93]:
- generic [ref=f26e95]:
- generic [ref=f26e96]:
- generic [ref=f26e97]: 확인한 사실 1
- textbox "확인한 사실 1" [ref=f26e98]: 대량 시드 직후 측정한 계획에서 추정 rows는 1, 실제 rows는 500이었다. 500배 차이다.
- generic [ref=f26e99]:
- button "위로" [disabled] [ref=f26e100]
- button "아래로" [ref=f26e101]
- button "삭제" [ref=f26e102]
- generic [ref=f26e103]:
- generic [ref=f26e104]:
- generic [ref=f26e105]: 확인한 사실 2
- textbox "확인한 사실 2" [ref=f26e106]: 이 계획은 feed_item_id 조건을 인덱스로 처리했고 실행시간은 0.173 ms였다.
- generic [ref=f26e107]:
- button "위로" [ref=f26e108]
- button "아래로" [ref=f26e109]
- button "삭제" [ref=f26e110]
- generic [ref=f26e111]:
- generic [ref=f26e112]:
- generic [ref=f26e113]: 확인한 사실 3
- textbox "확인한 사실 3" [ref=f26e114]: 읽은 블록은 모두 캐시에서 왔다. 디스크 읽기는 0이었다.
- generic [ref=f26e115]:
- button "위로" [ref=f26e116]
- button "아래로" [ref=f26e117]
- button "삭제" [ref=f26e118]
- generic [ref=f26e119]:
- generic [ref=f26e120]:
- generic [ref=f26e121]: 확인한 사실 4
- textbox "확인한 사실 4" [ref=f26e122]: 하이라이트 개수는 순위 기반 편중 분포라 feed_item_id별 자식 수가 크게 다르다. 상한 500, 하한 1이다.
- generic [ref=f26e123]:
- button "위로" [ref=f26e124]
- button "아래로" [ref=f26e125]
- button "삭제" [ref=f26e126]
- generic [ref=f26e127]:
- generic [ref=f26e128]:
- generic [ref=f26e129]: 확인한 사실 5
- textbox "확인한 사실 5" [ref=f26e130]: fetch join 조인 계획에서도 추정 4,202와 실제 1,961의 차이가 있었다.
- generic [ref=f26e131]:
- button "위로" [ref=f26e132]
- button "아래로" [ref=f26e133]
- button "삭제" [ref=f26e134]
- generic [ref=f26e135]:
- generic [ref=f26e136]:
- generic [ref=f26e137]: 확인한 사실 6
- textbox "확인한 사실 6" [ref=f26e138]: 시드 직후 통계 갱신 명령을 실행하지 않았다.
- generic [ref=f26e139]:
- button "위로" [ref=f26e140]
- button "아래로" [disabled] [ref=f26e141]
- button "삭제" [ref=f26e142]
- button "확인한 사실 추가" [ref=f26e143]
- group "가정" [ref=f26e144]:
- generic [ref=f26e146]:
- generic [ref=f26e147]:
- generic [ref=f26e148]: 가정 1
- textbox "가정 1" [ref=f26e149]: 통계를 갱신하면 feed_item_id별 분포가 반영되어 추정이 실제에 가까워진다.
- generic [ref=f26e150]:
- button "위로" [disabled] [ref=f26e151]
- button "아래로" [ref=f26e152]
- button "삭제" [ref=f26e153]
- generic [ref=f26e154]:
- generic [ref=f26e155]:
- generic [ref=f26e156]: 가정 2
- textbox "가정 2" [ref=f26e157]: 추정이 달라지면 플래너가 다른 계획을 고를 수 있다.
- generic [ref=f26e158]:
- button "위로" [ref=f26e159]
- button "아래로" [ref=f26e160]
- button "삭제" [ref=f26e161]
- generic [ref=f26e162]:
- generic [ref=f26e163]:
- generic [ref=f26e164]: 가정 3
- textbox "가정 3" [ref=f26e165]: 편중이 큰 컬럼은 기본 통계 대상 수로 부족할 수 있다.
- generic [ref=f26e166]:
- button "위로" [ref=f26e167]
- button "아래로" [disabled] [ref=f26e168]
- button "삭제" [ref=f26e169]
- button "가정 추가" [ref=f26e170]
- group "남은 미지수" [ref=f26e171]:
- generic [ref=f26e173]:
- generic [ref=f26e174]:
- generic [ref=f26e175]: 남은 미지수 1
- textbox "남은 미지수 1" [ref=f26e176]: 통계를 갱신한 뒤 추정 행수가 실제에 얼마나 가까워지는가.
- generic [ref=f26e177]:
- button "위로" [disabled] [ref=f26e178]
- button "아래로" [ref=f26e179]
- button "삭제" [ref=f26e180]
- generic [ref=f26e181]:
- generic [ref=f26e182]:
- generic [ref=f26e183]: 남은 미지수 2
- textbox "남은 미지수 2" [ref=f26e184]: 추정이 바뀌면 스캔 방식이 바뀌는가. 인덱스에서 순차 스캔으로, 또는 그 반대로 뒤집히는가.
- generic [ref=f26e185]:
- button "위로" [ref=f26e186]
- button "아래로" [ref=f26e187]
- button "삭제" [ref=f26e188]
- generic [ref=f26e189]:
- generic [ref=f26e190]:
- generic [ref=f26e191]: 남은 미지수 3
- textbox "남은 미지수 3" [ref=f26e192]: 편중이 큰 컬럼에 통계 대상 수를 늘리면 추정이 더 좋아지는가.
- generic [ref=f26e193]:
- button "위로" [ref=f26e194]
- button "아래로" [ref=f26e195]
- button "삭제" [ref=f26e196]
- generic [ref=f26e197]:
- generic [ref=f26e198]:
- generic [ref=f26e199]: 남은 미지수 4
- textbox "남은 미지수 4" [ref=f26e200]: 실행시간과 읽은 블록 수가 달라지는가.
- generic [ref=f26e201]:
- button "위로" [ref=f26e202]
- button "아래로" [ref=f26e203]
- button "삭제" [ref=f26e204]
- generic [ref=f26e205]:
- generic [ref=f26e206]:
- generic [ref=f26e207]: 남은 미지수 5
- textbox "남은 미지수 5" [ref=f26e208]: 이 차이가 지금까지의 결론을 바꾸는가. 왕복 수와 전송 행수에 관한 판단은 통계와 무관하다.
- generic [ref=f26e209]:
- button "위로" [ref=f26e210]
- button "아래로" [ref=f26e211]
- button "삭제" [ref=f26e212]
- generic [ref=f26e213]:
- generic [ref=f26e214]:
- generic [ref=f26e215]: 남은 미지수 6
- textbox "남은 미지수 6" [ref=f26e216]: 운영에서 대량 적재 후 통계 갱신을 절차에 넣을 것인가.
- generic [ref=f26e217]:
- button "위로" [ref=f26e218]
- button "아래로" [disabled] [ref=f26e219]
- button "삭제" [ref=f26e220]
- button "남은 미지수 추가" [ref=f26e221]
- group "제약" [ref=f26e222]:
- generic [ref=f26e224]:
- generic [ref=f26e225]:
- generic [ref=f26e226]: 제약 1
- textbox "제약 1" [ref=f26e227]: 통계 갱신 전후를 비교하려면 같은 데이터에서 연속으로 재야 한다. 캐시 상태가 섞이면 비교가 흐려진다.
- generic [ref=f26e228]:
- button "위로" [disabled] [ref=f26e229]
- button "아래로" [ref=f26e230]
- button "삭제" [ref=f26e231]
- generic [ref=f26e232]:
- generic [ref=f26e233]:
- generic [ref=f26e234]: 제약 2
- textbox "제약 2" [ref=f26e235]: 지금까지 기록한 실행계획은 모두 갱신 전 값이다. 갱신 후 값과 섞어 읽지 않도록 표기를 구분해야 한다.
- generic [ref=f26e236]:
- button "위로" [ref=f26e237]
- button "아래로" [ref=f26e238]
- button "삭제" [ref=f26e239]
- generic [ref=f26e240]:
- generic [ref=f26e241]:
- generic [ref=f26e242]: 제약 3
- textbox "제약 3" [ref=f26e243]: 실행하기 전에는 수치를 채우지 않는다.
- generic [ref=f26e244]:
- button "위로" [ref=f26e245]
- button "아래로" [disabled] [ref=f26e246]
- button "삭제" [ref=f26e247]
- button "제약 추가" [ref=f26e248]
- group "검토한 선택지" [ref=f26e249]:
- generic [ref=f26e251]:
- generic [ref=f26e252]:
- generic [ref=f26e253]: 선택지 1 제목
- textbox "선택지 1 제목" [ref=f26e254]: 통계를 갱신하고 전후를 비교한다
- generic [ref=f26e255]:
- generic [ref=f26e256]: 선택지 1 설명
- textbox "선택지 1 설명" [ref=f26e257]: 같은 데이터에서 갱신 전후 계획을 나란히 기록한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 대조한다. 측정이 한 번 더 필요하지만 가설을 닫을 수 있다.
- generic [ref=f26e258]:
- button "위로" [disabled] [ref=f26e259]
- button "아래로" [ref=f26e260]
- button "삭제" [ref=f26e261]
- generic [ref=f26e262]:
- generic [ref=f26e263]:
- generic [ref=f26e264]: 선택지 2 제목
- textbox "선택지 2 제목" [ref=f26e265]: 통계 대상 수까지 조절해 본다
- generic [ref=f26e266]:
- generic [ref=f26e267]: 선택지 2 설명
- textbox "선택지 2 설명" [ref=f26e268]: 편중이 큰 컬럼의 통계 대상 수를 늘린 뒤 다시 잰다. 기본값으로 부족한지 확인한다. 변수가 하나 더 늘어 비교가 복잡해진다.
- generic [ref=f26e269]:
- button "위로" [ref=f26e270]
- button "아래로" [ref=f26e271]
- button "삭제" [ref=f26e272]
- generic [ref=f26e273]:
- generic [ref=f26e274]:
- generic [ref=f26e275]: 선택지 3 제목
- textbox "선택지 3 제목" [ref=f26e276]: 갱신 전 값만 두고 넘어간다
- generic [ref=f26e277]:
- generic [ref=f26e278]: 선택지 3 설명
- textbox "선택지 3 설명" [ref=f26e279]: 왕복 수와 전송 행수에 관한 결론은 통계와 무관하다. 추정 차이를 한계로만 적고 진행한다. 플래너가 다른 계획을 고를 가능성을 확인하지 못한 채 남는다.
- generic [ref=f26e280]:
- button "위로" [ref=f26e281]
- button "아래로" [disabled] [ref=f26e282]
- button "삭제" [ref=f26e283]
- button "선택지 추가" [ref=f26e284]
- generic [ref=f26e285]:
- generic [ref=f26e286]: 다음 검증
- textbox "다음 검증" [ref=f26e287]: 1. 대량 시드 직후 현재 계획을 다시 기록한다. 갱신 전 값임을 명시한다. 2. 통계를 갱신한다. 3. 같은 쿼리를 같은 실행 안에서 다시 EXPLAIN한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 기록한다. 4. 두 계획을 나란히 두고 무엇이 달라졌는지 적는다. 5. 계획이 바뀌었다면 지금까지의 결론 중 영향을 받는 항목이 있는지 확인한다. 6. 운영 절차에 대량 적재 후 통계 갱신을 넣을지 판단한다.
- region [ref=f26e288]:
- generic [ref=f26e289]:
- paragraph [ref=f26e290]: LIVE
- heading "즉시 미리보기" [level=2] [ref=f26e291]
- generic [ref=f26e294]:
- generic [ref=f26e295]:
- navigation "문서 경로" [ref=f26e296]:
- link "열린 질문" [ref=f26e297] [cursor=pointer]:
- /url: /explore/questions
- generic [aria-hidden] [ref=f26e298]: /
- generic [ref=f26e299]: JPA 피드 조회 성능
- generic [aria-hidden] [ref=f26e300]: /
- link "Liner N + 1문제" [ref=f26e301] [cursor=pointer]:
- /url: /projects/liner-n-plus-1
- heading "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가" [level=1] [ref=f26e302]
- paragraph [ref=f26e303]: 반복되는 하이라이트 조회의 실행계획에서 추정 행수는 1이고 실제 행수는 500이었다. 대량 데이터를 넣은 직후 통계를 갱신하지 않아 편중을 담지 못했다는 가설을 세웠지만 아직 검증하지 않았다.
- generic [ref=f26e304]:
- generic [ref=f26e305]:
- term [ref=f26e306]: 유형
- definition [ref=f26e307]: 열린 질문
- generic [ref=f26e308]:
- term [ref=f26e309]: 프로젝트
- definition [ref=f26e310]: Liner N + 1문제
- generic [ref=f26e311]:
- term [ref=f26e312]: 게시
- definition [ref=f26e313]: 게시 전
- paragraph [ref=f26e314]: OPEN
- article [ref=f26e315]:
- region [ref=f26e316]:
- heading "확인한 사실" [level=2] [ref=f26e317]
- list [ref=f26e318]:
- listitem [ref=f26e319]:
- paragraph [ref=f26e320]: 대량 시드 직후 측정한 계획에서 추정 rows는 1, 실제 rows는 500이었다. 500배 차이다.
- listitem [ref=f26e321]:
- paragraph [ref=f26e322]: 이 계획은 feed_item_id 조건을 인덱스로 처리했고 실행시간은 0.173 ms였다.
- listitem [ref=f26e323]:
- paragraph [ref=f26e324]: 읽은 블록은 모두 캐시에서 왔다. 디스크 읽기는 0이었다.
- listitem [ref=f26e325]:
- paragraph [ref=f26e326]: 하이라이트 개수는 순위 기반 편중 분포라 feed_item_id별 자식 수가 크게 다르다. 상한 500, 하한 1이다.
- listitem [ref=f26e327]:
- paragraph [ref=f26e328]: fetch join 조인 계획에서도 추정 4,202와 실제 1,961의 차이가 있었다.
- listitem [ref=f26e329]:
- paragraph [ref=f26e330]: 시드 직후 통계 갱신 명령을 실행하지 않았다.
- region [ref=f26e331]:
- heading "가정" [level=2] [ref=f26e332]
- list [ref=f26e333]:
- listitem [ref=f26e334]:
- paragraph [ref=f26e335]: 통계를 갱신하면 feed_item_id별 분포가 반영되어 추정이 실제에 가까워진다.
- listitem [ref=f26e336]:
- paragraph [ref=f26e337]: 추정이 달라지면 플래너가 다른 계획을 고를 수 있다.
- listitem [ref=f26e338]:
- paragraph [ref=f26e339]: 편중이 큰 컬럼은 기본 통계 대상 수로 부족할 수 있다.
- region [ref=f26e340]:
- heading "남은 미지수" [level=2] [ref=f26e341]
- list [ref=f26e342]:
- listitem [ref=f26e343]:
- paragraph [ref=f26e344]: 통계를 갱신한 뒤 추정 행수가 실제에 얼마나 가까워지는가.
- listitem [ref=f26e345]:
- paragraph [ref=f26e346]: 추정이 바뀌면 스캔 방식이 바뀌는가. 인덱스에서 순차 스캔으로, 또는 그 반대로 뒤집히는가.
- listitem [ref=f26e347]:
- paragraph [ref=f26e348]: 편중이 큰 컬럼에 통계 대상 수를 늘리면 추정이 더 좋아지는가.
- listitem [ref=f26e349]:
- paragraph [ref=f26e350]: 실행시간과 읽은 블록 수가 달라지는가.
- listitem [ref=f26e351]:
- paragraph [ref=f26e352]: 이 차이가 지금까지의 결론을 바꾸는가. 왕복 수와 전송 행수에 관한 판단은 통계와 무관하다.
- listitem [ref=f26e353]:
- paragraph [ref=f26e354]: 운영에서 대량 적재 후 통계 갱신을 절차에 넣을 것인가.
- region [ref=f26e355]:
- heading "제약" [level=2] [ref=f26e356]
- list [ref=f26e357]:
- listitem [ref=f26e358]:
- paragraph [ref=f26e359]: 통계 갱신 전후를 비교하려면 같은 데이터에서 연속으로 재야 한다. 캐시 상태가 섞이면 비교가 흐려진다.
- listitem [ref=f26e360]:
- paragraph [ref=f26e361]: 지금까지 기록한 실행계획은 모두 갱신 전 값이다. 갱신 후 값과 섞어 읽지 않도록 표기를 구분해야 한다.
- listitem [ref=f26e362]:
- paragraph [ref=f26e363]: 실행하기 전에는 수치를 채우지 않는다.
- region [ref=f26e364]:
- heading "검토한 선택지" [level=2] [ref=f26e365]
- list [ref=f26e366]:
- listitem [ref=f26e367]:
- heading "통계를 갱신하고 전후를 비교한다" [level=3] [ref=f26e368]
- paragraph [ref=f26e369]: 같은 데이터에서 갱신 전후 계획을 나란히 기록한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 대조한다.
- paragraph [ref=f26e370]: 측정이 한 번 더 필요하지만 가설을 닫을 수 있다.
- listitem [ref=f26e371]:
- heading "통계 대상 수까지 조절해 본다" [level=3] [ref=f26e372]
- paragraph [ref=f26e373]: 편중이 큰 컬럼의 통계 대상 수를 늘린 뒤 다시 잰다. 기본값으로 부족한지 확인한다.
- paragraph [ref=f26e374]: 변수가 하나 더 늘어 비교가 복잡해진다.
- listitem [ref=f26e375]:
- heading "갱신 전 값만 두고 넘어간다" [level=3] [ref=f26e376]
- paragraph [ref=f26e377]: 왕복 수와 전송 행수에 관한 결론은 통계와 무관하다. 추정 차이를 한계로만 적고 진행한다.
- paragraph [ref=f26e378]: 플래너가 다른 계획을 고를 가능성을 확인하지 못한 채 남는다.
- region [ref=f26e379]:
- paragraph [ref=f26e380]: Next
- heading "다음 검증" [level=2] [ref=f26e381]
- paragraph [ref=f26e382]: 1. 대량 시드 직후 현재 계획을 다시 기록한다. 갱신 전 값임을 명시한다.
- paragraph [ref=f26e383]: 2. 통계를 갱신한다.
- paragraph [ref=f26e384]: 3. 같은 쿼리를 같은 실행 안에서 다시 EXPLAIN한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 기록한다.
- paragraph [ref=f26e385]: 4. 두 계획을 나란히 두고 무엇이 달라졌는지 적는다.
- paragraph [ref=f26e386]: 5. 계획이 바뀌었다면 지금까지의 결론 중 영향을 받는 항목이 있는지 확인한다.
- paragraph [ref=f26e387]: 6. 운영 절차에 대량 적재 후 통계 갱신을 넣을지 판단한다.
- region [ref=f26e388]:
- paragraph [ref=f26e389]: Next
- heading "다음에 읽을 것" [level=2] [ref=f26e390]
- list [ref=f26e391]:
- listitem [ref=f26e392]:
- link "검증 기록 Fetch 타입이 아닌 조회 방식으로 인한 N+1" [ref=f26e393] [cursor=pointer]:
- /url: /cases/eager-toone-nplus1-without-access
- generic [ref=f26e394]: 검증 기록
- strong [ref=f26e396]: Fetch 타입이 아닌 조회 방식으로 인한 N+1
- generic [aria-hidden] [ref=f26e397]:
- complementary [ref=f26e398]:
- heading "작업 상태" [level=2] [ref=f26e399]
- status "편집 상태" [ref=f26e400]: 저장되지 않음
- generic [ref=f26e401]:
- generic [ref=f26e402]:
- term [ref=f26e403]: 저장 버전
- definition [ref=f26e404]: "6"
- generic [ref=f26e405]:
- term [ref=f26e406]: 종류
- definition [ref=f26e407]: 열린 질문
- paragraph [ref=f26e408]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
- generic [ref=f26e409]:
- button "저장" [ref=f26e410]
- button "게시" [ref=f26e411]
- paragraph [ref=f26e412]