- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
129 lines
6.5 KiB
Markdown
129 lines
6.5 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: analysis-finding-a16-f003
|
|
title: 5계층 예산 모델에서 요청 계층만 강제되고, 나머지 파생이 전부 미배선이다
|
|
topic: graphql-surface
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:analysis-finding-a16-f003
|
|
evidenceCapturedOn: 2026-09-01
|
|
body: case-analysis-finding-a16-f003.body.md
|
|
assets:
|
|
- key: analysis-finding-a16-f003
|
|
file: ../../../final/evidence/rendered/analysis-finding-a16-f003.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/analysis-finding-a16-f003.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#a16#L382 이다.
|
|
---
|
|
|
|
# 5계층 예산 모델에서 요청 계층만 강제되고, 나머지 파생이 전부 미배선이다
|
|
|
|
마감 전파기의 자바독이 다섯 계층이 왜 분리되는지 정확히 적는다. 그 파생을 수행하는 다섯 메서드 전부 프로덕션 호출자가 0 이다. 요청 전체 마감만 작동한다.
|
|
|
|
## 관계
|
|
|
|
- **스키마 조립과 계약 정체성과 해시 사슬이 통째로 미배선이고 그것을 발행할 엔드포인트도 등록되지 않는다**
|
|
같은 리프의 다른 미배선 사슬이다.
|
|
- **용량 보호 계층 전체가 자기 테스트 픽스처 안에서만 실행된다**
|
|
같은 형태의 미등록 사례다.
|
|
- **계층을 나눈 이유가 코드에 적혀 있어도 파생이 없으면 계층은 하나다**
|
|
이 사례가 그 규칙의 형태다.
|
|
|
|
## 문제
|
|
|
|
이 리프는 마감을 다섯 계층으로 나눈다.
|
|
|
|
마감 전파기의 자바독이 그 이유를 적는다.
|
|
|
|
계층이 서로 다른 것을 뜻하고 서로 다른 시점에 만료되기 때문이라는 것이다. 전송 악수와 요청 실행과 하나의 해석기와 하나의 적재 배치, 그리고 구독의 경우 연결 자체다.
|
|
|
|
마지막 것은 의도적으로 요청 예산에서 파생하지 않는다는 것이다. 구독은 오래 사는 흐름이고 오 초 요청 시간 제한을 적용하면 모든 구독이 시작 오 초 뒤에 끝나기 때문이라는 것이다.
|
|
|
|
## 결론
|
|
|
|
그 파생을 수행하는 메서드 다섯 개 전부 프로덕션 호출자가 0 이다.
|
|
|
|
실제로 강제되는 것과 아닌 것이 갈린다.
|
|
|
|
요청 전체 마감은 작동한다. 플랫폼 인터셉터가 만들고 취소 장치가 끊는다.
|
|
|
|
개별 해석기가 남은 요청 예산으로 잘리는 것은 없다.
|
|
|
|
적재 배치 시간 제한이 남은 요청 예산으로 잘리는 것도 없다.
|
|
|
|
하류 호출에 남은 예산이 전달되는 것도 없다.
|
|
|
|
실패 시나리오는 이렇다.
|
|
|
|
요청 예산이 오 초이고 해석기 하나가 하류 호출을 부른다.
|
|
|
|
그 호출에 전달되는 마감은 나가는 어댑터 자신의 기본값이고, 남은 요청 예산이 일 초라는 사실은 전달되지 않는다.
|
|
|
|
요청은 오 초에 취소되지만 하류 호출은 계속 진행되어 연결과 스레드를 사 초 더 붙잡는다.
|
|
|
|
전파기의 하류 마감 메서드가 정확히 그 죄기를 위해 존재한다.
|
|
|
|
적재 쪽은 더 직접적이다.
|
|
|
|
배치 문맥이 마감을 레코드 성분으로 갖지만, 그 값을 남은 요청 예산으로 잘라 넣는 코드가 배치 시간 제한 메서드이고 호출자가 없다.
|
|
|
|
권고는 전파기를 빈으로 등록하고 세 지점에 연결하는 것이다.
|
|
|
|
배치 등록기가 배치 시간 제한을, 해석기 실행 경로가 해석기 예산을, 나가는 포트 호출 지점이 하류 마감을 쓰게 한다.
|
|
|
|
구독 계층은 별도 하위 범위에서 확인한다.
|
|
|
|
판정은 P2 다.
|
|
|
|
## 검증 환경
|
|
|
|
확인 방식 : 파생 메서드 호출자 계수와 강제 지점 확인
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
원문은 final/evidence/raw/182 계열에 있다.
|
|
|
|
1. 마감 전파기의 자바독을 읽는다.
|
|
2. 파생 메서드 다섯을 나열한다.
|
|
3. 각 메서드의 프로덕션 호출자를 센다.
|
|
4. 요청 전체 마감이 어디서 만들어지고 어디서 끊기는지 확인한다.
|
|
5. 배치 문맥이 마감을 어떻게 받는지 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
`GraphQlDeadlinePropagator`의 javadoc이 계층 분리의 이유를 정확히 적는다.
|
|
|
|
> "The layers are separate because they mean different things and expire at different points: a transport handshake, the request execution, one resolver, one DataLoader batch, and — for a subscription — the connection itself. The last one is deliberately not derived from the request budget: **a subscription is a long-lived stream, and applying a five-second request timeout to it would terminate every subscription five seconds after it started.**"
|
|
|
|
## GraphQlDeadlinePropagator 참조 위치
|
|
|
|
:::evidence key="analysis-finding-a16-f003" alt="코드베이스에서 GraphQlDeadlinePropagator 를 검색한 출력 2줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="GraphQlDeadlinePropagator 코드베이스 검색 — 2줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
## 파생 메서드 다섯 전부 호출자가 0이다
|
|
|
|
§11.1·§11.3.
|
|
|
|
## 강제되는 것과 아닌 것
|
|
|
|
요청 전체 데드라인은 `GraphQlPlatformWebInterceptor`가 만들고 `GraphQlCancellation`이 끊는다 — 작동한다. 개별 리졸버가 남은 요청 예산으로 잘리는 것, DataLoader 배치 타임아웃이 남은 요청 예산으로 잘리는 것, DB/HTTP 다운스트림 호출에 남은 예산이 전달되는 것은 없다.
|
|
|
|
## 실패 시나리오
|
|
|
|
요청 예산이 5초이고 리졸버 하나가 다운스트림 HTTP를 부른다. 그 호출에 전달되는 데드라인은 아웃바운드 어댑터 자신의 기본값(예: 10초)이고, 남은 요청 예산이 1초라는 사실은 전달되지 않는다. 요청은 5초에 취소되지만 다운스트림 호출은 계속 진행되어 연결과 스레드를 4초 더 붙잡는다. `GraphQlDeadlinePropagator.downstreamDeadline`이 정확히 그 clamping을 위해 존재한다. DataLoader 쪽은 더 직접적이다 — `dataloader/GraphQlBatchContext`가 `GraphQlDeadline`을 레코드 컴포넌트로 갖지만, 그 값을 남은 요청 예산으로 잘라 넣는 코드가 `dataLoaderBatchTimeout`이고 호출자가 없다. P2.
|
|
|
|
## 권고
|
|
|
|
`GraphQlDeadlinePropagator`를 빈으로 등록하고 세 지점에 연결한다 — `GraphQlBatchLoaderRegistrar`(autoconf=4)가 배치 타임아웃을, 리졸버 실행 경로가 `resolverBudget`을, 아웃바운드 포트 호출 지점이 `downstreamDeadline`을 쓰게 한다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
요청 취소 후 하류 호출이 계속되는 것을 실행으로 재현하지 않았다. 호출자 부재상 그 결과가 나온다.
|
|
|
|
<!-- body:end -->
|