- 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>
17 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | analysis-finding-a06-f008 | 데드라인을 서버로 보내는 접근자를 부르는 프로덕션 코드가 없다 | multitenancy-isolation | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:analysis-finding-a06-f008 | 2026-09-04 | case-analysis-finding-a06-f008.body.md |
|
|
|
데드라인을 서버로 보내는 접근자를 부르는 프로덕션 코드가 없다
BoundScopedOperations 가 질의에 데드라인을 붙이는 자리는 :53 과 :98 둘이다. 그 타입을 돌려주는 scoped() 를 부르는 줄이 저장소 전체에 0 이고, SpringMongoGeospatialOperations 와 MongoAtomicOperationsTemplate 와 MongoBulkExecutor 는 MongoRawAccessBoundaryTest:20 이 데드라인이 없다고 적은 rawOperations() 를 다섯 번 부른다.
관계
- 데드라인은 호출 예산에서 시작해 세 단계로 좁힌다
그 규칙의 세 번째 단계인 데이터베이스 로컬 타임아웃이 MongoDB 에서는 질의에
maxTimeMS를 붙이는 것이다. 그 값을 붙이는 타입이 이 리프에 있는데 호출자가 없다. - 쓰기 트랜잭션에는 유한 타임아웃이 필수다
MongoOperationContext가 양수 타임아웃을 요구하므로 선언은 언제나 유한하다. 그 값이 서버까지 가는지는 별개이고, 여기서는 세 경로에서 가지 않는다. - @Bean이 있다는 것은 조립 증거가 아니다
그 규칙은 프로덕션 참조가 0 이면 조립되지 않은 것으로 본다.
scoped()는 참조가 0 은 아니어서 인터페이스 둘과 구현 둘에 선언이 있는데, 그것을 부르는 프로덕션 코드만 없다.
문제
모든 연산은 타임아웃을 선언해야 한다. 그 값을 maxTimeMS 로 붙여 서버까지 보내는 타입이 BoundScopedOperations 다.
그 타입에 실제로 도달하는 경로가 있는지 확인했다.
결론
그 자바독 :19~:23 은 질의에 maxTimeMS 를 함께 보내는지가 데드라인을 실제로 거는 것과 초과를 보고만 하는 것을 가르는 차이라고 적는다. 사후 측정은 초과를 알려 줄 뿐이고 서버가 받은 숫자는 질의를 끊는다는 것이다. :51~:54 의 bounded 가 질의 형태 메서드에 maxTimeMsec 을 붙이고 :95~:99 가 집계에 maxTime 을 붙인다.
ScopedMongoOperations 를 돌려주는 메서드는 MongoCollectionAccess:32 의 scoped() 하나인데, 자바 파일 6444 개를 훑어도 그것을 부르는 줄이 없다.
대신 SpringMongoGeospatialOperations:58·:82 와 MongoAtomicOperationsTemplate:84·:92 와 MongoBulkExecutor:69 가 MongoPlatformCollectionAccess:19 의 rawOperations() 를 쓴다.
그것이 실수가 아니라는 것은 MongoPlatformCollectionAccess:6~:14 와 MongoRawAccessBoundaryTest:24~:26 두 곳에 적혀 있다. 네 실행기가 드라이버 수준 명령을 만들려면 제한 없는 템플릿이 필요하다는 것이고, 그래서 규칙은 그 손잡이를 지우는 것이 아니라 플랫폼 밖에서 부르지 못하게 하는 것이다.
그 다섯 줄이 받는 MongoOperations 에는 데드라인이 붙지 않는다. MongoRawAccessBoundaryTest:18~:20 이 그 손잡이가 컬렉션 결속도 일관성 템플릿도 데드라인도 결과 예산도 없는 무제한 템플릿을 내준다고 적는다. 세 파일 안에서 시간 제한을 거는 줄도 0 인데, 같은 검색이 BoundScopedOperations 에서는 8 줄을 찾는다.
서버에 데드라인이 붙는 경로는 따로 셋 있다. PolicyAwareMongoAggregationExecutor:96 이 등록된 예산과 컨텍스트 밀리초의 최솟값을 쓰고, PolicyAwareMongoQueryBuilder:200 과 MongoReactiveCursorPublisher:58 은 등록된 예산 값만 쓴다.
context.timeout() 을 읽는 자리를 전부 세면 예산을 좁히는 것은 PolicyAwareMongoAggregationExecutor:92 하나다. 나머지는 DefaultMongoImperativeExecutor 의 사후 검사와 DefaultReactiveMongoExecutor 의 클라이언트 쪽 timeout 연산자다. 연산이 선언한 타임아웃이 서버까지 닿는 길은 집계 하나이고, 그것도 등록된 예산과의 최솟값으로만 간다. 나머지 셋에는 사후 검사만 남는데, DefaultMongoImperativeExecutor:94~:96 의 주석이 그 검사가 작업을 끊을 수 없다고 스스로 적는다.
검증 환경
OpenJDK : 21.0.12 확인 방식 : 그 타입의 자바독과 데드라인을 붙이는 두 자리 인용, 두 접근자의 선언과 raw 접근이 의도된 이유를 적은 자바독 인용, 두 이름을 부르는 줄을 저장소 전체에서 계수하고 등장 자리 전수와 훑은 파일 수 대조, rawOperations() 구현 인용, 집계 실행기의 데드라인 계산과 maxTimeMillis() 와 context.timeout() 을 부르는 자리 전수, 세 실행기의 조립 상태와 그 안의 데드라인 계수와 대조, 경계 가드 시험의 자바독과 금지 목록 인용, 사후 검사 분기와 그 주석 인용 소스 수정 : x
재현 조건
- BoundScopedOperations 의 클래스 자바독과 maxTime 을 거는 줄을 인용한다.
- scoped() 와 rawOperations() 의 선언을 두 인터페이스에서 인용한다.
- 두 이름을 부르는 줄을 각각 세고, 선언까지 포함해 등장 자리를 전부 나열하고, 훑은 파일 수를 함께 낸다.
- rawOperations() 구현이 무엇을 돌려주는지 인용한다.
- 집계 실행기가 데드라인을 만드는 자리와 maxTimeMillis() 를 부르는 자리를 전부 인용한다.
- 사후 경과 검사 분기와 그 주석을 인용한다.
본문
MongoOperationContext 는 모든 연산에 타임아웃을 선언하게 한다. 그 값이 드라이버 명령에 실려 나가려면 질의에 maxTimeMS 로 붙어야 한다.
BoundScopedOperations 가 존재하는 이유
:::evidence key="analysis-finding-a06-f008" alt="저장소 루트에서 돌린 정적 검색 출력 214줄. 먼저 BoundScopedOperations 1227번 클래스 자바독이 실린다. 1317번은 이것이 물리 컬렉션 하나 위의 ScopedMongoOperations 이고 모든 메서드가 이 범위가 만들어질 때 받은 컬렉션을 넘기며, 호출자가 다른 것을 줄 수 없으므로 등록된 컬렉션 프로파일과 거기서 파생된 테넌트 경계가 호출자의 협조 없이 성립한다고 적는다. 1923번은 모든 질의 형태 메서드가 연산의 데드라인을 maxTimeMS 로 함께 보내며 그것이 데드라인과 그것에 대한 보고를 가르는 차이라고 적고, 블로킹 실행기는 콜백이 반환된 뒤에야 경과 시간을 잴 수 있으며 자바 콜백은 드라이버 호출 중간에 끊을 수 없어서 예산을 넘긴 연산은 탐지될 뿐 중단되지 않았지만 같은 숫자를 서버로 보내면 작업이 끝난다고 적는다. 2526번은 그것이 Query 나 Aggregation 을 받는 메서드에 적용되며 insert 는 붙일 질의가 없다고 적는다. 그 아래 maxTimeMS 가 실제로 붙는 자리로 자바독 19번과 98번의 점 maxTime 괄호 timeout 이 나온다. 이어서 MongoCollectionAccess 2436번과 MongoPlatformCollectionAccess 1024번이 실려 각각 scoped 와 rawOperations 선언을 보인다. 다음으로 scoped 를 부르는 줄이 0 개이고 rawOperations 를 부르는 줄이 5 개라고 나오며, 두 이름이 나오는 자리가 소스 세트와 함께 전부 나열된다 — SpringMongoGeospatialOperations 58번과 82번, DefaultMongoImperativeExecutor 184번과 192번의 선언, MongoCollectionAccess 32번과 MongoPlatformCollectionAccess 19번의 선언, MongoAtomicOperationsTemplate 84번과 92번, MongoBulkExecutor 69번, DefaultReactiveMongoExecutor 218번과 ReactiveMongoCollectionAccess 31번의 리액티브 쪽 선언이며 전부 main 이다. 그 검색이 훑은 파일은 491 개다. 이어서 DefaultMongoImperativeExecutor 180196번이 실려 184번의 scoped 가 BoundScopedOperations 를 만들고 192194번의 rawOperations 가 경계 없는 MongoOperations 를 그대로 돌려주는 것이 보인다. 다음으로 PolicyAwareMongoAggregationExecutor 88108번이 실리는데 91번 주석이 컨텍스트의 타임아웃은 호출자의 데드라인이며 maxTimeMS 를 좁힐 수는 있어도 늘릴 수는 없다고 적고, 96번이 registered.maxTimeMillis 와 contextMillis 의 최솟값을 쓰며, 106번이 그 값을 AggregationOptions 의 maxTime 에 넣는다. 그 아래 maxTimeMillis 를 부르는 자리가 전부 나열되는데 PolicyAwareMongoAggregationExecutor 96번과 106번, PolicyAwareMongoQueryBuilder 200번, MongoReactiveCursorPublisher 58번이다. 마지막으로 DefaultMongoImperativeExecutor 85106번이 실려 88번이 콜백을 부르고 89번이 경과 시간을 재며 93번이 그것을 컨텍스트 타임아웃과 견주고, 94~96번 주석이 컨텍스트가 모든 연산에 데드라인을 선언하는데 아무것도 그것과 견주지 않았으며 콜백은 드라이버 호출 중간에 끊을 수 없어 이것이 작업을 끊지는 못하지만 예산을 넘긴 작업에 성공을 보고하는 것보다는 낫다고 적는다." caption="데드라인을 서버로 보내는 타입의 자바독과 maxTime 을 거는 줄 · 두 접근자의 선언 · scoped 호출 0 과 rawOperations 호출 다섯의 전수 · rawOperations 가 돌려주는 것 · 서버에 데드라인을 붙이는 다른 세 경로 · 사후 경과 검사와 그 주석 — 214줄 · exit 0" zoom="true"
:::
BoundScopedOperations:15~:17 이 첫 목적을 적는다. 모든 메서드가 이 범위를 만들 때 받은 컬렉션을 넘기므로 호출자가 다른 것을 줄 수 없고, 등록된 컬렉션 프로파일과 거기서 파생된 테넌트 경계가 호출자의 협조 없이 성립한다.
:19~:23 이 두 번째다. 질의 형태 메서드가 연산의 데드라인을 maxTimeMS 로 함께 보내는 것이 데드라인과 그것에 대한 보고를 가르는 차이라는 것이다.
이유도 함께 있다. 블로킹 실행기는 콜백이 반환된 뒤에야 경과 시간을 잴 수 있고 자바 콜백은 드라이버 호출 중간에 끊을 수 없으므로, 예산을 넘긴 연산은 탐지될 뿐 중단되지 않았다. 같은 숫자를 서버로 보내면 작업이 끝난다.
붙이는 자리는 둘이다. :51~:54 의 private bounded 가 query.maxTimeMsec(timeout.toMillis()) 로 질의 형태 메서드에 붙이고, :95~:99 가 AggregationOptions 의 maxTime 으로 집계에 붙인다. :25~:26 은 그것이 Query 나 Aggregation 을 받는 메서드에만 붙는다고도 적는다. insert 에는 붙일 질의가 없다.
scoped() 와 rawOperations() 가 돌려주는 것
MongoCollectionAccess:32 의 scoped() 가 ScopedMongoOperations 를 돌려준다. 콜백이 그것을 받으면 데드라인이 붙은 연산을 쓴다.
MongoPlatformCollectionAccess:19 의 rawOperations() 는 MongoOperations 를 돌려준다.
그 인터페이스의 자바독 :6~:11 이 존재 이유를 적는다. 벌크와 원자적 연산과 집계와 지리 공간 실행기는 드라이버 수준 명령을 만들기 때문에 제한 없는 템플릿이 필요하고, 그것들은 플랫폼 안에서 가드레일을 설치하는 코드라 호출자의 콜백과 위치가 다르므로, raw 손잡이는 모든 콜백이 받는 것이 아니라 이름을 대고 요청하는 타입이라는 것이다.
:13~:14 가 그것을 MongoCollectionAccess 밖에 둔 이유를 적는다. 범위 API 와 탈출구를 함께 제공하는 인터페이스는 탈출구를 제공하는 것이다.
scoped() 호출 0 과 rawOperations() 호출 다섯
.scoped() 를 부르는 줄이 0 이다. 나오는 자리는 MongoCollectionAccess:32 와 ReactiveMongoCollectionAccess:31 의 선언, DefaultMongoImperativeExecutor:184 와 DefaultReactiveMongoExecutor:218 의 구현 넷뿐이다.
.rawOperations() 를 부르는 줄은 다섯이다. SpringMongoGeospatialOperations:58 과 :82, MongoAtomicOperationsTemplate:84 와 :92, MongoBulkExecutor:69 다.
같은 검색이 파일 6444 개를 훑었으므로 0 은 검색 대상이 잡히지 않아 나온 값이 아니다. 같은 정규식이 다른 이름에 대해 다섯을 찾았다.
DefaultMongoImperativeExecutor:192~:194 의 rawOperations() 는 :82 가 일관성 프로파일로 고른 템플릿을 그대로 돌려준다. 컬렉션도 데드라인도 붙지 않는다.
세 실행기 안에서 maxTime 이나 timeout 이 나오는 줄도 0 이다. 같은 검색을 BoundScopedOperations 에 걸면 8 줄이 나온다.
저장소가 그 손잡이에 대해 아는 것
MongoRawAccessBoundaryTest 가 그 두 탈출구를 막는 아키텍처 가드다. :37 이 금지 문자열로 .rawOperations() 와 .executeInternal( 을 세우고, :40~:41 이 허용 경로를 mongo 어댑터의 프로덕션 소스로 한정한다.
그 자바독 :18~:20 이 두 탈출구가 무엇을 내주는지 적는다. 컬렉션 결속도, 일관성 템플릿도, 데드라인도, 결과 예산도 없는 스프링 데이터의 무제한 MongoOperations 다.
:24~:26 은 둘 다 플랫폼 안에서는 정말로 필요하다고 적는다. 지리 공간과 원자적 연산과 벌크가 그것 위에 세워져 있으므로 규칙은 지우라는 것이 아니라 밖에서 부르지 말라는 것이고, 그것은 가시성 문제가 아니라 경계 문제라는 것이다.
그래서 저장소는 데드라인이 없다는 것을 알고 있고, 그 사실을 경계로만 다룬다. 밖에서 못 부르게 막을 뿐 안에서 부른 다섯 자리가 데드라인 없이 도는 것은 다루지 않는다.
서버에 데드라인을 붙이는 다른 세 경로
PolicyAwareMongoAggregationExecutor:96 이 Math.min(registered.maxTimeMillis(), contextMillis) 를 만들고 :106 이 그것을 AggregationOptions 의 maxTime 에 넣는다. :91 주석이 컨텍스트의 타임아웃은 maxTimeMS 를 좁힐 수는 있어도 늘릴 수는 없다고 적는다.
PolicyAwareMongoQueryBuilder:200 과 MongoReactiveCursorPublisher:58 은 budget.maxTimeMillis() 만 쓴다.
context.timeout() 과 narrowedTo 가 나오는 자리를 전부 세면 예산을 컨텍스트로 좁히는 것은 PolicyAwareMongoAggregationExecutor:72 와 :92 뿐이다. DefaultMongoImperativeExecutor:83·:93·:103 은 사후 검사이고, DefaultReactiveMongoExecutor:76·:108 은 Reactor 의 클라이언트 쪽 timeout 연산자다.
그래서 context.timeout() 이 서버까지 가는 경로는 집계 하나이고, 그것도 예산과의 최솟값으로만 간다.
지리 공간과 원자적 연산과 벌크에 남는 사후 검사
DefaultMongoImperativeExecutor:88 이 콜백을 부르고 :89 가 경과 시간을 재고 :93 이 그것을 context.timeout() 과 견준다.
:94~:96 주석이 그 검사의 한계를 적는다. 컨텍스트가 모든 연산에 데드라인을 선언하는데 아무것도 그것과 견주지 않았고, 콜백은 드라이버 호출 중간에 끊을 수 없어 이 검사가 작업을 끊지 못하며, 다만 예산을 넘긴 작업에 성공을 보고하는 것보다는 위반을 보고하는 편이 낫다는 것이다.
SpringMongoGeospatialOperations 와 MongoAtomicOperationsTemplate 와 MongoBulkExecutor 는 그 사후 검사만 받는다. 서버에는 데드라인이 전달되지 않는다.
원문에 없는 것
원문은 scoped() 를 부르는 프로덕션 코드가 0 이고 세 실행기가 rawOperations() 를 쓴다고 적으면서, 서버에 데드라인을 붙이는 경로별 어휘를 표로 갈라 놓는다. 그대로다.
여기에 더한 것은 셋이다. 그 0 이 검색 실패가 아니라는 확인 — 같은 pathspec 이 6444 개 파일을 훑었고 같은 검색이 rawOperations() 에 대해 다섯을 찾았다. 데드라인이 붙는 자리가 :98 하나가 아니라 :53 과 둘이라는 것. 그리고 raw 손잡이가 실수가 아니라 설계라는 저장소 자신의 진술 둘 — MongoPlatformCollectionAccess:8~:14 와 MongoRawAccessBoundaryTest:18~:26 이다. 뒤쪽은 그 손잡이에 데드라인이 없다는 것까지 적으면서 경계만 지킨다.
확인하지 못한 것
maxTimeMsec 이 붙은 질의를 서버에 보내 실제로 잘리는 것을 관측하지 않았다.
세 컴포넌트를 scoped() 로 옮기려면 어떤 연산이 더 있어야 하는지 조사하지 않았다.
scoped() 의 의도된 호출자가 누구인지, 포크의 콜백이 그 메서드를 부르도록 설계된 것인지 설계 문서로 확인하지 않았다.