docs(n+1liner): adopt the decomposition contract and strip evaluative prose

- 계약 채택 — schema 2 → 4. 독자 질문, 후보 36건(PROMOTE 24 · MERGE_INTO 8 ·
  KEEP_IN_SSOT 4), 종류별 칸, kind:slug 관계. 저장소 github-project/ca-tmpl @ 761384d
  (문서가 인용한 IT 7종이 그 커밋에 있다)
- 재선별 결과 기존 24건이 전부 살아남았다. 측정이 Reference 안에 들어 있었지만 독립성 검사를
  통과하지 못해 Case 로 빼지 않았다
- 평문 칸의 백틱 제거, 「기준선」을 실제 이름으로, frontmatter 에 source·sourceRevision
- 리뷰 39건 반영 — 설명 뒤에 붙은 평가·예고·되풀이·독자 오해 가정을 지웠다. 사실을 담은
  절반이 있는 문장은 평가만 뺐다

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 12:39:20 +09:00
co-authored by Claude Fable 5.1
parent b25357c48a
commit 62520a4dce
28 changed files with 1818 additions and 838 deletions
@@ -4,85 +4,88 @@ kind: QUESTION
slug: concurrency-stability
title: 실제 동시 트래픽에서도 이 구조가 안정적인가
topic: jpa-feed-query-performance
topicName: JPA 피드 조회 성능 — N+1 진단과 조회 전략의 진화
topicName: JPA 피드 조회 성능
project: Liner N + 1문제
status: 게시 전
studio: "https://hyeonworks.com/studio/documents/6cbe963f-86f8-4df6-be5a-900712970d01/edit"
questionStatus: OPEN
sourceRevision: n+1liner-lab@2026-08
source:
- final/document.md#4-5
- final/document.md#18
---
# 실제 동시 트래픽에서도 이 구조가 안정적인가
지금까지의 측정은 단일 스레드 퍼시스턴스 통합 테스트에서 조회 횟수의 증가 형태를 확인한 것이다. 처리량, 커넥션 풀 안정성, 동시성은 이 측정의 범위 밖이라 조회 구조가 실제 부하를 견디는지 아직 모른다.
지금까지의 측정은 단일 스레드 퍼시스턴스 통합 테스트에서 아이템 수 N을 키울 때 쿼리 수와 읽는 행수가 얼마나 늘어나는지만 확인했다. 여러 요청이 겹칠 때의 처리량 커넥션 풀 안정성은 이 테스트가 재지 않았으므로, 조회 구조가 실제 부하를 견디는지 아직 모른다.
한 요청만 놓고 보면 왕복 수는 페이지 크기에 묶여 있다. 그 요청당 왕복이 트래픽에 곱해진 값은 요청을 동시에 보내야 나오고, 그 측정은 하지 않았다.
## 관계
- **PostgreSQL Query Plan 측정 기준**
측정 범위와 도구 선택을 정한 기준이다.
무엇을 어디까지 재고 어떤 도구로 잴지 그 기록에서 정했다.
- **feed_visible을 Production CQRS로 승격할 것인가**
부하 결과가 필요한 다른 판단이다.
고트래픽 읽기에서 사전계산이 실제로 필요한지는 부하 결과를 봐야 정할 수 있다.
## 사실
- 측정은 단일 스레드에서 이미 열린 테스트 트랜잭션 안의 어댑터 호출만 쟀다. HTTP 종단 거치지 않았다.
- 지연 값은 warm cache 상태의 로컬 비교값이다. 표본은 7회 중 앞 2회를 버린 5개다.
- 표본이 적어 백분위수 대신 중앙값과 최댓값으로 기록했다.
- 왕복 수는 2,022개에서 23개로, 엔티티 로드는 1,569개에서 0개로, 자식 전송은 1,509행에서 최대 60행으로 줄었다.
- 깊은 페이지 조회는 2,000행 대신 20행 읽었다.
- 문서 첫머리에 고트래픽 처리량, 커넥션 풀 안정성, 동시성이 범위 밖임을 명시했다.
- 측정은 단일 스레드에서 이미 열린 테스트 트랜잭션 안의 어댑터 호출만 쟀고, HTTP 종단 거치지 않았다.
- 지연 값은 DB 캐시가 이미 채워진 상태에서 잰 로컬 비교값이고, 7회 반복 중 앞 2회를 워밍업으로 버린 5개가 표본이다.
- 표본이 5개뿐이어서 p50·p99 대신 중앙값과 최댓값으로 적었다.
- N=1,000에서 왕복 수는 2,022개에서 23개로, 엔티티 로드는 1,569개에서 0개로, 자식 전송은 1,509행에서 최대 60행으로 줄었다.
- 100번째 페이지에서 OFFSET은 20행을 만들려고 2,000행을 읽었고 keyset은 20행 읽었다.
- 고트래픽 처리량 커넥션 풀 안정성, 동시성은 이 측정의 범위 밖이라고 문서 첫머리에 적어 두었다.
- 커넥션 풀과 Little's Law는 이번 조회 문제를 해결한 뒤 별도 주제로 검증할 수 있다고 다음 단계에 함께 적어 두었다.
## 가정
- 왕복 수가 줄면 같은 트래픽에서 DB 부하도 줄어든다.
- 요청 왕복이 처리량에 곱해지므로 왕복 감소는 처리량 한계다.
- 단일 스레드에서 확인한 조회 형태는 동시 실행에서도 유지된다.
- 요청마다 반복되는 왕복이 처리량에 곱해지므로, 왕복이 줄면 처리량 한계라간다.
- 단일 스레드에서 잰 요청당 쿼리 수와 읽는 행수는 여러 요청이 동시에 돌아도 달라지지 않는다.
## 미지수
- 목표 처리량에서 커넥션 풀이 포화되는가. 풀 크기와 대기 시간은 어떻게 되는가.
- 동시 실행에서 지연 분포가 어떻게 되는가. 꼬리 지연 어디까지 늘어나는가.
- 배치 크기 설정이 동시 실행에서 어떻게 작동하는가. 세션마다 독립인가.
- 깊은 페이지 요청이 섞이면 전체 지연에 어떤 영향을 주는가.
- 캐시가 차갑거나 통계가 갱신되지 않은 상태에서 계획이 달라지는가.
- 어느 지표를 운영 알람 기준으로 삼을 것인가.
- 목표 처리량에서 커넥션 풀이 포화되는가. 포화되지 않으려면 풀을 몇 개로 잡아야 하고 요청은 얼마나 기다리는가.
- 요청이 동시에 몰릴 때 지연이 어느 구간에 몰리는가. 꼬리 지연 어디까지 벌어지는가.
- hibernate.default_batch_fetch_size=100은 세션 전체에 걸리는 설정인데, 여러 세션이 동시에 도는 동안에도 세션마다 따로 적용되는가.
- 깊은 페이지 요청이 섞이면 전체 지연이 얼마나 밀리는가.
- DB 캐시가 비어 있거나 ANALYZE를 돌리지 않아 통계가 낡은 상태에서 실행계획이 달라지는가.
- 운영 알람 기준으로 어느 지표를 삼을 것인가.
## 제약
- 단일 스레드 값으로는 동시성 질문에 답할 수 없다. 도구를 바꿔야 한다.
- 안정적인 꼬리 지연을 말하려면 워밍업 후 반복 횟수를 크게 늘린 독립 세트가 여러 개 필요하다.
- 부하 테스트 환경이 운영과 다르면 결과를 그대로 옮길 수 없다. 데이터 규모와 하드웨어를 맞춰야 한다.
- 로컬에서 확인한 것을 운영에서 확인한 것으로 승격하지 않는다.
- 단일 스레드에서 잰 값으로는 동시 실행을 말할 수 없어서, 부하 테스트와 APM(Application Performance Monitoring, 애플리케이션 성능 모니터링)으로 도구를 바꿔야 한다.
- 안정적인 꼬리 지연(p99)을 말하려면 워밍업 뒤 100회 넘게 반복한 독립 세트가 여러 개 있어야 한다.
- 부하 테스트 환경의 데이터 규모와 하드웨어를 운영에 맞추지 않으면 거기서 나온 결과를 운영으로 그대로 옮길 수 없다.
## 선택지
### 1. 부하 테스트와 APM으로 확인한다
목표 처리량을 정하고 종단 지연, 처리량, 커넥션 풀 지표를 함께 잰다. 조회 구조의 개선이 부하에서도 나타나는지 본다.
환경 구성과 데이터 준비에 시간이 든다.
목표 처리량을 먼저 정하고 HTTP 종단 지연 처리량, 커넥션 풀 사용률을 함께 잰다. 왕복이 줄어든 만큼 처리량이 올라가는지 부하에서 확인한다.
### 2. 반복 횟수를 늘린 지연 측정부터 한다
같은 단일 스레드 조건에서 표본을 크게 늘려 꼬리 지연을 먼저 안정화한다. 동시성은 아직 다루지 않는다.
동시성 질문에는 여전히 답하지 못한다.
같은 단일 스레드 조건에서 반복을 100회 넘게 늘려 꼬리 지연부터 안정시킨다. 동시성은 아직 다루지 않는다.
### 3. 현재 범위를 명시하고 운영 판단은 미룬다
조회 형태 개선까지만 주장하고 처리량은 다루지 않는다. 필요해때 부하 테스트를 연다.
쿼리 수와 읽는 행수가 줄었다는 데까지만 주장하고 처리량은 다루지 않는다. 필요해지면 그때 부하 테스트를 연다.
운영에 올린 뒤 문제를 발견할 위험이 남는다.
운영에 올린 뒤에야 문제를 발견할 수 있다.
## 다음 검증
1. 목표 처리량과 허용 지연을 먼저 정한다. 기준이 없으면 결과를 판정할 수 없다.
1. 목표 처리량과 허용 지연을 먼저 정한다. 이 두 값이 없으면 부하 결과가 통과인지 실패인지 판정할 수 없다.
2. 운영과 비슷한 데이터 규모를 준비한다. 편중 분포를 유지한다.
2. 운영과 비슷한 데이터 규모를 준비하되 하이라이트 개수의 Zipf 형태 편중은 그대로 둔다.
3. 부하 테스트로 종단 지연과 처리량을 측정한다. 커넥션 풀 사용률과 대기 시간을 함께 본다.
3. 부하 테스트로 HTTP 종단 지연과 처리량을 측정하고, 커넥션 풀 사용률과 대기 시간을 함께 본다.
4. 개선 전후 구조를 같은 조건에서 비교한다. 왕복 감소가 처리량으로 이어지는지 확인한다.
4. 개선 전후 구조를 같은 조건에서 비교해 왕복이 줄어든 것이 처리량으로 이어지는지 확인한다.
5. 깊은 페이지 얕은 페이지 요청을 섞어 지연 분포를 본다.
5. 깊은 페이지 요청과 얕은 페이지 요청을 섞어 지연이 어디까지 벌어지는지 본다.
6. 운영 알람으로 쓸 지표를 정한다.